Move some things to C that probably should have never been in JS, especially sha1. Minor refactors, cleanup, and deletes along the way.

git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4154 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
Cory McWilliams 2023-01-28 21:59:36 +00:00
parent 48cd08e095
commit 7091b6e6a5
8 changed files with 109 additions and 289 deletions

View File

@ -1,5 +1,4 @@
import * as core from './core.js';
import * as http from './http.js';
import * as form from './form.js';
var gTokens = {};

View File

@ -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);
});
});
}

View File

@ -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) {

View File

@ -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<N; i++) {
M[i] = new Array(16);
for (var j=0; j<16; j++) { // encode 4 chars per integer, big-endian encoding
M[i][j] = (msg.charCodeAt(i*64+j*4)<<24) | (msg.charCodeAt(i*64+j*4+1)<<16) |
(msg.charCodeAt(i*64+j*4+2)<<8) | (msg.charCodeAt(i*64+j*4+3));
} // note running off the end of msg is ok 'cos bitwise ops on NaN return 0
}
// add length (in bits) into final pair of 32-bit integers (big-endian) [§5.1.1]
// note: most significant word would be (len-1)*8 >>> 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<N; i++) {
// 1 - prepare message schedule 'W'
for (var t=0; t<16; t++) W[t] = M[i][t];
for (var t=16; t<80; t++) W[t] = Sha1.ROTL(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1);
// 2 - initialise five working variables a, b, c, d, e with previous hash value
a = H0; b = H1; c = H2; d = H3; e = H4;
// 3 - main loop
for (var t=0; t<80; t++) {
var s = Math.floor(t/20); // seq for blocks of 'f' functions and 'K' constants
var T = (Sha1.ROTL(a,5) + Sha1.f(s,b,c,d) + e + K[s] + W[t]) & 0xffffffff;
e = d;
d = c;
c = Sha1.ROTL(b, 30);
b = a;
a = T;
}
// 4 - compute the new intermediate hash value (note 'addition modulo 2^32')
H0 = (H0+a) & 0xffffffff;
H1 = (H1+b) & 0xffffffff;
H2 = (H2+c) & 0xffffffff;
H3 = (H3+d) & 0xffffffff;
H4 = (H4+e) & 0xffffffff;
}
return Sha1.toHexStr(H0) + Sha1.toHexStr(H1) + Sha1.toHexStr(H2) +
Sha1.toHexStr(H3) + Sha1.toHexStr(H4);
};
/**
* Function 'f' [§4.1.1].
* @private
*/
Sha1.f = function(s, x, y, z) {
switch (s) {
case 0: return (x & y) ^ (~x & z); // Ch()
case 1: return x ^ y ^ z; // Parity()
case 2: return (x & y) ^ (x & z) ^ (y & z); // Maj()
case 3: return x ^ y ^ z; // Parity()
}
};
/**
* Rotates left (circular left shift) value x by n positions [§3.2.5].
* @private
*/
Sha1.ROTL = function(x, n) {
return (x<<n) | (x>>>(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;

View File

@ -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
{

View File

@ -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;

View File

@ -5,6 +5,8 @@
#include "trace.h"
#include <base64c.h>
#include <openssl/crypto.h>
#include <openssl/sha.h>
#include <picohttpparser.h>
#include <quickjs-libc.h>
#include <uv.h>
@ -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);
}

View File

@ -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);