/** * TODOC * TODO: document so we can improve this * @param {*} url * @returns */ function parseUrl(url) { // XXX: Hack. let match = url.match(new RegExp("(\\w+)://([^/:]+)(?::(\\d+))?(.*)")); return { protocol: match[1], host: match[2], path: match[4], port: match[3] ? parseInt(match[3]) : match[1] == "http" ? 80 : 443, }; } /** * TODOC * @param {*} data * @returns */ function parseResponse(data) { let firstLine; let headers = {}; while (true) { let endLine = data.indexOf('\r\n'); let line = data.substring(0, endLine); data = data.substring(endLine + 2); if (!line.length) { break; } else if (!firstLine) { firstLine = line; } else { let colon = line.indexOf(":"); headers[line.substring(colon)] = line.substring(colon + 1); } } return {body: data}; } /** * TODOC * @param {*} url * @param {*} options * @param {*} allowed_hosts * @returns */ export function fetch(url, options, allowed_hosts) { let parsed = parseUrl(url); return new Promise(function(resolve, reject) { if ((allowed_hosts ?? []).indexOf(parsed.host) == -1) { throw new Error(`fetch() request to host ${parsed.host} is not allowed.`); } let socket = new Socket(); let buffer = new Uint8Array(0); return socket.connect(parsed.host, parsed.port).then(function() { socket.read(function(data) { if (data && data.length) { let newBuffer = new Uint8Array(buffer.length + data.length); newBuffer.set(buffer, 0); newBuffer.set(data, buffer.length); buffer = newBuffer; } else { let result = parseHttpResponse(buffer); if (!result) { reject(new Exception('Parse failed.')); } if (typeof result == 'number') { if (result == -2) { reject('Incomplete request.'); } else { reject('Bad request.'); } } else if (typeof result == 'object') { resolve({ body: buffer.slice(result.bytes_parsed), status: result.status, message: result.message, headers: result.headers, }); } else { reject(new Exception('Unexpected parse result.')); } resolve(parseResponse(utf8Decode(buffer))); } }); if (parsed.port == 443) { return socket.startTls(); } }).then(function() { let body = typeof options?.body == 'string' ? utf8Encode(options.body) : (options.body || new Uint8Array(0)); let headers = utf8Encode(`${options?.method ?? 'GET'} ${parsed.path} HTTP/1.0\r\nHost: ${parsed.host}\r\nConnection: close\r\nContent-Length: ${body.length}\r\n\r\n`); let fullRequest = new Uint8Array(headers.length + body.length); fullRequest.set(headers, 0); fullRequest.set(body, headers.length); socket.write(fullRequest); }).catch(function(error) { reject(error); }); }); }