diff --git a/core/auth.js b/core/auth.js index c725e6f3..4a57f5e6 100644 --- a/core/auth.js +++ b/core/auth.js @@ -1,5 +1,4 @@ import * as core from './core.js'; -import * as http from './http.js'; import * as form from './form.js'; var gTokens = {}; diff --git a/core/http.js b/core/http.js deleted file mode 100644 index 9cf5d1b8..00000000 --- a/core/http.js +++ /dev/null @@ -1,59 +0,0 @@ -"use strict"; - -function parseUrl(url) { - // XXX: Hack. - var match = url.match(new RegExp("(\\w+)://([^/]+)?(.*)")); - return { - protocol: match[1], - host: match[2], - path: match[3], - port: match[1] == "http" ? 80 : 443, - }; -} - -function parseResponse(data) { - var firstLine; - var headers = {}; - - while (true) { - var endLine = data.indexOf("\r\n"); - var line = data.substring(0, endLine); - if (!firstLine) { - firstLine = line; - } else if (!line.length) { - break; - } else { - var colon = line.indexOf(":"); - headers[line.substring(colon)] = line.substring(colon + 1); - } - data = data.substring(endLine + 2); - } - return {body: data}; -} - -export function get(url) { - var parsed = parseUrl(url); - return new Promise(function(resolve, reject) { - var socket = new Socket(); - var buffer = ""; - - return socket.connect(parsed.host, parsed.port).then(function() { - socket.read(function(data) { - if (data) { - buffer += data; - } else { - resolve(parseResponse(buffer)); - } - }); - - if (parsed.port == 443) { - return socket.startTls(); - } - }).then(function() { - socket.write(`GET ${parsed.path} HTTP/1.0\r\nHost: ${parsed.host}\r\nConnection: close\r\n\r\n`); - socket.shutdown(); - }).catch(function(error) { - reject(error); - }); - }); -} diff --git a/core/httpd.js b/core/httpd.js index ff54d608..5a68197f 100644 --- a/core/httpd.js +++ b/core/httpd.js @@ -1,4 +1,3 @@ -import * as sha1 from './sha1.js'; import * as core from './core.js'; "use strict"; @@ -196,7 +195,7 @@ function handleRequest(request, response) { function handleWebSocketRequest(request, response, client) { let buffer = new Uint8Array(0); - let frame = new Uint8Array(0); + let frame; let frameOpCode = 0x0; let handler = findSocketHandler(request); @@ -285,19 +284,24 @@ function handleWebSocketRequest(request, response, client) { } let havePayload = buffer.length >= payloadLength + 2 + 4; if (havePayload) { - let mask = buffer.slice(maskStart, maskStart + 4); + let mask = + buffer[maskStart + 0] | + buffer[maskStart + 1] << 8 | + buffer[maskStart + 2] << 16 | + buffer[maskStart + 3] << 24; let dataStart = maskStart + 4; - let decoded = new Array(payloadLength); let payload = buffer.slice(dataStart, dataStart + payloadLength); + let decoded = maskBytes(payload, mask); buffer = buffer.slice(dataStart + payloadLength); - for (let i = 0; i < payloadLength; i++) { - decoded[i] = payload[i] ^ mask[i % 4]; - } - let newBuffer = new Uint8Array(frame.length + decoded.length); - newBuffer.set(frame, 0); - newBuffer.set(decoded, frame.length); - frame = newBuffer; + if (frame) { + let newBuffer = new Uint8Array(frame.length + decoded.length); + newBuffer.set(frame, 0); + newBuffer.set(decoded, frame.length); + frame = newBuffer; + } else { + frame = decoded; + } if (opCode) { frameOpCode = opCode; @@ -310,7 +314,7 @@ function handleWebSocketRequest(request, response, client) { opCode: frameOpCode, }); } - frame = new Uint8Array(0); + frame = undefined; } } else { break; @@ -340,23 +344,7 @@ function handleWebSocketRequest(request, response, client) { function webSocketAcceptResponse(key) { let kMagic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; let kAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - let hex = sha1.hash(key + kMagic) - let binary = ""; - for (let i = 0; i < hex.length; i += 6) { - let characters = hex.substring(i, i + 6); - if (characters.length < 6) { - characters += "0".repeat(6 - characters.length); - } - let value = parseInt(characters, 16); - for (let bit = 0; bit < 8 * 3; bit += 6) { - if (i * 8 / 2 + bit >= 8 * hex.length / 2) { - binary += kAlphabet.charAt(64); - } else { - binary += kAlphabet.charAt((value >> (18 - bit)) & 63); - } - } - } - return binary; + return base64Encode(sha1Digest(key + kMagic)); } function badRequest(client, reason) { diff --git a/core/sha1.js b/core/sha1.js deleted file mode 100644 index 3bf79c95..00000000 --- a/core/sha1.js +++ /dev/null @@ -1,160 +0,0 @@ -/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -/* SHA-1 implementation in JavaScript (c) Chris Veness 2002-2014 / MIT Licence */ -/* */ -/* - see http://csrc.nist.gov/groups/ST/toolkit/secure_hashing.html */ -/* http://csrc.nist.gov/groups/ST/toolkit/examples.html */ -/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ - -/* jshint node:true *//* global define, escape, unescape */ -'use strict'; - - -/** - * SHA-1 hash function reference implementation. - * - * @namespace - */ -var Sha1 = {}; - - -/** - * Generates SHA-1 hash of string. - * - * @param {string} msg - (Unicode) string to be hashed. - * @returns {string} Hash of msg as hex character string. - */ -Sha1.hash = function(msg) { - // convert string to UTF-8, as SHA only deals with byte-streams - msg = msg.utf8Encode(); - - // constants [§4.2.1] - var K = [ 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6 ]; - - // PREPROCESSING - - msg += String.fromCharCode(0x80); // add trailing '1' bit (+ 0's padding) to string [§5.1.1] - - // convert string msg into 512-bit/16-integer blocks arrays of ints [§5.2.1] - var l = msg.length/4 + 2; // length (in 32-bit integers) of msg + ‘1’ + appended length - var N = Math.ceil(l/16); // number of 16-integer-blocks required to hold 'l' ints - var M = new Array(N); - - for (var i=0; i>> 32, but since JS converts - // bitwise-op args to 32 bits, we need to simulate this by arithmetic operators - M[N-1][14] = ((msg.length-1)*8) / Math.pow(2, 32); M[N-1][14] = Math.floor(M[N-1][14]); - M[N-1][15] = ((msg.length-1)*8) & 0xffffffff; - - // set initial hash value [§5.3.1] - var H0 = 0x67452301; - var H1 = 0xefcdab89; - var H2 = 0x98badcfe; - var H3 = 0x10325476; - var H4 = 0xc3d2e1f0; - - // HASH COMPUTATION [§6.1.2] - - var W = new Array(80); var a, b, c, d, e; - for (var i=0; i>>(32-n)); -}; - - -/** - * Hexadecimal representation of a number. - * @private - */ -Sha1.toHexStr = function(n) { - // note can't use toString(16) as it is implementation-dependant, - // and in IE returns signed numbers when used on full words - var s="", v; - for (var i=7; i>=0; i--) { v = (n>>>(i*4)) & 0xf; s += v.toString(16); } - return s; -}; - - -/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ - - -/** Extend String object with method to encode multi-byte string to utf8 - * - monsur.hossa.in/2012/07/20/utf-8-in-javascript.html */ -if (typeof String.prototype.utf8Encode == 'undefined') { - String.prototype.utf8Encode = function() { - return unescape( encodeURIComponent( this ) ); - }; -} - -/** Extend String object with method to decode utf8 string to multi-byte */ -if (typeof String.prototype.utf8Decode == 'undefined') { - String.prototype.utf8Decode = function() { - try { - return decodeURIComponent( escape( this ) ); - } catch (e) { - return this; // invalid UTF-8? return as-is - } - }; -} - - -/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -if (typeof module != 'undefined' && module.exports) module.exports = Sha1; // CommonJs export -if (typeof define == 'function' && define.amd) define([], function() { return Sha1; }); // AMD - -export let hash = Sha1.hash; diff --git a/src/file.js.c b/src/file.js.c index 03ceeee8..b7219522 100644 --- a/src/file.js.c +++ b/src/file.js.c @@ -72,15 +72,9 @@ static void _file_read_read_callback(uv_fs_t* req) promiseid_t promise = (promiseid_t)(intptr_t)req->data; if (req->result >= 0) { - JSValue arrayBuffer = JS_NewArrayBufferCopy(context, (const uint8_t*)fsreq->buffer, req->result); - JSValue global = JS_GetGlobalObject(context); - JSValue constructor = JS_GetPropertyStr(context, global, "Uint8Array"); - JSValue typedArray = JS_CallConstructor(context, constructor, 1, &arrayBuffer); - JS_FreeValue(context, constructor); - JS_FreeValue(context, global); - JS_FreeValue(context, arrayBuffer); - tf_task_resolve_promise(task, promise, typedArray); - JS_FreeValue(context, typedArray); + JSValue array = tf_util_new_uint8_array(context, (const uint8_t*)fsreq->buffer, req->result); + tf_task_resolve_promise(task, promise, array); + JS_FreeValue(context, array); } else { diff --git a/src/socket.js.c b/src/socket.js.c index 89c5ef16..73ca1529 100644 --- a/src/socket.js.c +++ b/src/socket.js.c @@ -823,25 +823,13 @@ void _socket_onRead(uv_stream_t* stream, ssize_t readSize, const uv_buf_t* buffe JS_FreeValue(context, ref); } -static JSValue _newUint8Array(JSContext* context, const void* data, size_t length) -{ - JSValue arrayBuffer = JS_NewArrayBufferCopy(context, (const uint8_t*)data, length); - JSValue global = JS_GetGlobalObject(context); - JSValue constructor = JS_GetPropertyStr(context, global, "Uint8Array"); - JSValue typedArray = JS_CallConstructor(context, constructor, 1, &arrayBuffer); - JS_FreeValue(context, constructor); - JS_FreeValue(context, global); - JS_FreeValue(context, arrayBuffer); - return typedArray; -} - void _socket_notifyDataRead(socket_t* socket, const char* data, size_t length) { if (data && length > 0) { JSContext* context = tf_task_get_context(socket->_task); JSValue ref = JS_DupValue(context, socket->_object); - JSValue typedArray = _newUint8Array(context, data, length); + JSValue typedArray = tf_util_new_uint8_array(context, (const uint8_t*)data, length); JSValue args[] = { typedArray }; if (!JS_IsUndefined(socket->_onRead)) { @@ -1072,7 +1060,7 @@ JSValue _socket_getPeerCertificate(JSContext* context, JSValueConst this_val, in int result = tf_tls_session_get_peer_certificate(socket->_tls, buffer, sizeof(buffer)); if (result > 0) { - return _newUint8Array(tf_task_get_context(socket->_task), buffer, sizeof(buffer)); + return tf_util_new_uint8_array(tf_task_get_context(socket->_task), (const uint8_t*)buffer, sizeof(buffer)); } } return JS_UNDEFINED; diff --git a/src/util.js.c b/src/util.js.c index 0630835e..c9ba91b7 100644 --- a/src/util.js.c +++ b/src/util.js.c @@ -5,6 +5,8 @@ #include "trace.h" #include +#include +#include #include #include #include @@ -15,15 +17,9 @@ static JSValue _util_utf8_encode(JSContext* context, JSValueConst this_val, int { size_t length = 0; const char* value = JS_ToCStringLen(context, &length, argv[0]); - JSValue arrayBuffer = JS_NewArrayBufferCopy(context, (const uint8_t*)value, length); - JSValue global = JS_GetGlobalObject(context); - JSValue constructor = JS_GetPropertyStr(context, global, "Uint8Array"); - JS_FreeValue(context, global); - JSValue typedArray = JS_CallConstructor(context, constructor, 1, &arrayBuffer); - JS_FreeValue(context, constructor); - JS_FreeValue(context, arrayBuffer); + JSValue typed_array = tf_util_new_uint8_array(context, (const uint8_t*)value, length); JS_FreeCString(context, value); - return typedArray; + return typed_array; } static JSValue _util_utf8_decode(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) @@ -70,17 +66,29 @@ static JSValue _util_base64_encode(JSContext* context, JSValueConst this_val, in { JSValue result = JS_UNDEFINED; size_t length = 0; - const char* value = JS_ToCStringLen(context, &length, argv[0]); - char* encoded = tf_malloc(length * 4); - - int r = base64c_encode((const uint8_t*)value, length, (uint8_t*)encoded, length * 4); - if (r >= 0) + uint8_t* array = tf_util_try_get_array_buffer(context, &length, argv[0]); + if (array) { - result = JS_NewStringLen(context, encoded, r); + char* encoded = tf_malloc(length * 4); + int r = base64c_encode(array, length, (uint8_t*)encoded, length * 4); + if (r >= 0) + { + result = JS_NewStringLen(context, encoded, r); + } + tf_free(encoded); + } + else + { + const char* value = JS_ToCStringLen(context, &length, argv[0]); + char* encoded = tf_malloc(length * 4); + int r = base64c_encode((const uint8_t*)value, length, (uint8_t*)encoded, length * 4); + if (r >= 0) + { + result = JS_NewStringLen(context, encoded, r); + } + tf_free(encoded); + JS_FreeCString(context, value); } - - tf_free(encoded); - JS_FreeCString(context, value); return result; } @@ -291,6 +299,65 @@ static JSValue _util_parseHttp(JSContext* context, JSValueConst this_val, int ar } +static JSValue _util_sha1_digest(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) +{ + size_t length = 0; + const char* value = JS_ToCStringLen(context, &length, argv[0]); + unsigned char digest[SHA_DIGEST_LENGTH] = { 0 }; + SHA1((const unsigned char*)value, length, digest); + return JS_NewArrayBufferCopy(context, digest, sizeof(digest)); +} + +JSValue tf_util_new_uint8_array(JSContext* context, const uint8_t* data, size_t size) +{ + JSValue array_buffer = JS_NewArrayBufferCopy(context, data, size); + JSValue global = JS_GetGlobalObject(context); + JSValue constructor = JS_GetPropertyStr(context, global, "Uint8Array"); + JSValue result = JS_CallConstructor(context, constructor, 1, &array_buffer); + JS_FreeValue(context, constructor); + JS_FreeValue(context, global); + JS_FreeValue(context, array_buffer); + return result; +} + +static JSValue _util_mask_bytes(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) +{ + JSValue result = JS_UNDEFINED; + uint32_t mask = 0; + JS_ToUint32(context, &mask, argv[1]); + uint64_t double_mask = ((uint64_t)mask << 32) | mask; + + size_t offset = 0; + size_t length = 0; + size_t element_size = 0; + JSValue buffer = tf_util_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size); + if (!JS_IsException(buffer)) + { + size_t size = 0; + const uint8_t* array = tf_util_try_get_array_buffer(context, &size, buffer); + if (array) + { + uint8_t* copy = tf_malloc(size); + size_t i = 0; + for (; i + sizeof(double_mask) < size; i += sizeof(double_mask)) + { + ((uint64_t*)copy)[i / sizeof(double_mask)] = ((const uint64_t*)array)[i / sizeof(double_mask)] ^ double_mask; + } + for (; i + sizeof(mask) < size; i += sizeof(mask)) + { + ((uint32_t*)copy)[i / sizeof(mask)] = ((const uint32_t*)array)[i / sizeof(mask)] ^ mask; + } + for (; i < size; i++) + { + copy[i] = array[i] ^ ((mask >> (8 * (i % 4))) & 0xff); + } + result = tf_util_new_uint8_array(context, copy, size); + tf_free(copy); + } + } + return result; +} + void tf_util_register(JSContext* context) { JSValue global = JS_GetGlobalObject(context); @@ -301,6 +368,8 @@ void tf_util_register(JSContext* context) JS_SetPropertyStr(context, global, "print", JS_NewCFunction(context, _util_print, "print", 1)); JS_SetPropertyStr(context, global, "setTimeout", JS_NewCFunction(context, _util_setTimeout, "setTimeout", 2)); JS_SetPropertyStr(context, global, "parseHttp", JS_NewCFunction(context, _util_parseHttp, "parseHttp", 2)); + JS_SetPropertyStr(context, global, "sha1Digest", JS_NewCFunction(context, _util_sha1_digest, "sha1Digest", 1)); + JS_SetPropertyStr(context, global, "maskBytes", JS_NewCFunction(context, _util_mask_bytes, "maskBytes", 2)); JS_FreeValue(context, global); } diff --git a/src/util.js.h b/src/util.js.h index c59f8043..b0e74ed4 100644 --- a/src/util.js.h +++ b/src/util.js.h @@ -11,3 +11,4 @@ JSValue tf_util_try_get_typed_array_buffer(JSContext* context, JSValueConst obj, bool tf_util_report_error(JSContext* context, JSValue value); int tf_util_get_length(JSContext* context, JSValue value); int tf_util_insert_index(const void* key, const void* base, size_t count, size_t size, int (*compare)(const void*, const void*)); +JSValue tf_util_new_uint8_array(JSContext* context, const uint8_t* data, size_t size);