frame.js (6219B)
1 /* 2 * Copyright (c) 2018 Deepak Kumar 3 * 4 * MIT License 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 * 8 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 * 12 * https://github.com/stomp-js/stompjs 13 * https://github.com/stomp-js/stompjs/blob/develop/src/frame.ts 14 */ 15 16 "use strict"; 17 18 const { 19 BYTE, 20 } = require("resource://devtools/client/netmonitor/src/components/messages/parsers/stomp/byte.js"); 21 22 /** 23 * Frame class represents a STOMP frame. 24 * 25 * @internal 26 */ 27 class FrameImpl { 28 /** 29 * Frame constructor. `command`, `headers` and `body` are available as properties. 30 * 31 * @internal 32 */ 33 constructor(params) { 34 const { 35 command, 36 headers, 37 body, 38 binaryBody, 39 escapeHeaderValues, 40 skipContentLengthHeader, 41 } = params; 42 this.command = command; 43 this.headers = Object.assign({}, headers || {}); 44 if (binaryBody) { 45 this._binaryBody = binaryBody; 46 this.isBinaryBody = true; 47 } else { 48 this._body = body || ""; 49 this.isBinaryBody = false; 50 } 51 this.escapeHeaderValues = escapeHeaderValues || false; 52 this.skipContentLengthHeader = skipContentLengthHeader || false; 53 } 54 /** 55 * body of the frame 56 */ 57 get body() { 58 if (!this._body && this.isBinaryBody) { 59 this._body = new TextDecoder().decode(this._binaryBody); 60 } 61 return this._body; 62 } 63 /** 64 * body as Uint8Array 65 */ 66 get binaryBody() { 67 if (!this._binaryBody && !this.isBinaryBody) { 68 this._binaryBody = new TextEncoder().encode(this._body); 69 } 70 return this._binaryBody; 71 } 72 /** 73 * deserialize a STOMP Frame from raw data. 74 * 75 * @internal 76 */ 77 static fromRawFrame(rawFrame, escapeHeaderValues) { 78 const headers = {}; 79 const trim = str => str.replace(/^\s+|\s+$/g, ""); 80 // In case of repeated headers, as per standards, first value need to be used 81 for (const header of rawFrame.headers.reverse()) { 82 const key = trim(header[0]); 83 let value = trim(header[1]); 84 if ( 85 escapeHeaderValues && 86 rawFrame.command !== "CONNECT" && 87 rawFrame.command !== "CONNECTED" 88 ) { 89 value = FrameImpl.hdrValueUnEscape(value); 90 } 91 headers[key] = value; 92 } 93 return new FrameImpl({ 94 command: rawFrame.command, 95 headers, 96 binaryBody: rawFrame.binaryBody, 97 escapeHeaderValues, 98 }); 99 } 100 /** 101 * @internal 102 */ 103 toString() { 104 return this.serializeCmdAndHeaders(); 105 } 106 /** 107 * serialize this Frame in a format suitable to be passed to WebSocket. 108 * If the body is string the output will be string. 109 * If the body is binary (i.e. of type Unit8Array) it will be serialized to ArrayBuffer. 110 * 111 * @internal 112 */ 113 serialize() { 114 const cmdAndHeaders = this.serializeCmdAndHeaders(); 115 if (this.isBinaryBody) { 116 return FrameImpl.toUnit8Array(cmdAndHeaders, this._binaryBody).buffer; 117 } 118 return cmdAndHeaders + this._body + BYTE.NULL; 119 } 120 serializeCmdAndHeaders() { 121 const lines = [this.command]; 122 if (this.skipContentLengthHeader) { 123 delete this.headers["content-length"]; 124 } 125 for (const name of Object.keys(this.headers || {})) { 126 const value = this.headers[name]; 127 if ( 128 this.escapeHeaderValues && 129 this.command !== "CONNECT" && 130 this.command !== "CONNECTED" 131 ) { 132 lines.push(`${name}:${FrameImpl.hdrValueEscape(`${value}`)}`); 133 } else { 134 lines.push(`${name}:${value}`); 135 } 136 } 137 if ( 138 this.isBinaryBody || 139 (!this.isBodyEmpty() && !this.skipContentLengthHeader) 140 ) { 141 lines.push(`content-length:${this.bodyLength()}`); 142 } 143 return lines.join(BYTE.LF) + BYTE.LF + BYTE.LF; 144 } 145 isBodyEmpty() { 146 return this.bodyLength() === 0; 147 } 148 bodyLength() { 149 const binaryBody = this.binaryBody; 150 return binaryBody ? binaryBody.length : 0; 151 } 152 /** 153 * Compute the size of a UTF-8 string by counting its number of bytes 154 * (and not the number of characters composing the string) 155 */ 156 static sizeOfUTF8(s) { 157 return s ? new TextEncoder().encode(s).length : 0; 158 } 159 static toUnit8Array(cmdAndHeaders, binaryBody) { 160 const uint8CmdAndHeaders = new TextEncoder().encode(cmdAndHeaders); 161 const nullTerminator = new Uint8Array([0]); 162 const uint8Frame = new Uint8Array( 163 uint8CmdAndHeaders.length + binaryBody.length + nullTerminator.length 164 ); 165 uint8Frame.set(uint8CmdAndHeaders); 166 uint8Frame.set(binaryBody, uint8CmdAndHeaders.length); 167 uint8Frame.set( 168 nullTerminator, 169 uint8CmdAndHeaders.length + binaryBody.length 170 ); 171 return uint8Frame; 172 } 173 /** 174 * Serialize a STOMP frame as per STOMP standards, suitable to be sent to the STOMP broker. 175 * 176 * @internal 177 */ 178 static marshall(params) { 179 const frame = new FrameImpl(params); 180 return frame.serialize(); 181 } 182 /** 183 * Escape header values 184 */ 185 static hdrValueEscape(str) { 186 return str 187 .replace(/\\/g, "\\\\") 188 .replace(/\r/g, "\\r") 189 .replace(/\n/g, "\\n") 190 .replace(/:/g, "\\c"); 191 } 192 /** 193 * UnEscape header values 194 */ 195 static hdrValueUnEscape(str) { 196 return str 197 .replace(/\\r/g, "\r") 198 .replace(/\\n/g, "\n") 199 .replace(/\\c/g, ":") 200 .replace(/\\\\/g, "\\"); 201 } 202 } 203 204 module.exports = { Frame: FrameImpl };