1 | import { BYTE } from './byte.js';
|
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 |
|
45 | return this._binaryBody;
|
46 | }
|
47 | |
48 |
|
49 |
|
50 |
|
51 |
|
52 | static fromRawFrame(rawFrame, escapeHeaderValues) {
|
53 | const headers = {};
|
54 | const trim = (str) => str.replace(/^\s+|\s+$/g, '');
|
55 |
|
56 | for (const header of rawFrame.headers.reverse()) {
|
57 | const idx = header.indexOf(':');
|
58 | const key = trim(header[0]);
|
59 | let value = trim(header[1]);
|
60 | if (escapeHeaderValues &&
|
61 | rawFrame.command !== 'CONNECT' &&
|
62 | rawFrame.command !== 'CONNECTED') {
|
63 | value = FrameImpl.hdrValueUnEscape(value);
|
64 | }
|
65 | headers[key] = value;
|
66 | }
|
67 | return new FrameImpl({
|
68 | command: rawFrame.command,
|
69 | headers,
|
70 | binaryBody: rawFrame.binaryBody,
|
71 | escapeHeaderValues,
|
72 | });
|
73 | }
|
74 | |
75 |
|
76 |
|
77 | toString() {
|
78 | return this.serializeCmdAndHeaders();
|
79 | }
|
80 | |
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 | serialize() {
|
88 | const cmdAndHeaders = this.serializeCmdAndHeaders();
|
89 | if (this.isBinaryBody) {
|
90 | return FrameImpl.toUnit8Array(cmdAndHeaders, this._binaryBody).buffer;
|
91 | }
|
92 | else {
|
93 | return cmdAndHeaders + this._body + BYTE.NULL;
|
94 | }
|
95 | }
|
96 | serializeCmdAndHeaders() {
|
97 | const lines = [this.command];
|
98 | if (this.skipContentLengthHeader) {
|
99 | delete this.headers['content-length'];
|
100 | }
|
101 | for (const name of Object.keys(this.headers || {})) {
|
102 | const value = this.headers[name];
|
103 | if (this.escapeHeaderValues &&
|
104 | this.command !== 'CONNECT' &&
|
105 | this.command !== 'CONNECTED') {
|
106 | lines.push(`${name}:${FrameImpl.hdrValueEscape(`${value}`)}`);
|
107 | }
|
108 | else {
|
109 | lines.push(`${name}:${value}`);
|
110 | }
|
111 | }
|
112 | if (this.isBinaryBody ||
|
113 | (!this.isBodyEmpty() && !this.skipContentLengthHeader)) {
|
114 | lines.push(`content-length:${this.bodyLength()}`);
|
115 | }
|
116 | return lines.join(BYTE.LF) + BYTE.LF + BYTE.LF;
|
117 | }
|
118 | isBodyEmpty() {
|
119 | return this.bodyLength() === 0;
|
120 | }
|
121 | bodyLength() {
|
122 | const binaryBody = this.binaryBody;
|
123 | return binaryBody ? binaryBody.length : 0;
|
124 | }
|
125 | |
126 |
|
127 |
|
128 |
|
129 | static sizeOfUTF8(s) {
|
130 | return s ? new TextEncoder().encode(s).length : 0;
|
131 | }
|
132 | static toUnit8Array(cmdAndHeaders, binaryBody) {
|
133 | const uint8CmdAndHeaders = new TextEncoder().encode(cmdAndHeaders);
|
134 | const nullTerminator = new Uint8Array([0]);
|
135 | const uint8Frame = new Uint8Array(uint8CmdAndHeaders.length + binaryBody.length + nullTerminator.length);
|
136 | uint8Frame.set(uint8CmdAndHeaders);
|
137 | uint8Frame.set(binaryBody, uint8CmdAndHeaders.length);
|
138 | uint8Frame.set(nullTerminator, uint8CmdAndHeaders.length + binaryBody.length);
|
139 | return uint8Frame;
|
140 | }
|
141 | |
142 |
|
143 |
|
144 |
|
145 |
|
146 | static marshall(params) {
|
147 | const frame = new FrameImpl(params);
|
148 | return frame.serialize();
|
149 | }
|
150 | |
151 |
|
152 |
|
153 | static hdrValueEscape(str) {
|
154 | return str
|
155 | .replace(/\\/g, '\\\\')
|
156 | .replace(/\r/g, '\\r')
|
157 | .replace(/\n/g, '\\n')
|
158 | .replace(/:/g, '\\c');
|
159 | }
|
160 | |
161 |
|
162 |
|
163 | static hdrValueUnEscape(str) {
|
164 | return str
|
165 | .replace(/\\r/g, '\r')
|
166 | .replace(/\\n/g, '\n')
|
167 | .replace(/\\c/g, ':')
|
168 | .replace(/\\\\/g, '\\');
|
169 | }
|
170 | }
|
171 |
|
\ | No newline at end of file |