UNPKG

6.97 kBJavaScriptView Raw
1/**
2 * @internal
3 */
4const NULL = 0;
5/**
6 * @internal
7 */
8const LF = 10;
9/**
10 * @internal
11 */
12const CR = 13;
13/**
14 * @internal
15 */
16const COLON = 58;
17/**
18 * This is an evented, rec descent parser.
19 * A stream of Octets can be passed and whenever it recognizes
20 * a complete Frame or an incoming ping it will invoke the registered callbacks.
21 *
22 * All incoming Octets are fed into _onByte function.
23 * Depending on current state the _onByte function keeps changing.
24 * Depending on the state it keeps accumulating into _token and _results.
25 * State is indicated by current value of _onByte, all states are named as _collect.
26 *
27 * STOMP standards https://stomp.github.io/stomp-specification-1.2.html
28 * imply that all lengths are considered in bytes (instead of string lengths).
29 * So, before actual parsing, if the incoming data is String it is converted to Octets.
30 * This allows faithful implementation of the protocol and allows NULL Octets to be present in the body.
31 *
32 * There is no peek function on the incoming data.
33 * When a state change occurs based on an Octet without consuming the Octet,
34 * the Octet, after state change, is fed again (_reinjectByte).
35 * This became possible as the state change can be determined by inspecting just one Octet.
36 *
37 * There are two modes to collect the body, if content-length header is there then it by counting Octets
38 * otherwise it is determined by NULL terminator.
39 *
40 * Following the standards, the command and headers are converted to Strings
41 * and the body is returned as Octets.
42 * Headers are returned as an array and not as Hash - to allow multiple occurrence of an header.
43 *
44 * This parser does not use Regular Expressions as that can only operate on Strings.
45 *
46 * It handles if multiple STOMP frames are given as one chunk, a frame is split into multiple chunks, or
47 * any combination there of. The parser remembers its state (any partial frame) and continues when a new chunk
48 * is pushed.
49 *
50 * Typically the higher level function will convert headers to Hash, handle unescaping of header values
51 * (which is protocol version specific), and convert body to text.
52 *
53 * Check the parser.spec.js to understand cases that this parser is supposed to handle.
54 *
55 * Part of `@stomp/stompjs`.
56 *
57 * @internal
58 */
59export class Parser {
60 constructor(onFrame, onIncomingPing) {
61 this.onFrame = onFrame;
62 this.onIncomingPing = onIncomingPing;
63 this._encoder = new TextEncoder();
64 this._decoder = new TextDecoder();
65 this._token = [];
66 this._initState();
67 }
68 parseChunk(segment, appendMissingNULLonIncoming = false) {
69 let chunk;
70 if (segment instanceof ArrayBuffer) {
71 chunk = new Uint8Array(segment);
72 }
73 else {
74 chunk = this._encoder.encode(segment);
75 }
76 // See https://github.com/stomp-js/stompjs/issues/89
77 // Remove when underlying issue is fixed.
78 //
79 // Send a NULL byte, if the last byte of a Text frame was not NULL.F
80 if (appendMissingNULLonIncoming && chunk[chunk.length - 1] !== 0) {
81 const chunkWithNull = new Uint8Array(chunk.length + 1);
82 chunkWithNull.set(chunk, 0);
83 chunkWithNull[chunk.length] = 0;
84 chunk = chunkWithNull;
85 }
86 // tslint:disable-next-line:prefer-for-of
87 for (let i = 0; i < chunk.length; i++) {
88 const byte = chunk[i];
89 this._onByte(byte);
90 }
91 }
92 // The following implements a simple Rec Descent Parser.
93 // The grammar is simple and just one byte tells what should be the next state
94 _collectFrame(byte) {
95 if (byte === NULL) {
96 // Ignore
97 return;
98 }
99 if (byte === CR) {
100 // Ignore CR
101 return;
102 }
103 if (byte === LF) {
104 // Incoming Ping
105 this.onIncomingPing();
106 return;
107 }
108 this._onByte = this._collectCommand;
109 this._reinjectByte(byte);
110 }
111 _collectCommand(byte) {
112 if (byte === CR) {
113 // Ignore CR
114 return;
115 }
116 if (byte === LF) {
117 this._results.command = this._consumeTokenAsUTF8();
118 this._onByte = this._collectHeaders;
119 return;
120 }
121 this._consumeByte(byte);
122 }
123 _collectHeaders(byte) {
124 if (byte === CR) {
125 // Ignore CR
126 return;
127 }
128 if (byte === LF) {
129 this._setupCollectBody();
130 return;
131 }
132 this._onByte = this._collectHeaderKey;
133 this._reinjectByte(byte);
134 }
135 _reinjectByte(byte) {
136 this._onByte(byte);
137 }
138 _collectHeaderKey(byte) {
139 if (byte === COLON) {
140 this._headerKey = this._consumeTokenAsUTF8();
141 this._onByte = this._collectHeaderValue;
142 return;
143 }
144 this._consumeByte(byte);
145 }
146 _collectHeaderValue(byte) {
147 if (byte === CR) {
148 // Ignore CR
149 return;
150 }
151 if (byte === LF) {
152 this._results.headers.push([this._headerKey, this._consumeTokenAsUTF8()]);
153 this._headerKey = undefined;
154 this._onByte = this._collectHeaders;
155 return;
156 }
157 this._consumeByte(byte);
158 }
159 _setupCollectBody() {
160 const contentLengthHeader = this._results.headers.filter((header) => {
161 return header[0] === 'content-length';
162 })[0];
163 if (contentLengthHeader) {
164 this._bodyBytesRemaining = parseInt(contentLengthHeader[1], 10);
165 this._onByte = this._collectBodyFixedSize;
166 }
167 else {
168 this._onByte = this._collectBodyNullTerminated;
169 }
170 }
171 _collectBodyNullTerminated(byte) {
172 if (byte === NULL) {
173 this._retrievedBody();
174 return;
175 }
176 this._consumeByte(byte);
177 }
178 _collectBodyFixedSize(byte) {
179 // It is post decrement, so that we discard the trailing NULL octet
180 if (this._bodyBytesRemaining-- === 0) {
181 this._retrievedBody();
182 return;
183 }
184 this._consumeByte(byte);
185 }
186 _retrievedBody() {
187 this._results.binaryBody = this._consumeTokenAsRaw();
188 this.onFrame(this._results);
189 this._initState();
190 }
191 // Rec Descent Parser helpers
192 _consumeByte(byte) {
193 this._token.push(byte);
194 }
195 _consumeTokenAsUTF8() {
196 return this._decoder.decode(this._consumeTokenAsRaw());
197 }
198 _consumeTokenAsRaw() {
199 const rawResult = new Uint8Array(this._token);
200 this._token = [];
201 return rawResult;
202 }
203 _initState() {
204 this._results = {
205 command: undefined,
206 headers: [],
207 binaryBody: undefined,
208 };
209 this._token = [];
210 this._headerKey = undefined;
211 this._onByte = this._collectFrame;
212 }
213}
214//# sourceMappingURL=parser.js.map
\No newline at end of file