Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/UA.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const Exceptions = require('./Exceptions');
const URI = require('./URI');
const Parser = require('./Parser');
const SIPMessage = require('./SIPMessage');
const sanityCheck = require('./sanityCheck');
const { sanityCheck } = require('./sanityCheck');
const config = require('./Configuration');

const logger = new Logger('UA');
Expand Down
186 changes: 112 additions & 74 deletions src/sanityCheck.js → src/sanityCheck.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,70 @@
const { Logger } = require('./Logger');
const JsSIP_C = require('./Constants');
const SIPMessage = require('./SIPMessage');
const Utils = require('./Utils');
import * as JsSIP_C from './Constants';
import { Logger } from './Logger';
import { IncomingRequest, IncomingResponse } from './SIPMessage';
import type { IncomingMessage, UALike } from './SIPMessage';
import type { TransactionRegistry } from './Transactions';
import type { Transport } from './Transport';
import * as Utils from './Utils';

const logger = new Logger('sanityCheck');

/**
* The `UA` surface the sanity checks depend on.
*
* TODO: Replace with `import type { UA } from './UA'` once `UA` is ported to
* TypeScript.
*/
export interface SanityCheckUA extends UALike {
readonly _transactions: TransactionRegistry;
}

/**
* A single check. Returns `false` to drop the message, `true` to let it
* through.
*/
type Check<M extends IncomingMessage> = (
message: M,
transport: Transport,
ua: SanityCheckUA
) => boolean;

// Checks for requests and responses.
const all = [minimumHeaders];
const all: Check<IncomingMessage>[] = [minimumHeaders];

