Added browser to device websocket relay, #4172
Ylian Saint-Hilaire committed
Jun 28, 2022 at 14:20 UTC
b41fdec922b8b6bb9fc6d2e7fd3c0f24b7f03e1b
1 file changed
+44
-9
apprelays.js
+44
-9
@@ -229,7 +229,8 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
229
230
// Process a websocket request
231
obj.processWebSocket = function (req, ws) {
232
- console.log('processWebSocket', req.url);
232
+ if (obj.relayActive == false) { console.log("ERROR: Attempt to use an unconnected tunnel"); return false; }
233
+ parent.lastOperation = obj.lastOperation = Date.now();
234
235
// Mark this tunnel as being a web socket tunnel
236
obj.isWebSocket = true;
@@ -239,10 +240,9 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
240
obj.ws._socket.pause();
241
242
// Remove the trailing '/.websocket' if needed
242
- if (req.url.endsWith('/.websocket')) { req.url = req.url.substring(0, req.url.length - 11); }
243
-
244
- if (obj.relayActive == false) { console.log("ERROR: Attempt to use an unconnected tunnel"); return false; }
245
- parent.lastOperation = obj.lastOperation = Date.now();
243
+ var baseurl = req.url, i = req.url.indexOf('?');
244
+ if (i > 0) { baseurl = req.url.substring(0, i); }
245
+ if (baseurl.endsWith('/.websocket')) { req.url = baseurl.substring(0, baseurl.length - 11) + ((i < 1) ? '' : req.url.substring(i)); }
246
247
// Construct the HTTP request and send it out
248
var request = req.method + ' ' + req.url + ' HTTP/' + req.httpVersion + '\r\n';
@@ -251,8 +251,46 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
251
for (var i in req.headers) { if (blockedHeaders.indexOf(i) == -1) { request += i + ': ' + req.headers[i] + '\r\n'; } }
252
if (parent.webCookie != null) { request += 'cookie: ' + parent.webCookie + '\r\n' } // If we have a sessin cookie, use it.
253
request += '\r\n';
254
-
254
send(Buffer.from(request));
255
+
256
+ // Hook up the websocket events
257
+ obj.ws.on('message', function (data) {
258
+ // Setup opcode and payload
259
+ var op = 2, payload = data;
260
+ if (typeof data == 'string') { op = 1; payload = Buffer.from(data, 'binary'); } // Text frame
261
+
262
+ // Select a random mask
263
+ const mask = parent.parent.crypto.randomBytes(4)
264
+
265
+ // Setup header and mask
266
+ var header = null;
267
+ if (data.length < 126) {
268
+ header = Buffer.alloc(6); // Header (2) + Mask (4)
269
+ header[0] = 0x80 + op; // FIN + OP
270
+ header[1] = 0x80 + data.length; // Mask + Length
271
+ mask.copy(header, 2, 0, 4); // Copy the mask
272
+ } else if (data.length <= 0xFFFF) {
273
+ header = Buffer.alloc(8); // Header (2) + Length (2) + Mask (4)
274
+ header[0] = 0x80 + op; // FIN + OP
275
+ header[1] = 0x80 + 126; // Mask + 126
276
+ header.writeInt16BE(data.length, 2); // Payload size
277
+ mask.copy(header, 4, 0, 4); // Copy the mask
278
+ } else {
279
+ header = Buffer.alloc(14); // Header (2) + Length (8) + Mask (4)
280
+ header[0] = 0x80 + op; // FIN + OP
281
+ header[1] = 0x80 + 127; // Mask + 127
282
+ header.writeInt32BE(data.length, 6); // Payload size
283
+ mask.copy(header, 10, 0, 4); // Copy the mask
284
+ }
285
+
286
+ // Mask the payload
287
+ for (var i = 0; i < payload.length; i++) { payload[i] = (payload[i] ^ mask[i % 4]); }
288
+
289
+ // Send the frame
290
+ send(Buffer.concat([header, payload]));
291
+ });
292
+ obj.ws.on('close', function () { obj.close(); });
293
+ obj.ws.on('error', function (err) { obj.close(); });
294
}
295
296
// Disconnect
@@ -407,13 +445,10 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
445
if (obj.socketXHeader['content-length'] != null) { obj.socketParseState = 1; }
446
if ((obj.socketXHeader['transfer-encoding'] != null) && (obj.socketXHeader['transfer-encoding'].toLowerCase() == 'chunked')) { obj.socketParseState = 1; }
447
if (obj.isWebSocket) {
410
- console.log('websocket', obj.socketXHeader);
448
if ((obj.socketXHeader['connection'] != null) && (obj.socketXHeader['connection'].toLowerCase() == 'upgrade')) {
412
- console.log('websocket pass-thru');
449
obj.socketParseState = 2; // Switch to decoding websocket frames
450
obj.ws._socket.resume(); // Resume the browser's websocket
451
} else {
416
- console.log('websocket failed');
452
obj.close(); // Failed to upgrade to websocket
453
}
454
}