1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 const { executeSoon
} = require("resource://devtools/shared/DevToolsUtils.js");
10 } = require("resource://devtools/shared/transport/stream-utils.js");
11 const CryptoHash
= Components
.Constructor(
12 "@mozilla.org/security/hash;1",
16 const threadManager
= Cc
["@mozilla.org/thread-manager;1"].getService();
18 // Limit the header size to put an upper bound on allocated memory
19 const HEADER_MAX_LEN
= 8000;
22 * Read a line from async input stream and return promise that resolves to the line once
23 * it has been read. If the line is longer than HEADER_MAX_LEN, will throw error.
25 function readLine(input
) {
26 return new Promise((resolve
, reject
) => {
32 const amountToRead
= HEADER_MAX_LEN
- line
.length
;
33 line
+= delimitedRead(input
, "\n", amountToRead
);
35 if (line
.endsWith("\n")) {
36 resolve(line
.trimRight());
40 if (line
.length
>= HEADER_MAX_LEN
) {
42 `Failed to read HTTP header longer than ${HEADER_MAX_LEN} bytes`
53 threadManager
.currentThread
62 * Write a string of bytes to async output stream and return promise that resolves once
63 * all data has been written. Doesn't do any utf-16/utf-8 conversion - the string is
64 * treated as an array of bytes.
66 function writeString(output
, data
) {
67 return new Promise((resolve
, reject
) => {
69 if (data
.length
=== 0) {
77 const written
= output
.write(data
, data
.length
);
78 data
= data
.slice(written
);
86 threadManager
.currentThread
95 * Read HTTP request from async input stream.
96 * @return Request line (string) and Map of header names and values.
98 const readHttpRequest
= async
function (input
) {
100 const headers
= new Map();
103 const line
= await
readLine(input
);
111 const colon
= line
.indexOf(":");
113 throw new Error(`Malformed HTTP header: ${line}`);
116 const name
= line
.slice(0, colon
).toLowerCase();
117 const value
= line
.slice(colon
+ 1).trim();
118 headers
.set(name
, value
);
122 return { requestLine
, headers
};
126 * Write HTTP response (array of strings) to async output stream.
128 function writeHttpResponse(output
, response
) {
129 const responseString
= response
.join("\r\n") + "\r\n\r\n";
130 return writeString(output
, responseString
);
134 * Process the WebSocket handshake headers and return the key to be sent in
135 * Sec-WebSocket-Accept response header.
137 function processRequest({ requestLine
, headers
}) {
138 const [method
, path
] = requestLine
.split(" ");
139 if (method
!== "GET") {
140 throw new Error("The handshake request must use GET method");
144 throw new Error("The handshake request has unknown path");
147 const upgrade
= headers
.get("upgrade");
148 if (!upgrade
|| upgrade
!== "websocket") {
149 throw new Error("The handshake request has incorrect Upgrade header");
152 const connection
= headers
.get("connection");
160 throw new Error("The handshake request has incorrect Connection header");
163 const version
= headers
.get("sec-websocket-version");
164 if (!version
|| version
!== "13") {
166 "The handshake request must have Sec-WebSocket-Version: 13"
170 // Compute the accept key
171 const key
= headers
.get("sec-websocket-key");
174 "The handshake request must have a Sec-WebSocket-Key header"
178 return { acceptKey
: computeKey(key
) };
181 function computeKey(key
) {
182 const str
= key
+ "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
184 const data
= Array
.from(str
, ch
=> ch
.charCodeAt(0));
185 const hash
= new CryptoHash("sha1");
186 hash
.update(data
, data
.length
);
187 return hash
.finish(true);
191 * Perform the server part of a WebSocket opening handshake on an incoming connection.
193 const serverHandshake
= async
function (input
, output
) {
195 const request
= await
readHttpRequest(input
);
198 // Check and extract info from the request
199 const { acceptKey
} = processRequest(request
);
201 // Send response headers
202 await
writeHttpResponse(output
, [
203 "HTTP/1.1 101 Switching Protocols",
204 "Upgrade: websocket",
205 "Connection: Upgrade",
206 `Sec-WebSocket-Accept: ${acceptKey}`,
209 // Send error response in case of error
210 await
writeHttpResponse(output
, ["HTTP/1.1 400 Bad Request"]);
216 * Accept an incoming WebSocket server connection.
217 * Takes an established nsISocketTransport in the parameters.
218 * Performs the WebSocket handshake and waits for the WebSocket to open.
219 * Returns Promise with a WebSocket ready to send and receive messages.
221 const accept
= async
function (transport
, input
, output
) {
222 await
serverHandshake(input
, output
);
224 const transportProvider
= {
225 setListener(upgradeListener
) {
226 // The onTransportAvailable callback shouldn't be called synchronously.
228 upgradeListener
.onTransportAvailable(transport
, input
, output
);
233 return new Promise((resolve
, reject
) => {
234 const socket
= WebSocket
.createServerWebSocket(
240 socket
.addEventListener("close", () => {
245 socket
.onopen
= () => resolve(socket
);
246 socket
.onerror
= err
=> reject(err
);
250 exports
.accept
= accept
;