sip.js
Version:
A SIP library for JavaScript
242 lines (241 loc) • 9.47 kB
JavaScript
/* eslint-disable no-inner-declarations */
/* eslint-disable @typescript-eslint/no-namespace */
import { Grammar } from "../../grammar/grammar.js";
import { IncomingRequestMessage } from "./incoming-request-message.js";
import { IncomingResponseMessage } from "./incoming-response-message.js";
/**
* Extract and parse every header of a SIP message.
* @internal
*/
export var Parser;
(function (Parser) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function getHeader(data, headerStart) {
// 'start' position of the header.
let start = headerStart;
// 'end' position of the header.
let end = 0;
// 'partial end' position of the header.
let partialEnd = 0;
// End of message.
if (data.substring(start, start + 2).match(/(^\r\n)/)) {
return -2;
}
while (end === 0) {
// Partial End of Header.
partialEnd = data.indexOf("\r\n", start);
// 'indexOf' returns -1 if the value to be found never occurs.
if (partialEnd === -1) {
return partialEnd;
}
if (!data.substring(partialEnd + 2, partialEnd + 4).match(/(^\r\n)/) &&
data.charAt(partialEnd + 2).match(/(^\s+)/)) {
// Not the end of the message. Continue from the next position.
start = partialEnd + 2;
}
else {
end = partialEnd;
}
}
return end;
}
Parser.getHeader = getHeader;
function parseHeader(message,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data, headerStart, headerEnd) {
const hcolonIndex = data.indexOf(":", headerStart);
const headerName = data.substring(headerStart, hcolonIndex).trim();
const headerValue = data.substring(hcolonIndex + 1, headerEnd).trim();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let parsed;
// If header-field is well-known, parse it.
switch (headerName.toLowerCase()) {
case "via":
case "v":
message.addHeader("via", headerValue);
if (message.getHeaders("via").length === 1) {
parsed = message.parseHeader("Via");
if (parsed) {
message.via = parsed;
message.viaBranch = parsed.branch;
}
}
else {
parsed = 0;
}
break;
case "from":
case "f":
message.setHeader("from", headerValue);
parsed = message.parseHeader("from");
if (parsed) {
message.from = parsed;
message.fromTag = parsed.getParam("tag");
}
break;
case "to":
case "t":
message.setHeader("to", headerValue);
parsed = message.parseHeader("to");
if (parsed) {
message.to = parsed;
message.toTag = parsed.getParam("tag");
}
break;
case "record-route":
parsed = Grammar.parse(headerValue, "Record_Route");
if (parsed === -1) {
parsed = undefined;
break;
}
if (!(parsed instanceof Array)) {
parsed = undefined;
break;
}
parsed.forEach((header) => {
message.addHeader("record-route", headerValue.substring(header.position, header.offset));
message.headers["Record-Route"][message.getHeaders("record-route").length - 1].parsed = header.parsed;
});
break;
case "call-id":
case "i":
message.setHeader("call-id", headerValue);
parsed = message.parseHeader("call-id");
if (parsed) {
message.callId = headerValue;
}
break;
case "contact":
case "m":
parsed = Grammar.parse(headerValue, "Contact");
if (parsed === -1) {
parsed = undefined;
break;
}
if (!(parsed instanceof Array)) {
parsed = undefined;
break;
}
parsed.forEach((header) => {
message.addHeader("contact", headerValue.substring(header.position, header.offset));
message.headers.Contact[message.getHeaders("contact").length - 1].parsed = header.parsed;
});
break;
case "content-length":
case "l":
message.setHeader("content-length", headerValue);
parsed = message.parseHeader("content-length");
break;
case "content-type":
case "c":
message.setHeader("content-type", headerValue);
parsed = message.parseHeader("content-type");
break;
case "cseq":
message.setHeader("cseq", headerValue);
parsed = message.parseHeader("cseq");
if (parsed) {
message.cseq = parsed.value;
}
if (message instanceof IncomingResponseMessage) {
message.method = parsed.method;
}
break;
case "max-forwards":
message.setHeader("max-forwards", headerValue);
parsed = message.parseHeader("max-forwards");
break;
case "www-authenticate":
message.setHeader("www-authenticate", headerValue);
parsed = message.parseHeader("www-authenticate");
break;
case "proxy-authenticate":
message.setHeader("proxy-authenticate", headerValue);
parsed = message.parseHeader("proxy-authenticate");
break;
case "refer-to":
case "r":
message.setHeader("refer-to", headerValue);
parsed = message.parseHeader("refer-to");
if (parsed) {
message.referTo = parsed;
}
break;
default:
// Do not parse this header.
message.addHeader(headerName.toLowerCase(), headerValue);
parsed = 0;
}
if (parsed === undefined) {
return {
error: "error parsing header '" + headerName + "'"
};
}
else {
return true;
}
}
Parser.parseHeader = parseHeader;
function parseMessage(data, logger) {
let headerStart = 0;
let headerEnd = data.indexOf("\r\n");
if (headerEnd === -1) {
logger.warn("no CRLF found, not a SIP message, discarded");
return;
}
// Parse first line. Check if it is a Request or a Reply.
const firstLine = data.substring(0, headerEnd);
const parsed = Grammar.parse(firstLine, "Request_Response");
let message;
if (parsed === -1) {
logger.warn('error parsing first line of SIP message: "' + firstLine + '"');
return;
}
else if (!parsed.status_code) {
message = new IncomingRequestMessage();
message.method = parsed.method;
message.ruri = parsed.uri;
}
else {
message = new IncomingResponseMessage();
message.statusCode = parsed.status_code;
message.reasonPhrase = parsed.reason_phrase;
}
message.data = data;
headerStart = headerEnd + 2;
// Loop over every line in data. Detect the end of each header and parse
// it or simply add to the headers collection.
let bodyStart;
// eslint-disable-next-line no-constant-condition
while (true) {
headerEnd = getHeader(data, headerStart);
// The SIP message has normally finished.
if (headerEnd === -2) {
bodyStart = headerStart + 2;
break;
}
else if (headerEnd === -1) {
// data.indexOf returned -1 due to a malformed message.
logger.error("malformed message");
return;
}
const parsedHeader = parseHeader(message, data, headerStart, headerEnd);
if (parsedHeader && parsedHeader !== true) {
logger.error(parsedHeader.error);
return;
}
headerStart = headerEnd + 2;
}
// RFC3261 18.3.
// If there are additional bytes in the transport packet
// beyond the end of the body, they MUST be discarded.
if (message.hasHeader("content-length")) {
message.body = data.substr(bodyStart, Number(message.getHeader("content-length")));
}
else {
message.body = data.substring(bodyStart);
}
return message;
}
Parser.parseMessage = parseMessage;
})(Parser = Parser || (Parser = {}));