UNPKG

@stomp/stompjs

Version:

STOMP client for Javascript and Typescript

213 lines 7.57 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); /** * @internal */ var NULL = 0; /** * @internal */ var LF = 10; /** * @internal */ var CR = 13; /** * @internal */ var COLON = 58; /** * This is an evented, rec descent parser. * A stream of Octets can be passed and whenever it recognizes * a complete Frame or an incoming ping it will invoke the registered callbacks. * * All incoming Octets are fed into _onByte function. * Depending on current state the _onByte function keeps changing. * Depending on the state it keeps accumulating into _token and _results. * State is indicated by current value of _onByte, all states are named as _collect. * * STOMP standards https://stomp.github.io/stomp-specification-1.2.html * imply that all lengths are considered in bytes (instead of string lengths). * So, before actual parsing, if the incoming data is String it is converted to Octets. * This allows faithful implementation of the protocol and allows NULL Octets to be present in the body. * * There is no peek function on the incoming data. * When a state change occurs based on an Octet without consuming the Octet, * the Octet, after state change, is fed again (_reinjectByte). * This became possible as the state change can be determined by inspecting just one Octet. * * There are two modes to collect the body, if content-length header is there then it by counting Octets * otherwise it is determined by NULL terminator. * * Following the standards, the command and headers are converted to Strings * and the body is returned as Octets. * Headers are returned as an array and not as Hash - to allow multiple occurrence of an header. * * This parser does not use Regular Expressions as that can only operate on Strings. * * It handles if multiple STOMP frames are given as one chunk, a frame is split into multiple chunks, or * any combination there of. The parser remembers its state (any partial frame) and continues when a new chunk * is pushed. * * Typically the higher level function will convert headers to Hash, handle unescaping of header values * (which is protocol version specific), and convert body to text. * * Check the parser.spec.js to understand cases that this parser is supposed to handle. * * Part of `@stomp/stompjs`. * * @internal */ var Parser = /** @class */ (function () { function Parser(onFrame, onIncomingPing) { this.onFrame = onFrame; this.onIncomingPing = onIncomingPing; this._encoder = new TextEncoder(); this._decoder = new TextDecoder(); this._token = []; this._initState(); } Parser.prototype.parseChunk = function (segment, appendMissingNULLonIncoming) { if (appendMissingNULLonIncoming === void 0) { appendMissingNULLonIncoming = false; } var chunk; if ((segment instanceof ArrayBuffer)) { chunk = new Uint8Array(segment); } else { chunk = this._encoder.encode(segment); } // See https://github.com/stomp-js/stompjs/issues/89 // Remove when underlying issue is fixed. // // Send a NULL byte, if the last byte of a Text frame was not NULL.F if (appendMissingNULLonIncoming && chunk[chunk.length - 1] !== 0) { var chunkWithNull = new Uint8Array(chunk.length + 1); chunkWithNull.set(chunk, 0); chunkWithNull[chunk.length] = 0; chunk = chunkWithNull; } // tslint:disable-next-line:prefer-for-of for (var i = 0; i < chunk.length; i++) { var byte = chunk[i]; this._onByte(byte); } }; // The following implements a simple Rec Descent Parser. // The grammar is simple and just one byte tells what should be the next state Parser.prototype._collectFrame = function (byte) { if (byte === NULL) { // Ignore return; } if (byte === CR) { // Ignore CR return; } if (byte === LF) { // Incoming Ping this.onIncomingPing(); return; } this._onByte = this._collectCommand; this._reinjectByte(byte); }; Parser.prototype._collectCommand = function (byte) { if (byte === CR) { // Ignore CR return; } if (byte === LF) { this._results.command = this._consumeTokenAsUTF8(); this._onByte = this._collectHeaders; return; } this._consumeByte(byte); }; Parser.prototype._collectHeaders = function (byte) { if (byte === CR) { // Ignore CR return; } if (byte === LF) { this._setupCollectBody(); return; } this._onByte = this._collectHeaderKey; this._reinjectByte(byte); }; Parser.prototype._reinjectByte = function (byte) { this._onByte(byte); }; Parser.prototype._collectHeaderKey = function (byte) { if (byte === COLON) { this._headerKey = this._consumeTokenAsUTF8(); this._onByte = this._collectHeaderValue; return; } this._consumeByte(byte); }; Parser.prototype._collectHeaderValue = function (byte) { if (byte === CR) { // Ignore CR return; } if (byte === LF) { this._results.headers.push([this._headerKey, this._consumeTokenAsUTF8()]); this._headerKey = undefined; this._onByte = this._collectHeaders; return; } this._consumeByte(byte); }; Parser.prototype._setupCollectBody = function () { var contentLengthHeader = this._results.headers.filter(function (header) { return header[0] === 'content-length'; })[0]; if (contentLengthHeader) { this._bodyBytesRemaining = parseInt(contentLengthHeader[1], 10); this._onByte = this._collectBodyFixedSize; } else { this._onByte = this._collectBodyNullTerminated; } }; Parser.prototype._collectBodyNullTerminated = function (byte) { if (byte === NULL) { this._retrievedBody(); return; } this._consumeByte(byte); }; Parser.prototype._collectBodyFixedSize = function (byte) { // It is post decrement, so that we discard the trailing NULL octet if (this._bodyBytesRemaining-- === 0) { this._retrievedBody(); return; } this._consumeByte(byte); }; Parser.prototype._retrievedBody = function () { this._results.binaryBody = this._consumeTokenAsRaw(); this.onFrame(this._results); this._initState(); }; // Rec Descent Parser helpers Parser.prototype._consumeByte = function (byte) { this._token.push(byte); }; Parser.prototype._consumeTokenAsUTF8 = function () { return this._decoder.decode(this._consumeTokenAsRaw()); }; Parser.prototype._consumeTokenAsRaw = function () { var rawResult = new Uint8Array(this._token); this._token = []; return rawResult; }; Parser.prototype._initState = function () { this._results = { command: undefined, headers: [], binaryBody: undefined }; this._token = []; this._headerKey = undefined; this._onByte = this._collectFrame; }; return Parser; }()); exports.Parser = Parser; //# sourceMappingURL=parser.js.map