1 | import { BYTE } from './byte';
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | export class FrameImpl {
|
8 | |
9 |
|
10 |
|
11 |
|
12 |
|
13 | constructor(params) {
|
14 | const { command, headers, body, binaryBody, escapeHeaderValues, skipContentLengthHeader, } = params;
|
15 | this.command = command;
|
16 | this.headers = Object.assign({}, headers || {});
|
17 | if (binaryBody) {
|
18 | this._binaryBody = binaryBody;
|
19 | this.isBinaryBody = true;
|
20 | }
|
21 | else {
|
22 | this._body = body || '';
|
23 | this.isBinaryBody = false;
|
24 | }
|
25 | this.escapeHeaderValues = escapeHeaderValues || false;
|
26 | this.skipContentLengthHeader = skipContentLengthHeader || false;
|
27 | }
|
28 | |
29 |
|
30 |
|
31 | get body() {
|
32 | if (!this._body && this.isBinaryBody) {
|
33 | this._body = new TextDecoder().decode(this._binaryBody);
|
34 | }
|
35 | return this._body;
|
36 | }
|
37 | |
38 |
|
39 |
|
40 | get binaryBody() {
|
41 | if (!this._binaryBody && !this.isBinaryBody) {
|
42 | this._binaryBody = new TextEncoder().encode(this._body);
|
43 | }
|
44 | return this._binaryBody;
|
45 | }
|
46 | |
47 |
|
48 |
|
49 |
|
50 |
|
51 | static fromRawFrame(rawFrame, escapeHeaderValues) {
|
52 | const headers = {};
|
53 | const trim = (str) => str.replace(/^\s+|\s+$/g, '');
|
54 |
|
55 | for (const header of rawFrame.headers.reverse()) {
|
56 | const idx = header.indexOf(':');
|
57 | const key = trim(header[0]);
|
58 | let value = trim(header[1]);
|
59 | if (escapeHeaderValues &&
|
60 | rawFrame.command !== 'CONNECT' &&
|
61 | rawFrame.command !== 'CONNECTED') {
|
62 | value = FrameImpl.hdrValueUnEscape(value);
|
63 | }
|
64 | headers[key] = value;
|
65 | }
|
66 | return new FrameImpl({
|
67 | command: rawFrame.command,
|
68 | headers,
|
69 | binaryBody: rawFrame.binaryBody,
|
70 | escapeHeaderValues,
|
71 | });
|
72 | }
|
73 | |
74 |
|
75 |
|
76 | toString() {
|
77 | return this.serializeCmdAndHeaders();
|
78 | }
|
79 | |
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 | serialize() {
|
87 | const cmdAndHeaders = this.serializeCmdAndHeaders();
|
88 | if (this.isBinaryBody) {
|
89 | return FrameImpl.toUnit8Array(cmdAndHeaders, this._binaryBody).buffer;
|
90 | }
|
91 | else {
|
92 | return cmdAndHeaders + this._body + BYTE.NULL;
|
93 | }
|
94 | }
|
95 | serializeCmdAndHeaders() {
|
96 | const lines = [this.command];
|
97 | if (this.skipContentLengthHeader) {
|
98 | delete this.headers['content-length'];
|
99 | }
|
100 | for (const name of Object.keys(this.headers || {})) {
|
101 | const value = this.headers[name];
|
102 | if (this.escapeHeaderValues &&
|
103 | this.command !== 'CONNECT' &&
|
104 | this.command !== 'CONNECTED') {
|
105 | lines.push(`${name}:${FrameImpl.hdrValueEscape(`${value}`)}`);
|
106 | }
|
107 | else {
|
108 | lines.push(`${name}:${value}`);
|
109 | }
|
110 | }
|
111 | if (this.isBinaryBody ||
|
112 | (!this.isBodyEmpty() && !this.skipContentLengthHeader)) {
|
113 | lines.push(`content-length:${this.bodyLength()}`);
|
114 | }
|
115 | return lines.join(BYTE.LF) + BYTE.LF + BYTE.LF;
|
116 | }
|
117 | isBodyEmpty() {
|
118 | return this.bodyLength() === 0;
|
119 | }
|
120 | bodyLength() {
|
121 | const binaryBody = this.binaryBody;
|
122 | return binaryBody ? binaryBody.length : 0;
|
123 | }
|
124 | |
125 |
|
126 |
|
127 |
|
128 | static sizeOfUTF8(s) {
|
129 | return s ? new TextEncoder().encode(s).length : 0;
|
130 | }
|
131 | static toUnit8Array(cmdAndHeaders, binaryBody) {
|
132 | const uint8CmdAndHeaders = new TextEncoder().encode(cmdAndHeaders);
|
133 | const nullTerminator = new Uint8Array([0]);
|
134 | const uint8Frame = new Uint8Array(uint8CmdAndHeaders.length + binaryBody.length + nullTerminator.length);
|
135 | uint8Frame.set(uint8CmdAndHeaders);
|
136 | uint8Frame.set(binaryBody, uint8CmdAndHeaders.length);
|
137 | uint8Frame.set(nullTerminator, uint8CmdAndHeaders.length + binaryBody.length);
|
138 | return uint8Frame;
|
139 | }
|
140 | |
141 |
|
142 |
|
143 |
|
144 |
|
145 | static marshall(params) {
|
146 | const frame = new FrameImpl(params);
|
147 | return frame.serialize();
|
148 | }
|
149 | |
150 |
|
151 |
|
152 | static hdrValueEscape(str) {
|
153 | return str
|
154 | .replace(/\\/g, '\\\\')
|
155 | .replace(/\r/g, '\\r')
|
156 | .replace(/\n/g, '\\n')
|
157 | .replace(/:/g, '\\c');
|
158 | }
|
159 | |
160 |
|
161 |
|
162 | static hdrValueUnEscape(str) {
|
163 | return str
|
164 | .replace(/\\r/g, '\r')
|
165 | .replace(/\\n/g, '\n')
|
166 | .replace(/\\c/g, ':')
|
167 | .replace(/\\\\/g, '\\');
|
168 | }
|
169 | }
|
170 |
|
\ | No newline at end of file |