// Checks for requests.
const requests = [
const requests: Check<IncomingRequest>[] = [
rfc3261_8_2_2_1,
rfc3261_16_3_4,
rfc3261_18_3_request,
rfc3261_8_2_2_2,
];

// Checks for responses.
const responses = [rfc3261_8_1_3_3, rfc3261_18_3_response];

// local variables.
let message;
let ua;
let transport;

module.exports = (m, u, t) => {
message = m;
ua = u;
transport = t;
const responses: Check<IncomingResponse>[] = [
rfc3261_8_1_3_3,
rfc3261_18_3_response,
];

export const sanityCheck = (
message: IncomingRequest | IncomingResponse,
ua: SanityCheckUA,
transport: Transport
): boolean => {
for (const check of all) {
if (check() === false) {
if (!check(message, transport, ua)) {
return false;
}
}

if (message instanceof SIPMessage.IncomingRequest) {
if (message instanceof IncomingRequest) {
for (const check of requests) {
if (check() === false) {
if (!check(message, transport, ua)) {
return false;
}
}
} else if (message instanceof SIPMessage.IncomingResponse) {
} else if (message instanceof IncomingResponse) {
for (const check of responses) {
if (check() === false) {
if (!check(message, transport, ua)) {
return false;
}
}
Expand Down Expand Up @@ -73,73 +94,87 @@ module.exports = (m, u, t) => {
*/

// Sanity Check functions for requests.
function rfc3261_8_2_2_1() {
if (message.s('to').uri.scheme !== 'sip') {
reply(416);
function rfc3261_8_2_2_1(
message: IncomingRequest,
transport: Transport
): boolean {
if (message.to.uri.scheme !== 'sip') {
reply(message, transport, 416);

return false;
}

return true;
}

function rfc3261_16_3_4() {
function rfc3261_16_3_4(
message: IncomingRequest,
transport: Transport,
ua: SanityCheckUA
): boolean {
if (!message.to_tag) {
if (message.call_id.substr(0, 5) === ua.configuration.jssip_id) {
reply(482);
if (message.call_id?.substring(0, 5) === ua.configuration.jssip_id) {
reply(message, transport, 482);

return false;
}
}

return true;
}

function rfc3261_18_3_request() {
function rfc3261_18_3_request(
message: IncomingRequest,
transport: Transport
): boolean {
const len = Utils.str_utf8_length(message.body);
const contentLength = message.getHeader('content-length');
const contentLength = Number(message.getHeader('content-length'));

if (len < contentLength) {
reply(400);
reply(message, transport, 400);

return false;
}

return true;
}

function rfc3261_8_2_2_2() {
function rfc3261_8_2_2_2(
message: IncomingRequest,
transport: Transport,
ua: SanityCheckUA
): boolean {
const fromTag = message.from_tag;
const call_id = message.call_id;
const cseq = message.cseq;
let tr;

// Accept any in-dialog request.
if (message.to_tag) {
return;
return true;
}

// INVITE request.
if (message.method === JsSIP_C.INVITE) {
// If the branch matches the key of any IST then assume it is a retransmission
// and ignore the INVITE.
// TODO: we should reply the last response.
if (ua._transactions.ist[message.via_branch]) {
// TODO: A Via with no `branch` parameter keys on the literal string
// 'undefined', so two such requests look like retransmissions of each
// other. Kept as is to preserve behaviour.
if (ua._transactions.ist[String(message.via_branch)]) {
return false;
}
// Otherwise check whether it is a merged request.
else {
for (const transaction in ua._transactions.ist) {
for (const tr of Object.values(ua._transactions.ist)) {
if (
Object.prototype.hasOwnProperty.call(
ua._transactions.ist,
transaction
)
tr.request.from_tag === fromTag &&
tr.request.call_id === call_id &&
tr.request.cseq === cseq
) {
tr = ua._transactions.ist[transaction];
if (
tr.request.from_tag === fromTag &&
tr.request.call_id === call_id &&
tr.request.cseq === cseq
) {
reply(482);

return false;
}
reply(message, transport, 482);

return false;
}
}
}
Expand All @@ -150,45 +185,44 @@ function rfc3261_8_2_2_2() {
// If the branch matches the key of any NIST then assume it is a retransmission
// and ignore the request.
// TODO: we should reply the last response.
else if (ua._transactions.nist[message.via_branch]) {
else if (ua._transactions.nist[String(message.via_branch)]) {
return false;
}

// Otherwise check whether it is a merged request.
else {
for (const transaction in ua._transactions.nist) {
for (const tr of Object.values(ua._transactions.nist)) {
if (
Object.prototype.hasOwnProperty.call(ua._transactions.nist, transaction)
tr.request.from_tag === fromTag &&
tr.request.call_id === call_id &&
tr.request.cseq === cseq
) {
tr = ua._transactions.nist[transaction];
if (
tr.request.from_tag === fromTag &&
tr.request.call_id === call_id &&
tr.request.cseq === cseq
) {
reply(482);
reply(message, transport, 482);

return false;
}
return false;
}
}
}

return true;
}

// Sanity Check functions for responses.
function rfc3261_8_1_3_3() {
function rfc3261_8_1_3_3(message: IncomingResponse): boolean {
if (message.getHeaders('via').length > 1) {
logger.debug(
'more than one Via header field present in the response, dropping the response'
);

return false;
}

return true;
}

function rfc3261_18_3_response() {
function rfc3261_18_3_response(message: IncomingResponse): boolean {
const len = Utils.str_utf8_length(message.body),
contentLength = message.getHeader('content-length');
contentLength = Number(message.getHeader('content-length'));

if (len < contentLength) {
logger.debug(
Expand All @@ -197,10 +231,12 @@ function rfc3261_18_3_response() {

return false;
}

return true;
}

// Sanity Check functions for requests and responses.
function minimumHeaders() {
function minimumHeaders(message: IncomingMessage): boolean {
const mandatoryHeaders = ['from', 'to', 'call_id', 'cseq', 'via'];

for (const header of mandatoryHeaders) {
Expand All @@ -212,26 +248,28 @@ function minimumHeaders() {
return false;
}
}

return true;
}

// Reply.
function reply(status_code) {
function reply(
message: IncomingRequest,
transport: Transport,
status_code: number
): void {
const vias = message.getHeaders('via');

let to;
let response = `SIP/2.0 ${status_code} ${JsSIP_C.REASON_PHRASE[status_code]}\r\n`;

for (const via of vias) {
response += `Via: ${via}\r\n`;
}

to = message.getHeader('To');

if (!message.to_tag) {
to += `;tag=${Utils.newTag()}`;
}
const to = message.getHeader('To');
const toTag = message.to_tag ? '' : `;tag=${Utils.newTag()}`;

response += `To: ${to}\r\n`;
response += `To: ${to}${toTag}\r\n`;
response += `From: ${message.getHeader('From')}\r\n`;
response += `Call-ID: ${message.call_id}\r\n`;
response += `CSeq: ${message.cseq} ${message.method}\r\n`;
Expand Down
Loading