Backed out 2 changesets (bug 1943998) for causing wd failures @ phases.py CLOSED...
[gecko.git] / devtools / server / socket / websocket-server.js
blobd49c6fbf1bf83b832795fa674f6b41f223eef812
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/. */
5 "use strict";
7 const { executeSoon } = require("resource://devtools/shared/DevToolsUtils.js");
8 const {
9 delimitedRead,
10 } = require("resource://devtools/shared/transport/stream-utils.js");
11 const CryptoHash = Components.Constructor(
12 "@mozilla.org/security/hash;1",
13 "nsICryptoHash",
14 "initWithString"
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;
21 /**
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) => {
27 let line = "";
28 const wait = () => {
29 input.asyncWait(
30 () => {
31 try {
32 const amountToRead = HEADER_MAX_LEN - line.length;
33 line += delimitedRead(input, "\n", amountToRead);
35 if (line.endsWith("\n")) {
36 resolve(line.trimRight());
37 return;
40 if (line.length >= HEADER_MAX_LEN) {
41 throw new Error(
42 `Failed to read HTTP header longer than ${HEADER_MAX_LEN} bytes`
46 wait();
47 } catch (ex) {
48 reject(ex);
53 threadManager.currentThread
57 wait();
58 });
61 /**
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) => {
68 const wait = () => {
69 if (data.length === 0) {
70 resolve();
71 return;
74 output.asyncWait(
75 () => {
76 try {
77 const written = output.write(data, data.length);
78 data = data.slice(written);
79 wait();
80 } catch (ex) {
81 reject(ex);
86 threadManager.currentThread
90 wait();
91 });
94 /**
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) {
99 let requestLine = "";
100 const headers = new Map();
102 while (true) {
103 const line = await readLine(input);
104 if (!line.length) {
105 break;
108 if (!requestLine) {
109 requestLine = line;
110 } else {
111 const colon = line.indexOf(":");
112 if (colon == -1) {
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");
143 if (path !== "/") {
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");
153 if (
154 !connection ||
155 !connection
156 .split(",")
157 .map(t => t.trim())
158 .includes("Upgrade")
160 throw new Error("The handshake request has incorrect Connection header");
163 const version = headers.get("sec-websocket-version");
164 if (!version || version !== "13") {
165 throw new Error(
166 "The handshake request must have Sec-WebSocket-Version: 13"
170 // Compute the accept key
171 const key = headers.get("sec-websocket-key");
172 if (!key) {
173 throw new Error(
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) {
194 // Read the request
195 const request = await readHttpRequest(input);
197 try {
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}`,
208 } catch (error) {
209 // Send error response in case of error
210 await writeHttpResponse(output, ["HTTP/1.1 400 Bad Request"]);
211 throw error;
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.
227 executeSoon(() => {
228 upgradeListener.onTransportAvailable(transport, input, output);
233 return new Promise((resolve, reject) => {
234 const socket = WebSocket.createServerWebSocket(
235 null,
237 transportProvider,
240 socket.addEventListener("close", () => {
241 input.close();
242 output.close();
245 socket.onopen = () => resolve(socket);
246 socket.onerror = err => reject(err);
250 exports.accept = accept;