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
27 changes: 27 additions & 0 deletions src/Grammar.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import NameAddrHeader = require('./NameAddrHeader');
import URI = require('./URI');

/**
* The result of `parse()`, whose shape depends on the start rule used.
Expand Down Expand Up @@ -75,6 +76,32 @@ export type EventHeader = {
*/
export type ReferToHeader = NameAddrHeader;

/**
* Result of the `Request_Response` start rule when it matches a `Status_Line`.
*/
export type StatusLine = {
status_code: number;
reason_phrase: string;
};

/**
* Result of the `Request_Response` start rule when it matches a `Request_Line`.
*
* NOTE: `uri` is only set when the Request-URI is a SIP URI. `Request_URI` also
* accepts an `absoluteURI`, which the grammar does not turn into a `URI`.
*/
export type RequestLine = {
method: string;
uri?: URI;
};

/**
* Result of the `Request_Response` start rule. Which of the two it is can be
* told apart with `'status_code' in parsed`, as the grammar only creates the
* properties the matched rule sets.
*/
export type RequestResponse = StatusLine | RequestLine;

export function parse(input: string, startRule?: string): Grammar;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
57 changes: 40 additions & 17 deletions src/Parser.js → src/Parser.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,49 @@
const { Logger } = require('./Logger');
const Grammar = require('./Grammar');
const { IncomingMessageBuilder } = require('./IncomingMessageBuilder');
import { Logger } from './Logger';
import * as Grammar from './Grammar';
import { IncomingMessageBuilder } from './IncomingMessageBuilder';
import type { IncomingRequest, IncomingResponse, UALike } from './SIPMessage';

const logger = new Logger('Parser');

/**
* Parse SIP Message
*/
exports.parseMessage = (data, ua) => {
let bodyStart;
export const parseMessage = (
data: string,
ua: UALike
): IncomingRequest | IncomingResponse | undefined => {
let bodyStart: number;
let headerEnd = data.indexOf('\r\n');

if (headerEnd === -1) {
logger.warn('parseMessage() | no CRLF found, not a SIP message');

return;
return undefined;
}

// 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');
const parsed: Grammar.RequestResponse | -1 = Grammar.parse(
firstLine,
'Request_Response'
);

if (parsed === -1) {
logger.warn(
`parseMessage() | error parsing first line of SIP message: "${firstLine}"`
);

return;
return undefined;
}

const isResponse = Boolean(parsed.status_code);
const builder = new IncomingMessageBuilder();

let 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.
*/

while (true) {
headerEnd = getHeader(data, headerStart);

Expand All @@ -49,15 +56,15 @@ exports.parseMessage = (data, ua) => {
else if (headerEnd === -1) {
logger.warn('parseMessage() | malformed message');

return;
return undefined;
}

const result = parseHeader(builder, data, headerStart, headerEnd);

if (result !== true) {
logger.warn('parseMessage() |', result.error);

return;
return undefined;
}

headerStart = headerEnd + 2;
Expand All @@ -72,27 +79,38 @@ exports.parseMessage = (data, ua) => {
if (builder.hasHeader('content-length')) {
const contentLength = builder.getHeader('content-length');

body = data.substr(bodyStart, contentLength);
body = data.substr(bodyStart, Number(contentLength));
} else {
body = data.substring(bodyStart);
}

if (isResponse) {
if ('status_code' in parsed) {
return builder.buildResponse(
data,
parsed.status_code,
parsed.reason_phrase,
body
);
} else {
return builder.buildRequest(ua, data, parsed.method, parsed.uri, body);
}

/* RFC 3261 8.2.2.1. The grammar accepts any absoluteURI as Request-URI but
* only turns a SIP one into a `URI`, so anything else cannot be handled.
*/
if (!parsed.uri) {
logger.warn(
`parseMessage() | unsupported Request-URI scheme in "${firstLine}"`
);

return undefined;
}

return builder.buildRequest(ua, data, parsed.method, parsed.uri, body);
};

/**
* Extract and parse every header of a SIP message.
*/
function getHeader(data, headerStart) {
function getHeader(data: string, headerStart: number): number {
// 'start' position of the header.
let start = headerStart;
// 'end' position of the header.
Expand Down Expand Up @@ -128,7 +146,12 @@ function getHeader(data, headerStart) {
return end;
}

function parseHeader(builder, data, headerStart, headerEnd) {
function parseHeader(
builder: IncomingMessageBuilder,
data: string,
headerStart: number,
headerEnd: number
): true | { error: string } {
const hcolonIndex = data.indexOf(':', headerStart);
const headerName = data.substring(headerStart, hcolonIndex).trim();
const headerValue = data.substring(hcolonIndex + 1, headerEnd).trim();
Expand Down
12 changes: 8 additions & 4 deletions src/test/test-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import * as consts from './include/consts';
// eslint-disable-next-line @typescript-eslint/no-require-imports
const JsSIP = require('../JsSIP.js');
const { URI, NameAddrHeader, Grammar, WebSocketInterface, UA } = JsSIP;
// eslint-disable-next-line @typescript-eslint/no-require-imports
const Parser = require('../Parser.js');

import * as Parser from '../Parser';

describe('parser', () => {
test('parse URI', () => {
Expand Down Expand Up @@ -429,14 +429,18 @@ P-Preferred-Identity: "Cullen Jennings" <sip:fluffy@cisco.com>\r\n\r\n';
const ua = new UA(config as ConstructorParameters<typeof UA>[0]);
const message = Parser.parseMessage(data, ua);

if (!message) {
throw new Error('parseMessage() did not return a message');
}

expect(message.hasHeader('P-Preferred-Identity')).toBe(true);

const pai = message.getHeader('P-Preferred-Identity');
const nameAddress = NameAddrHeader.parse(pai);

expect(nameAddress instanceof NameAddrHeader).toBeTruthy();
expect(nameAddress!.uri.user).toBe('fluffy');
expect(nameAddress!.uri.host).toBe('cisco.com');
expect(nameAddress.uri.user).toBe('fluffy');
expect(nameAddress.uri.host).toBe('cisco.com');

expect(message.hasHeader('Privacy')).toBe(true);
expect(message.getHeader('Privacy')).toBe('id');
Expand Down
Loading