From 6ef466f3ed09637a958790158f2b4f48b834ff8c Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Sun, 16 Jul 2023 21:04:48 +0000 Subject: [PATCH] Fixed enough thing sto be able to authenticate and get data from Strava. git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4347 ed5197a5-7fde-0310-b194-c3ffbd925b24 --- core/core.js | 30 +++++++++++++++++++++++------- core/http.js | 31 +++++++++++++++++++------------ 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/core/core.js b/core/core.js index 13fee092..2ddfaeb8 100644 --- a/core/core.js +++ b/core/core.js @@ -1,6 +1,7 @@ import * as app from './app.js'; import * as auth from './auth.js'; import * as form from './form.js'; +import * as http from './http.js'; import * as httpd from './httpd.js'; let gProcessIndex = 0; @@ -66,6 +67,11 @@ const k_global_settings = { default_value: undefined, description: 'If connecting by HTTP and HTTPS is configured, Location header prefix (ie, "https://example.com")', }, + fetch_hosts: { + type: 'string', + default_value: undefined, + description: 'Comma-separated list of host names to which HTTP fetch requests are allowed. None if empty.', + }, }; let gGlobalSettings = { @@ -404,6 +410,9 @@ async function getProcessBlob(blobId, key, options) { return ssb.privateMessageDecrypt(process.credentials.session.name, id, message); } }; + imports.fetch = function(url, options) { + return http.fetch(url, options, gGlobalSettings.fetch_hosts); + } if (process.credentials && process.credentials.session && @@ -583,12 +592,12 @@ function guessTypeFromMagicBytes(data) { } } -function sendData(response, data, type, headers) { +function sendData(response, data, type, headers, status_code) { if (data) { - response.writeHead(200, Object.assign({"Content-Type": type || guessTypeFromMagicBytes(data) || "application/binary", "Content-Length": data.byteLength}, headers || {})); + response.writeHead(status_code ?? 200, Object.assign({"Content-Type": type || guessTypeFromMagicBytes(data) || "application/binary", "Content-Length": data.byteLength}, headers || {})); response.end(data); } else { - response.writeHead(404, Object.assign({"Content-Type": "text/plain; charset=utf-8", "Content-Length": "File not found".length}, headers || {})); + response.writeHead(status_code ?? 404, Object.assign({"Content-Type": "text/plain; charset=utf-8", "Content-Length": "File not found".length}, headers || {})); response.end("File not found"); } } @@ -604,7 +613,8 @@ async function getBlobOrContent(id) { } let g_handler_index = 0; -async function useAppHandler(response, handler_blob_id, path, query, headers) { +async function useAppHandler(response, handler_blob_id, path, query, headers, packageOwner, packageName) { + print('useAppHandler', packageOwner, packageName); let do_resolve; let promise = new Promise(async function(resolve, reject) { do_resolve = resolve; @@ -622,6 +632,8 @@ async function useAppHandler(response, handler_blob_id, path, query, headers) { respond: do_resolve, }, credentials: auth.query(headers), + packageOwner: packageOwner, + packageName: packageName, }); await process.ready; @@ -784,7 +796,11 @@ async function blobHandler(request, response, blobId, uri) { let match; let id; let app_id = blobId; + let packageOwner; + let packageName; if (match = /^\/\~(\w+)\/(\w+)$/.exec(blobId)) { + packageOwner = match[1]; + packageName = match[2]; let db = new Database(match[1]); app_id = await db.get('path:' + match[2]); } @@ -794,7 +810,7 @@ async function blobHandler(request, response, blobId, uri) { if (!id && app_object.files['handler.js']) { let answer; try { - answer = await useAppHandler(response, app_id, uri.substring(1), request.query ? form.decodeForm(request.query) : undefined, request.headers); + answer = await useAppHandler(response, app_id, uri.substring(1), request.query ? form.decodeForm(request.query) : undefined, request.headers, packageOwner, packageName); } catch (error) { data = utf8Encode(`Internal Server Error\n\n${error?.message}\n${error?.stack}`); response.writeHead(500, {'Content-Type': 'text/plain; charset=utf-8', 'Content-Length': data.length}); @@ -804,10 +820,10 @@ async function blobHandler(request, response, blobId, uri) { if (answer && typeof answer.data == 'string') { answer.data = utf8Encode(answer.data); } - sendData(response, answer?.data, answer?.content_type, { + sendData(response, answer?.data, answer?.content_type, Object.assign(answer?.headers ?? {}, { 'Access-Control-Allow-Origin': '*', 'Content-Security-Policy': 'sandbox', - }); + }), answer.status_code); } else if (id) { if (request.headers['if-none-match'] && request.headers['if-none-match'] == '"' + id + '"') { let headers = { diff --git a/core/http.js b/core/http.js index 9bc02e3e..4caebb51 100644 --- a/core/http.js +++ b/core/http.js @@ -1,43 +1,45 @@ function parseUrl(url) { // XXX: Hack. - let match = url.match(new RegExp("(\\w+)://([^/]+)?(.*)")); + let match = url.match(new RegExp("(\\w+)://([^/:]+)(?::(\\d+))?(.*)")); return { protocol: match[1], host: match[2], - path: match[3], - port: match[1] == "http" ? 80 : 443, + path: match[4], + port: match[3] ? parseInt(match[3]) : match[1] == "http" ? 80 : 443, }; } function parseResponse(data) { let firstLine; let headers = {}; - while (true) { let endLine = data.indexOf('\r\n'); let line = data.substring(0, endLine); - if (!firstLine) { - firstLine = line; - } else if (!line.length) { + 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); } - data = data.substring(endLine + 2); } return {body: data}; } -export function fetch(url, options) { +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) + let buffer = new Uint8Array(0); return socket.connect(parsed.host, parsed.port).then(function() { socket.read(function(data) { - if (data) { + if (data && data.length) { let newBuffer = new Uint8Array(buffer.length + data.length); newBuffer.set(buffer, 0); newBuffer.set(data, buffer.length); @@ -51,7 +53,12 @@ export function fetch(url, options) { return socket.startTls(); } }).then(function() { - socket.write(`${options?.method ?? 'GET'} ${parsed.path} HTTP/1.0\r\nHost: ${parsed.host}\r\nConnection: close\r\n\r\n`); + 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); socket.shutdown(); }).catch(function(error) { reject(error);