Compare commits
20 Commits
v0.0.24
...
cb50c43e93
Author | SHA1 | Date | |
---|---|---|---|
cb50c43e93 | |||
5908d15f91 | |||
f66cfaec12 | |||
259f92c53b | |||
a84f850e91 | |||
5a765e6f07 | |||
791889c659 | |||
5da63faf1f | |||
30d108fc35 | |||
a09fefab5e | |||
f74ca1c236 | |||
30e027092b | |||
fd4ac7c9b9 | |||
4482049b94 | |||
5839380437 | |||
2152470fdc | |||
93b2a81495 | |||
e139e952c0 | |||
cf1c57ccb8 | |||
f7a2138488 |
@ -3,9 +3,9 @@
|
|||||||
MAKEFLAGS += --warn-undefined-variables
|
MAKEFLAGS += --warn-undefined-variables
|
||||||
MAKEFLAGS += --no-builtin-rules
|
MAKEFLAGS += --no-builtin-rules
|
||||||
|
|
||||||
VERSION_CODE := 29
|
VERSION_CODE := 30
|
||||||
VERSION_NUMBER := 0.0.24
|
VERSION_NUMBER := 0.0.25-wip
|
||||||
VERSION_NAME := Honey bunches of boats.
|
VERSION_NAME := This program kills fascists.
|
||||||
|
|
||||||
SQLITE_URL := https://www.sqlite.org/2024/sqlite-amalgamation-3470000.zip
|
SQLITE_URL := https://www.sqlite.org/2024/sqlite-amalgamation-3470000.zip
|
||||||
BUNDLETOOL_URL := https://github.com/google/bundletool/releases/download/1.17.0/bundletool-all-1.17.0.jar
|
BUNDLETOOL_URL := https://github.com/google/bundletool/releases/download/1.17.0/bundletool-all-1.17.0.jar
|
||||||
|
4
apps/test.json
Normal file
4
apps/test.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"type": "tildefriends-app",
|
||||||
|
"emoji": "📦"
|
||||||
|
}
|
3
apps/test/app.js
Normal file
3
apps/test/app.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
app.setDocument(
|
||||||
|
'<p style="color: #fff">Maybe one day this app will run tests, but for now there is nothing to see here.</p>'
|
||||||
|
);
|
1
apps/test/hello.txt
Normal file
1
apps/test/hello.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Hello, world!
|
@ -1174,7 +1174,7 @@ function api_requestPermission(permission, id) {
|
|||||||
|
|
||||||
let div = document.createElement('div');
|
let div = document.createElement('div');
|
||||||
div.appendChild(
|
div.appendChild(
|
||||||
document.createTextNode('This app is requesting the following permission:')
|
document.createTextNode('This app is requesting the following permission: ')
|
||||||
);
|
);
|
||||||
let span = document.createElement('span');
|
let span = document.createElement('span');
|
||||||
span.style = 'font-weight: bold';
|
span.style = 'font-weight: bold';
|
||||||
@ -1190,6 +1190,7 @@ function api_requestPermission(permission, id) {
|
|||||||
check.classList.add('w3-check');
|
check.classList.add('w3-check');
|
||||||
check.classList.add('w3-blue');
|
check.classList.add('w3-blue');
|
||||||
div.appendChild(check);
|
div.appendChild(check);
|
||||||
|
div.appendChild(document.createTextNode(' '));
|
||||||
let label = document.createElement('label');
|
let label = document.createElement('label');
|
||||||
label.htmlFor = check.id;
|
label.htmlFor = check.id;
|
||||||
label.appendChild(document.createTextNode('Remember this decision.'));
|
label.appendChild(document.createTextNode('Remember this decision.'));
|
||||||
|
364
core/core.js
364
core/core.js
@ -4,89 +4,6 @@ import * as http from './http.js';
|
|||||||
|
|
||||||
let gProcesses = {};
|
let gProcesses = {};
|
||||||
let gStatsTimer = false;
|
let gStatsTimer = false;
|
||||||
|
|
||||||
const k_content_security_policy =
|
|
||||||
'sandbox allow-downloads allow-top-navigation-by-user-activation';
|
|
||||||
|
|
||||||
const k_global_settings = {
|
|
||||||
index: {
|
|
||||||
type: 'string',
|
|
||||||
default_value: '/~core/apps/',
|
|
||||||
description: 'Default path.',
|
|
||||||
},
|
|
||||||
index_map: {
|
|
||||||
type: 'textarea',
|
|
||||||
default_value: undefined,
|
|
||||||
description:
|
|
||||||
'Mappings from hostname to redirect path, one per line, as in: "www.tildefriends.net=/~core/index/"',
|
|
||||||
},
|
|
||||||
room: {
|
|
||||||
type: 'boolean',
|
|
||||||
default_value: true,
|
|
||||||
description: 'Enable peers to tunnel through this instance as a room.',
|
|
||||||
},
|
|
||||||
room_name: {
|
|
||||||
type: 'string',
|
|
||||||
default_value: 'tilde friends tunnel',
|
|
||||||
description: 'Name of the room.',
|
|
||||||
},
|
|
||||||
replicator: {
|
|
||||||
type: 'boolean',
|
|
||||||
default_value: true,
|
|
||||||
description: 'Enable message and blob replication.',
|
|
||||||
},
|
|
||||||
code_of_conduct: {
|
|
||||||
type: 'textarea',
|
|
||||||
default_value: undefined,
|
|
||||||
description: 'Code of conduct presented at sign-in.',
|
|
||||||
},
|
|
||||||
http_redirect: {
|
|
||||||
type: 'string',
|
|
||||||
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.',
|
|
||||||
},
|
|
||||||
blob_fetch_age_seconds: {
|
|
||||||
type: 'integer',
|
|
||||||
default_value:
|
|
||||||
platform() == 'android' || platform() == 'iphone'
|
|
||||||
? 0.5 * 365 * 24 * 60 * 60
|
|
||||||
: undefined,
|
|
||||||
description:
|
|
||||||
'Only blobs mentioned more recently than this age will be automatically fetched.',
|
|
||||||
},
|
|
||||||
blob_expire_age_seconds: {
|
|
||||||
type: 'integer',
|
|
||||||
default_value:
|
|
||||||
platform() == 'android' || platform() == 'iphone'
|
|
||||||
? 1.0 * 365 * 24 * 60 * 60
|
|
||||||
: undefined,
|
|
||||||
description: 'Blobs older than this will be automatically deleted.',
|
|
||||||
},
|
|
||||||
seeds_host: {
|
|
||||||
type: 'string',
|
|
||||||
default_value: 'seeds.tildefriends.net',
|
|
||||||
description: 'Hostname for seed connections.',
|
|
||||||
},
|
|
||||||
peer_exchange: {
|
|
||||||
type: 'boolean',
|
|
||||||
default_value: false,
|
|
||||||
description:
|
|
||||||
'Enable discovery of, sharing of, and connecting to internet peer strangers, including announcing this instance.',
|
|
||||||
},
|
|
||||||
account_registration: {
|
|
||||||
type: 'boolean',
|
|
||||||
default_value: true,
|
|
||||||
description: 'Allow registration of new accounts.',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let kPingInterval = 60 * 1000;
|
let kPingInterval = 60 * 1000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -489,7 +406,7 @@ async function getProcessBlob(blobId, key, options) {
|
|||||||
};
|
};
|
||||||
if (process.credentials?.permissions?.administration) {
|
if (process.credentials?.permissions?.administration) {
|
||||||
imports.core.globalSettingsDescriptions = async function () {
|
imports.core.globalSettingsDescriptions = async function () {
|
||||||
let settings = Object.assign({}, k_global_settings);
|
let settings = Object.assign({}, defaultGlobalSettings());
|
||||||
for (let [key, value] of Object.entries(await loadSettings())) {
|
for (let [key, value] of Object.entries(await loadSettings())) {
|
||||||
if (settings[key]) {
|
if (settings[key]) {
|
||||||
settings[key].value = value;
|
settings[key].value = value;
|
||||||
@ -812,206 +729,6 @@ async function getProcessBlob(blobId, key, options) {
|
|||||||
return process;
|
return process;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* TODOC
|
|
||||||
* @param {*} response
|
|
||||||
* @param {*} data
|
|
||||||
* @param {*} type
|
|
||||||
* @param {*} headers
|
|
||||||
* @param {*} status_code
|
|
||||||
*/
|
|
||||||
function sendData(response, data, type, headers, status_code) {
|
|
||||||
if (data) {
|
|
||||||
response.writeHead(
|
|
||||||
status_code ?? 200,
|
|
||||||
Object.assign(
|
|
||||||
{
|
|
||||||
'Content-Type':
|
|
||||||
type ||
|
|
||||||
httpd.mime_type_from_magic_bytes(data) ||
|
|
||||||
'application/binary',
|
|
||||||
'Content-Length': data.byteLength,
|
|
||||||
},
|
|
||||||
headers || {}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
response.end(data);
|
|
||||||
} else {
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let g_handler_index = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODOC
|
|
||||||
* @param {*} response
|
|
||||||
* @param {*} handler_blob_id
|
|
||||||
* @param {*} path
|
|
||||||
* @param {*} query
|
|
||||||
* @param {*} headers
|
|
||||||
* @param {*} packageOwner
|
|
||||||
* @param {*} packageName
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
let process;
|
|
||||||
let result;
|
|
||||||
try {
|
|
||||||
process = await getProcessBlob(
|
|
||||||
handler_blob_id,
|
|
||||||
'handler_' + g_handler_index++,
|
|
||||||
{
|
|
||||||
script: 'handler.js',
|
|
||||||
imports: {
|
|
||||||
request: {
|
|
||||||
path: path,
|
|
||||||
query: query,
|
|
||||||
},
|
|
||||||
respond: do_resolve,
|
|
||||||
},
|
|
||||||
credentials: await httpd.auth_query(headers),
|
|
||||||
packageOwner: packageOwner,
|
|
||||||
packageName: packageName,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
await process.ready;
|
|
||||||
|
|
||||||
result = await promise;
|
|
||||||
} finally {
|
|
||||||
if (process?.task) {
|
|
||||||
await process.task.kill();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODOC
|
|
||||||
* @param {*} request
|
|
||||||
* @param {*} response
|
|
||||||
* @param {*} blobId
|
|
||||||
* @param {*} uri
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async function blobHandler(request, response, blobId, uri) {
|
|
||||||
if (!uri) {
|
|
||||||
response.writeHead(303, {
|
|
||||||
Location:
|
|
||||||
(request.client.tls ? 'https://' : 'http://') +
|
|
||||||
(request.headers['x-forwarded-host'] ?? request.headers.host) +
|
|
||||||
blobId +
|
|
||||||
'/',
|
|
||||||
'Content-Length': '0',
|
|
||||||
});
|
|
||||||
response.end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let process;
|
|
||||||
let data;
|
|
||||||
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]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let app_object = JSON.parse(utf8Decode(await ssb.blobGet(app_id)));
|
|
||||||
id = app_object?.files[uri.substring(1)];
|
|
||||||
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,
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
response.end(data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (answer && typeof answer.data == 'string') {
|
|
||||||
answer.data = utf8Encode(answer.data);
|
|
||||||
}
|
|
||||||
sendData(
|
|
||||||
response,
|
|
||||||
answer?.data,
|
|
||||||
answer?.content_type,
|
|
||||||
Object.assign(answer?.headers ?? {}, {
|
|
||||||
'Access-Control-Allow-Origin': '*',
|
|
||||||
'Content-Security-Policy': k_content_security_policy,
|
|
||||||
}),
|
|
||||||
answer.status_code
|
|
||||||
);
|
|
||||||
} else if (id) {
|
|
||||||
if (
|
|
||||||
request.headers['if-none-match'] &&
|
|
||||||
request.headers['if-none-match'] == '"' + id + '"'
|
|
||||||
) {
|
|
||||||
let headers = {
|
|
||||||
'Access-Control-Allow-Origin': '*',
|
|
||||||
'Content-Security-Policy': k_content_security_policy,
|
|
||||||
'Content-Length': '0',
|
|
||||||
};
|
|
||||||
response.writeHead(304, headers);
|
|
||||||
response.end();
|
|
||||||
} else {
|
|
||||||
let headers = {
|
|
||||||
ETag: '"' + id + '"',
|
|
||||||
'Access-Control-Allow-Origin': '*',
|
|
||||||
'Content-Security-Policy': k_content_security_policy,
|
|
||||||
};
|
|
||||||
data = await ssb.blobGet(id);
|
|
||||||
let type =
|
|
||||||
httpd.mime_type_from_extension(uri) ||
|
|
||||||
httpd.mime_type_from_magic_bytes(data);
|
|
||||||
sendData(response, data, type, headers);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendData(response, data, undefined, {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ssb.addEventListener('message', function () {
|
ssb.addEventListener('message', function () {
|
||||||
broadcastEvent('onMessage', [...arguments]);
|
broadcastEvent('onMessage', [...arguments]);
|
||||||
});
|
});
|
||||||
@ -1037,7 +754,7 @@ async function loadSettings() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
print('Settings not found in database:', error);
|
print('Settings not found in database:', error);
|
||||||
}
|
}
|
||||||
for (let [key, value] of Object.entries(k_global_settings)) {
|
for (let [key, value] of Object.entries(defaultGlobalSettings())) {
|
||||||
if (data[key] === undefined) {
|
if (data[key] === undefined) {
|
||||||
data[key] = value.default_value;
|
data[key] = value.default_value;
|
||||||
}
|
}
|
||||||
@ -1063,6 +780,73 @@ function sendStats() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let g_handler_index = 0;
|
||||||
|
|
||||||
|
exports.callAppHandler = async function callAppHandler(
|
||||||
|
response,
|
||||||
|
app_blob_id,
|
||||||
|
path,
|
||||||
|
query,
|
||||||
|
headers,
|
||||||
|
package_owner,
|
||||||
|
package_name
|
||||||
|
) {
|
||||||
|
let answer;
|
||||||
|
try {
|
||||||
|
let do_resolve;
|
||||||
|
let promise = new Promise(async function (resolve, reject) {
|
||||||
|
do_resolve = resolve;
|
||||||
|
});
|
||||||
|
let process;
|
||||||
|
try {
|
||||||
|
process = await getProcessBlob(
|
||||||
|
app_blob_id,
|
||||||
|
'handler_' + g_handler_index++,
|
||||||
|
{
|
||||||
|
script: 'handler.js',
|
||||||
|
imports: {
|
||||||
|
request: {
|
||||||
|
path: path,
|
||||||
|
query: query,
|
||||||
|
},
|
||||||
|
respond: do_resolve,
|
||||||
|
},
|
||||||
|
credentials: await httpd.auth_query(headers),
|
||||||
|
packageOwner: package_owner,
|
||||||
|
packageName: package_name,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await process.ready;
|
||||||
|
answer = await promise;
|
||||||
|
} finally {
|
||||||
|
if (process?.task) {
|
||||||
|
await process.task.kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
let 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,
|
||||||
|
});
|
||||||
|
response.end(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof answer?.data == 'string') {
|
||||||
|
answer.data = utf8Encode(answer.data);
|
||||||
|
}
|
||||||
|
response.writeHead(answer?.status_code, {
|
||||||
|
'Content-Type': answer?.content_type,
|
||||||
|
'Content-Length': answer?.data?.length,
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Content-Security-Policy':
|
||||||
|
'sandbox allow-downloads allow-top-navigation-by-user-activation',
|
||||||
|
});
|
||||||
|
response.end(answer?.data);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* TODOC
|
||||||
*/
|
*/
|
||||||
@ -1072,16 +856,6 @@ loadSettings()
|
|||||||
httpd.set_http_redirect(settings.http_redirect);
|
httpd.set_http_redirect(settings.http_redirect);
|
||||||
}
|
}
|
||||||
httpd.all('/app/socket', app.socket);
|
httpd.all('/app/socket', app.socket);
|
||||||
httpd.all('', function default_http_handler(request, response) {
|
|
||||||
let match;
|
|
||||||
if ((match = /^(\/~[^\/]+\/[^\/]+)(\/?.*)$/.exec(request.uri))) {
|
|
||||||
return blobHandler(request, response, match[1], match[2]);
|
|
||||||
} else if (
|
|
||||||
(match = /^\/([&\%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(request.uri))
|
|
||||||
) {
|
|
||||||
return blobHandler(request, response, match[1], match[2]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let port = httpd.start(tildefriends.http_port);
|
let port = httpd.start(tildefriends.http_port);
|
||||||
if (tildefriends.args.out_http_port_file) {
|
if (tildefriends.args.out_http_port_file) {
|
||||||
print('Writing the port file.');
|
print('Writing the port file.');
|
||||||
|
@ -21,14 +21,14 @@
|
|||||||
}:
|
}:
|
||||||
pkgs.stdenv.mkDerivation rec {
|
pkgs.stdenv.mkDerivation rec {
|
||||||
pname = "tildefriends";
|
pname = "tildefriends";
|
||||||
version = "0.0.23";
|
version = "0.0.24";
|
||||||
|
|
||||||
src = pkgs.fetchFromGitea {
|
src = pkgs.fetchFromGitea {
|
||||||
domain = "dev.tildefriends.net";
|
domain = "dev.tildefriends.net";
|
||||||
owner = "cory";
|
owner = "cory";
|
||||||
repo = "tildefriends";
|
repo = "tildefriends";
|
||||||
rev = "v${version}";
|
rev = "v${version}";
|
||||||
hash = "sha256-ukZpi+BXRTFGbdvd5ApmctTo8bjtPJMHjqFPgVSyBWU=";
|
hash = "sha256-XlmRr08UmScY//qxUEXHzagXHCFqARRYr3q8RK/jKFY=";
|
||||||
fetchSubmodules = true;
|
fetchSubmodules = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
2
deps/codemirror/cm6.js
vendored
2
deps/codemirror/cm6.js
vendored
File diff suppressed because one or more lines are too long
180
deps/codemirror_src/package-lock.json
generated
vendored
180
deps/codemirror_src/package-lock.json
generated
vendored
@ -19,9 +19,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@codemirror/autocomplete": {
|
"node_modules/@codemirror/autocomplete": {
|
||||||
"version": "6.18.1",
|
"version": "6.18.2",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.2.tgz",
|
||||||
"integrity": "sha512-iWHdj/B1ethnHRTwZj+C1obmmuCzquH29EbcKr0qIjA9NfDeBDJ7vs+WOHsFeLeflE4o+dHfYndJloMKHUkWUA==",
|
"integrity": "sha512-wJGylKtMFR/Ds6Gh01+OovXE/pncPiKZNNBKuC39pKnH+XK5d9+WsNqcrdxPjFPFTigRBqse0rfxw9UxrfyhPg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/language": "^6.0.0",
|
"@codemirror/language": "^6.0.0",
|
||||||
@ -129,9 +129,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@codemirror/search": {
|
"node_modules/@codemirror/search": {
|
||||||
"version": "6.5.6",
|
"version": "6.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.6.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.7.tgz",
|
||||||
"integrity": "sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==",
|
"integrity": "sha512-6+iLsXvITWKHYlkgHPCs/qiX4dNzn8N78YfhOFvPtPYCkuXqZq10rAfsUMhOq7O/1VjJqdXRflyExlfVcu/9VQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/state": "^6.0.0",
|
"@codemirror/state": "^6.0.0",
|
||||||
@ -370,9 +370,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.24.0",
|
"version": "4.24.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.3.tgz",
|
||||||
"integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==",
|
"integrity": "sha512-ufb2CH2KfBWPJok95frEZZ82LtDl0A6QKTa8MoM+cWwDZvVGl5/jNb79pIhRvAalUu+7LD91VYR0nwRD799HkQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@ -383,9 +383,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm64": {
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
"version": "4.24.0",
|
"version": "4.24.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.3.tgz",
|
||||||
"integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==",
|
"integrity": "sha512-iAHpft/eQk9vkWIV5t22V77d90CRofgR2006UiCjHcHJFVI1E0oBkQIAbz+pLtthFw3hWEmVB4ilxGyBf48i2Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -396,9 +396,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.24.0",
|
"version": "4.24.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.3.tgz",
|
||||||
"integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==",
|
"integrity": "sha512-QPW2YmkWLlvqmOa2OwrfqLJqkHm7kJCIMq9kOz40Zo9Ipi40kf9ONG5Sz76zszrmIZZ4hgRIkez69YnTHgEz1w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -409,9 +409,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.24.0",
|
"version": "4.24.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.3.tgz",
|
||||||
"integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==",
|
"integrity": "sha512-KO0pN5x3+uZm1ZXeIfDqwcvnQ9UEGN8JX5ufhmgH5Lz4ujjZMAnxQygZAVGemFWn+ZZC0FQopruV4lqmGMshow==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -421,10 +421,36 @@
|
|||||||
"darwin"
|
"darwin"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||||
|
"version": "4.24.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.24.3.tgz",
|
||||||
|
"integrity": "sha512-CsC+ZdIiZCZbBI+aRlWpYJMSWvVssPuWqrDy/zi9YfnatKKSLFCe6fjna1grHuo/nVaHG+kiglpRhyBQYRTK4A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||||
|
"version": "4.24.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.24.3.tgz",
|
||||||
|
"integrity": "sha512-F0nqiLThcfKvRQhZEzMIXOQG4EeX61im61VYL1jo4eBxv4aZRmpin6crnBJQ/nWnCsjH5F6J3W6Stdm0mBNqBg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
"version": "4.24.0",
|
"version": "4.24.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.3.tgz",
|
||||||
"integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==",
|
"integrity": "sha512-KRSFHyE/RdxQ1CSeOIBVIAxStFC/hnBgVcaiCkQaVC+EYDtTe4X7z5tBkFyRoBgUGtB6Xg6t9t2kulnX6wJc6A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@ -435,9 +461,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
"version": "4.24.0",
|
"version": "4.24.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.3.tgz",
|
||||||
"integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==",
|
"integrity": "sha512-h6Q8MT+e05zP5BxEKz0vi0DhthLdrNEnspdLzkoFqGwnmOzakEHSlXfVyA4HJ322QtFy7biUAVFPvIDEDQa6rw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@ -448,9 +474,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
"version": "4.24.0",
|
"version": "4.24.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.3.tgz",
|
||||||
"integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==",
|
"integrity": "sha512-fKElSyXhXIJ9pqiYRqisfirIo2Z5pTTve5K438URf08fsypXrEkVmShkSfM8GJ1aUyvjakT+fn2W7Czlpd/0FQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -461,9 +487,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.24.0",
|
"version": "4.24.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.3.tgz",
|
||||||
"integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==",
|
"integrity": "sha512-YlddZSUk8G0px9/+V9PVilVDC6ydMz7WquxozToozSnfFK6wa6ne1ATUjUvjin09jp34p84milxlY5ikueoenw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -474,9 +500,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||||
"version": "4.24.0",
|
"version": "4.24.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.3.tgz",
|
||||||
"integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==",
|
"integrity": "sha512-yNaWw+GAO8JjVx3s3cMeG5Esz1cKVzz8PkTJSfYzE5u7A+NvGmbVFEHP+BikTIyYWuz0+DX9kaA3pH9Sqxp69g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@ -487,9 +513,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
"version": "4.24.0",
|
"version": "4.24.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.3.tgz",
|
||||||
"integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==",
|
"integrity": "sha512-lWKNQfsbpv14ZCtM/HkjCTm4oWTKTfxPmr7iPfp3AHSqyoTz5AgLemYkWLwOBWc+XxBbrU9SCokZP0WlBZM9lA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@ -500,9 +526,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
"version": "4.24.0",
|
"version": "4.24.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.3.tgz",
|
||||||
"integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==",
|
"integrity": "sha512-HoojGXTC2CgCcq0Woc/dn12wQUlkNyfH0I1ABK4Ni9YXyFQa86Fkt2Q0nqgLfbhkyfQ6003i3qQk9pLh/SpAYw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@ -513,9 +539,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.24.0",
|
"version": "4.24.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.3.tgz",
|
||||||
"integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==",
|
"integrity": "sha512-mnEOh4iE4USSccBOtcrjF5nj+5/zm6NcNhbSEfR3Ot0pxBwvEn5QVUXcuOwwPkapDtGZ6pT02xLoPaNv06w7KQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -526,9 +552,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
"version": "4.24.0",
|
"version": "4.24.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.3.tgz",
|
||||||
"integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==",
|
"integrity": "sha512-rMTzawBPimBQkG9NKpNHvquIUTQPzrnPxPbCY1Xt+mFkW7pshvyIS5kYgcf74goxXOQk0CP3EoOC1zcEezKXhw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -539,9 +565,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
"version": "4.24.0",
|
"version": "4.24.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.3.tgz",
|
||||||
"integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==",
|
"integrity": "sha512-2lg1CE305xNvnH3SyiKwPVsTVLCg4TmNCF1z7PSHX2uZY2VbUpdkgAllVoISD7JO7zu+YynpWNSKAtOrX3AiuA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -552,9 +578,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
"version": "4.24.0",
|
"version": "4.24.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.3.tgz",
|
||||||
"integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==",
|
"integrity": "sha512-9SjYp1sPyxJsPWuhOCX6F4jUMXGbVVd5obVpoVEi8ClZqo52ViZewA6eFz85y8ezuOA+uJMP5A5zo6Oz4S5rVQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@ -565,9 +591,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
"version": "4.24.0",
|
"version": "4.24.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.3.tgz",
|
||||||
"integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==",
|
"integrity": "sha512-HGZgRFFYrMrP3TJlq58nR1xy8zHKId25vhmm5S9jETEfDf6xybPxsavFTJaufe2zgOGYJBskGlj49CwtEuFhWQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -590,9 +616,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.13.0",
|
"version": "8.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
||||||
"integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==",
|
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -754,9 +780,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.24.0",
|
"version": "4.24.3",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.3.tgz",
|
||||||
"integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==",
|
"integrity": "sha512-HBW896xR5HGmoksbi3JBDtmVzWiPAYqp7wip50hjQ67JbDz61nyoMPdqu1DvVW9asYb2M65Z20ZHsyJCMqMyDg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "1.0.6"
|
"@types/estree": "1.0.6"
|
||||||
@ -769,22 +795,24 @@
|
|||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.24.0",
|
"@rollup/rollup-android-arm-eabi": "4.24.3",
|
||||||
"@rollup/rollup-android-arm64": "4.24.0",
|
"@rollup/rollup-android-arm64": "4.24.3",
|
||||||
"@rollup/rollup-darwin-arm64": "4.24.0",
|
"@rollup/rollup-darwin-arm64": "4.24.3",
|
||||||
"@rollup/rollup-darwin-x64": "4.24.0",
|
"@rollup/rollup-darwin-x64": "4.24.3",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.24.0",
|
"@rollup/rollup-freebsd-arm64": "4.24.3",
|
||||||
"@rollup/rollup-linux-arm-musleabihf": "4.24.0",
|
"@rollup/rollup-freebsd-x64": "4.24.3",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.24.0",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.24.3",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.24.0",
|
"@rollup/rollup-linux-arm-musleabihf": "4.24.3",
|
||||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.24.0",
|
"@rollup/rollup-linux-arm64-gnu": "4.24.3",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.24.0",
|
"@rollup/rollup-linux-arm64-musl": "4.24.3",
|
||||||
"@rollup/rollup-linux-s390x-gnu": "4.24.0",
|
"@rollup/rollup-linux-powerpc64le-gnu": "4.24.3",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.24.0",
|
"@rollup/rollup-linux-riscv64-gnu": "4.24.3",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.24.0",
|
"@rollup/rollup-linux-s390x-gnu": "4.24.3",
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.24.0",
|
"@rollup/rollup-linux-x64-gnu": "4.24.3",
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.24.0",
|
"@rollup/rollup-linux-x64-musl": "4.24.3",
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.24.0",
|
"@rollup/rollup-win32-arm64-msvc": "4.24.3",
|
||||||
|
"@rollup/rollup-win32-ia32-msvc": "4.24.3",
|
||||||
|
"@rollup/rollup-win32-x64-msvc": "4.24.3",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
2
deps/libbacktrace
vendored
2
deps/libbacktrace
vendored
Submodule deps/libbacktrace updated: 531aec7c52...d48f84034c
@ -12,7 +12,7 @@
|
|||||||
- nix
|
- nix
|
||||||
- comment out the hash in default.nix
|
- comment out the hash in default.nix
|
||||||
- update the version
|
- update the version
|
||||||
- run `nix build`
|
- run `nix-build`
|
||||||
- update the hash
|
- update the hash
|
||||||
- bump the versions in GNUmakefile for the next release
|
- bump the versions in GNUmakefile for the next release
|
||||||
- make
|
- make
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.unprompted.tildefriends"
|
package="com.unprompted.tildefriends"
|
||||||
android:versionCode="29"
|
android:versionCode="30"
|
||||||
android:versionName="0.0.24">
|
android:versionName="0.0.25-wip">
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<application
|
<application
|
||||||
|
39
src/http.c
39
src/http.c
@ -67,7 +67,6 @@ typedef struct _tf_http_connection_t
|
|||||||
typedef struct _tf_http_handler_t
|
typedef struct _tf_http_handler_t
|
||||||
{
|
{
|
||||||
const char* pattern;
|
const char* pattern;
|
||||||
bool is_wildcard;
|
|
||||||
tf_http_callback_t* callback;
|
tf_http_callback_t* callback;
|
||||||
tf_http_cleanup_t* cleanup;
|
tf_http_cleanup_t* cleanup;
|
||||||
void* user_data;
|
void* user_data;
|
||||||
@ -129,9 +128,15 @@ static void _http_allocate_buffer(uv_handle_t* handle, size_t suggested_size, uv
|
|||||||
*buf = uv_buf_init(connection->incoming, sizeof(connection->incoming));
|
*buf = uv_buf_init(connection->incoming, sizeof(connection->incoming));
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _http_pattern_matches(const char* pattern, const char* path, bool is_wildcard)
|
bool tf_http_pattern_matches(const char* pattern, const char* path)
|
||||||
{
|
{
|
||||||
if (!pattern || !*pattern || (!is_wildcard && strcmp(path, pattern) == 0))
|
if (!*pattern && !*path)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const char* k_word = "{word}";
|
||||||
|
bool is_wildcard = strchr(pattern, '*') || strstr(pattern, k_word);
|
||||||
|
if (!is_wildcard && strcmp(path, pattern) == 0)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -140,17 +145,36 @@ static bool _http_pattern_matches(const char* pattern, const char* path, bool is
|
|||||||
{
|
{
|
||||||
int i = 0;
|
int i = 0;
|
||||||
int j = 0;
|
int j = 0;
|
||||||
while (pattern[i] && path[j] && pattern[i] != '*' && pattern[i] == path[j])
|
while (pattern[i] && path[j] && pattern[i] == path[j])
|
||||||
{
|
{
|
||||||
i++;
|
i++;
|
||||||
j++;
|
j++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pattern[i] == '*')
|
size_t k_word_len = strlen(k_word);
|
||||||
|
if (strncmp(pattern + i, k_word, k_word_len) == 0 && ((path[j] >= 'a' && path[j] <= 'z') || (path[j] >= 'A' && path[j] <= 'Z')))
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (_http_pattern_matches(pattern + i + 1, path + j, strchr(pattern + i + 1, '*') != NULL))
|
if ((path[j] >= 'a' && path[j] <= 'z') || (path[j] >= 'A' && path[j] <= 'Z') || (path[j] >= '0' && path[j] <= '9'))
|
||||||
|
{
|
||||||
|
if (tf_http_pattern_matches(pattern + i + k_word_len, path + j + 1))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (pattern[i] == '*')
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (tf_http_pattern_matches(pattern + i + 1, path + j))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -170,7 +194,7 @@ static bool _http_find_handler(tf_http_t* http, const char* path, tf_http_callba
|
|||||||
{
|
{
|
||||||
for (int i = 0; i < http->handlers_count; i++)
|
for (int i = 0; i < http->handlers_count; i++)
|
||||||
{
|
{
|
||||||
if (_http_pattern_matches(http->handlers[i].pattern, path, http->handlers[i].is_wildcard))
|
if (tf_http_pattern_matches(http->handlers[i].pattern, path))
|
||||||
{
|
{
|
||||||
*out_callback = http->handlers[i].callback;
|
*out_callback = http->handlers[i].callback;
|
||||||
*out_trace_name = http->handlers[i].pattern;
|
*out_trace_name = http->handlers[i].pattern;
|
||||||
@ -741,7 +765,6 @@ void tf_http_add_handler(tf_http_t* http, const char* pattern, tf_http_callback_
|
|||||||
http->handlers = tf_resize_vec(http->handlers, sizeof(tf_http_handler_t) * (http->handlers_count + 1));
|
http->handlers = tf_resize_vec(http->handlers, sizeof(tf_http_handler_t) * (http->handlers_count + 1));
|
||||||
http->handlers[http->handlers_count++] = (tf_http_handler_t) {
|
http->handlers[http->handlers_count++] = (tf_http_handler_t) {
|
||||||
.pattern = tf_strdup(pattern),
|
.pattern = tf_strdup(pattern),
|
||||||
.is_wildcard = pattern && strchr(pattern, '*') != NULL,
|
|
||||||
.callback = callback,
|
.callback = callback,
|
||||||
.cleanup = cleanup,
|
.cleanup = cleanup,
|
||||||
.user_data = user_data,
|
.user_data = user_data,
|
||||||
|
@ -228,4 +228,12 @@ void tf_http_request_websocket_upgrade(tf_http_request_t* request);
|
|||||||
*/
|
*/
|
||||||
const char* tf_http_status_text(int status);
|
const char* tf_http_status_text(int status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Match URL patterns. "*" matches anything, and "{word}" matches [a-zA-Z][a-zA-Z0-9]*".
|
||||||
|
** @param pattern The pattern to match.
|
||||||
|
** @param path The path to test.
|
||||||
|
** @return true if the path matches the pattern.
|
||||||
|
*/
|
||||||
|
bool tf_http_pattern_matches(const char* pattern, const char* path);
|
||||||
|
|
||||||
/** @} */
|
/** @} */
|
||||||
|
272
src/httpd.js.c
272
src/httpd.js.c
@ -10,6 +10,7 @@
|
|||||||
#include "tlscontext.js.h"
|
#include "tlscontext.js.h"
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
#include "util.js.h"
|
#include "util.js.h"
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
#include "ow-crypt.h"
|
#include "ow-crypt.h"
|
||||||
|
|
||||||
@ -33,6 +34,7 @@
|
|||||||
|
|
||||||
#define CYAN "\e[1;36m"
|
#define CYAN "\e[1;36m"
|
||||||
#define MAGENTA "\e[1;35m"
|
#define MAGENTA "\e[1;35m"
|
||||||
|
#define YELLOW "\e[1;33m"
|
||||||
#define RESET "\e[0m"
|
#define RESET "\e[0m"
|
||||||
|
|
||||||
const int64_t k_refresh_interval = 1ULL * 7 * 24 * 60 * 60 * 1000;
|
const int64_t k_refresh_interval = 1ULL * 7 * 24 * 60 * 60 * 1000;
|
||||||
@ -222,6 +224,17 @@ static void _httpd_message_callback(tf_http_request_t* request, int op_code, con
|
|||||||
JS_FreeValue(context, on_message);
|
JS_FreeValue(context, on_message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static JSValue _httpd_make_response_object(JSContext* context, tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
JSValue response_object = JS_NewObjectClass(context, _httpd_request_class_id);
|
||||||
|
JS_SetOpaque(response_object, request);
|
||||||
|
JS_SetPropertyStr(context, response_object, "writeHead", JS_NewCFunction(context, _httpd_response_write_head, "writeHead", 2));
|
||||||
|
JS_SetPropertyStr(context, response_object, "end", JS_NewCFunction(context, _httpd_response_end, "end", 1));
|
||||||
|
JS_SetPropertyStr(context, response_object, "send", JS_NewCFunction(context, _httpd_response_send, "send", 2));
|
||||||
|
JS_SetPropertyStr(context, response_object, "upgrade", JS_NewCFunction(context, _httpd_websocket_upgrade, "upgrade", 2));
|
||||||
|
return response_object;
|
||||||
|
}
|
||||||
|
|
||||||
static void _httpd_callback_internal(tf_http_request_t* request, bool is_websocket)
|
static void _httpd_callback_internal(tf_http_request_t* request, bool is_websocket)
|
||||||
{
|
{
|
||||||
http_handler_data_t* data = request->user_data;
|
http_handler_data_t* data = request->user_data;
|
||||||
@ -248,14 +261,9 @@ static void _httpd_callback_internal(tf_http_request_t* request, bool is_websock
|
|||||||
JS_SetPropertyStr(context, client, "tls", request->is_tls ? JS_TRUE : JS_FALSE);
|
JS_SetPropertyStr(context, client, "tls", request->is_tls ? JS_TRUE : JS_FALSE);
|
||||||
JS_SetPropertyStr(context, request_object, "client", client);
|
JS_SetPropertyStr(context, request_object, "client", client);
|
||||||
|
|
||||||
JSValue response_object = JS_NewObjectClass(context, _httpd_request_class_id);
|
JSValue response_object = _httpd_make_response_object(context, request);
|
||||||
/* The ref is owned by the JS object and will be released by the finalizer. */
|
/* The ref is owned by the JS object and will be released by the finalizer. */
|
||||||
tf_http_request_ref(request);
|
tf_http_request_ref(request);
|
||||||
JS_SetOpaque(response_object, request);
|
|
||||||
JS_SetPropertyStr(context, response_object, "writeHead", JS_NewCFunction(context, _httpd_response_write_head, "writeHead", 2));
|
|
||||||
JS_SetPropertyStr(context, response_object, "end", JS_NewCFunction(context, _httpd_response_end, "end", 1));
|
|
||||||
JS_SetPropertyStr(context, response_object, "send", JS_NewCFunction(context, _httpd_response_send, "send", 2));
|
|
||||||
JS_SetPropertyStr(context, response_object, "upgrade", JS_NewCFunction(context, _httpd_websocket_upgrade, "upgrade", 2));
|
|
||||||
JSValue args[] = {
|
JSValue args[] = {
|
||||||
request_object,
|
request_object,
|
||||||
response_object,
|
response_object,
|
||||||
@ -423,7 +431,7 @@ static JSValue _httpd_endpoint_start(JSContext* context, JSValueConst this_val,
|
|||||||
*listener = (httpd_listener_t) { .context = context, .tls = JS_DupValue(context, argv[1]) };
|
*listener = (httpd_listener_t) { .context = context, .tls = JS_DupValue(context, argv[1]) };
|
||||||
tf_tls_context_t* tls = tf_tls_context_get(listener->tls);
|
tf_tls_context_t* tls = tf_tls_context_get(listener->tls);
|
||||||
int assigned_port = tf_http_listen(http, port, tls, _httpd_listener_cleanup, listener);
|
int assigned_port = tf_http_listen(http, port, tls, _httpd_listener_cleanup, listener);
|
||||||
tf_printf(CYAN "~😎 Tilde Friends" RESET " is now up at " MAGENTA "http%s://127.0.0.1:%d/" RESET ".\n", tls ? "s" : "", assigned_port);
|
tf_printf(CYAN "~😎 Tilde Friends" RESET " " YELLOW VERSION_NUMBER RESET " is now up at " MAGENTA "http%s://127.0.0.1:%d/" RESET ".\n", tls ? "s" : "", assigned_port);
|
||||||
return JS_NewInt32(context, assigned_port);
|
return JS_NewInt32(context, assigned_port);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -558,7 +566,7 @@ static bool _magic_bytes_match(const magic_bytes_t* magic, const uint8_t* actual
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* _httpd_mime_type_from_magic_bytes_internal(const uint8_t* bytes, size_t size)
|
static const char* _httpd_mime_type_from_magic_bytes(const uint8_t* bytes, size_t size)
|
||||||
{
|
{
|
||||||
const char* type = "application/binary";
|
const char* type = "application/binary";
|
||||||
if (bytes)
|
if (bytes)
|
||||||
@ -635,13 +643,6 @@ static const char* _httpd_mime_type_from_magic_bytes_internal(const uint8_t* byt
|
|||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
static JSValue _httpd_mime_type_from_magic_bytes(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
|
||||||
{
|
|
||||||
size_t size = 0;
|
|
||||||
uint8_t* bytes = tf_util_try_get_array_buffer(context, &size, argv[0]);
|
|
||||||
return JS_NewString(context, _httpd_mime_type_from_magic_bytes_internal(bytes, size));
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char* _ext_to_content_type(const char* ext, bool use_fallback)
|
static const char* _ext_to_content_type(const char* ext, bool use_fallback)
|
||||||
{
|
{
|
||||||
if (ext)
|
if (ext)
|
||||||
@ -674,14 +675,6 @@ static const char* _ext_to_content_type(const char* ext, bool use_fallback)
|
|||||||
return use_fallback ? "application/binary" : NULL;
|
return use_fallback ? "application/binary" : NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static JSValue _httpd_mime_type_from_extension(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
|
||||||
{
|
|
||||||
const char* name = JS_ToCString(context, argv[0]);
|
|
||||||
const char* type = _ext_to_content_type(strrchr(name, '.'), false);
|
|
||||||
JS_FreeCString(context, name);
|
|
||||||
return type ? JS_NewString(context, type) : JS_UNDEFINED;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _httpd_finalizer(JSRuntime* runtime, JSValue value)
|
static void _httpd_finalizer(JSRuntime* runtime, JSValue value)
|
||||||
{
|
{
|
||||||
tf_http_t* http = JS_GetOpaque(value, _httpd_class_id);
|
tf_http_t* http = JS_GetOpaque(value, _httpd_class_id);
|
||||||
@ -966,6 +959,22 @@ static void _httpd_endpoint_static(tf_http_request_t* request)
|
|||||||
tf_file_stat(task, path, _httpd_endpoint_static_stat, request);
|
tf_file_stat(task, path, _httpd_endpoint_static_stat, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_add_slash(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
const char* host = tf_http_request_get_header(request, "x-forwarded-host");
|
||||||
|
if (!host)
|
||||||
|
{
|
||||||
|
host = tf_http_request_get_header(request, "host");
|
||||||
|
}
|
||||||
|
char url[1024];
|
||||||
|
snprintf(url, sizeof(url), "%s%s%s/", request->is_tls ? "https://" : "http://", host, request->path);
|
||||||
|
const char* headers[] = {
|
||||||
|
"Location",
|
||||||
|
url,
|
||||||
|
};
|
||||||
|
tf_http_respond(request, 303, headers, tf_countof(headers) / 2, "", 0);
|
||||||
|
}
|
||||||
|
|
||||||
typedef struct _user_app_t
|
typedef struct _user_app_t
|
||||||
{
|
{
|
||||||
const char* user;
|
const char* user;
|
||||||
@ -1016,12 +1025,204 @@ static user_app_t* _parse_user_app_from_path(const char* path, const char* expec
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef struct _app_blob_t
|
||||||
|
{
|
||||||
|
tf_http_request_t* request;
|
||||||
|
bool found;
|
||||||
|
bool not_modified;
|
||||||
|
bool use_handler;
|
||||||
|
void* data;
|
||||||
|
size_t size;
|
||||||
|
char app_blob_id[k_blob_id_len];
|
||||||
|
const char* file;
|
||||||
|
user_app_t* user_app;
|
||||||
|
char etag[256];
|
||||||
|
} app_blob_t;
|
||||||
|
|
||||||
|
static void _httpd_endpoint_app_blob_work(tf_ssb_t* ssb, void* user_data)
|
||||||
|
{
|
||||||
|
app_blob_t* data = user_data;
|
||||||
|
tf_http_request_t* request = data->request;
|
||||||
|
if (request->path[0] == '/' && request->path[1] == '~')
|
||||||
|
{
|
||||||
|
const char* last_slash = strchr(request->path + 1, '/');
|
||||||
|
if (last_slash)
|
||||||
|
{
|
||||||
|
last_slash = strchr(last_slash + 1, '/');
|
||||||
|
}
|
||||||
|
data->user_app = last_slash ? _parse_user_app_from_path(request->path, last_slash) : NULL;
|
||||||
|
if (data->user_app)
|
||||||
|
{
|
||||||
|
size_t path_length = strlen("path:") + strlen(data->user_app->app) + 1;
|
||||||
|
char* app_path = tf_malloc(path_length);
|
||||||
|
snprintf(app_path, path_length, "path:%s", data->user_app->app);
|
||||||
|
const char* value = tf_ssb_db_get_property(ssb, data->user_app->user, app_path);
|
||||||
|
snprintf(data->app_blob_id, sizeof(data->app_blob_id), "%s", value);
|
||||||
|
tf_free(app_path);
|
||||||
|
tf_free((void*)value);
|
||||||
|
data->file = last_slash + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (request->path[0] == '/' && request->path[1] == '&')
|
||||||
|
{
|
||||||
|
const char* end = strstr(request->path, ".sha256/");
|
||||||
|
if (end)
|
||||||
|
{
|
||||||
|
snprintf(data->app_blob_id, sizeof(data->app_blob_id), "%.*s", (int)(end + strlen(".sha256") - request->path - 1), request->path + 1);
|
||||||
|
data->file = end + strlen(".sha256/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char* app_blob = NULL;
|
||||||
|
size_t app_blob_size = 0;
|
||||||
|
if (*data->app_blob_id && tf_ssb_db_blob_get(ssb, data->app_blob_id, (uint8_t**)&app_blob, &app_blob_size))
|
||||||
|
{
|
||||||
|
JSMallocFunctions funcs = { 0 };
|
||||||
|
tf_get_js_malloc_functions(&funcs);
|
||||||
|
JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL);
|
||||||
|
JSContext* context = JS_NewContext(runtime);
|
||||||
|
|
||||||
|
JSValue app_object = JS_ParseJSON(context, app_blob, app_blob_size, NULL);
|
||||||
|
JSValue files = JS_GetPropertyStr(context, app_object, "files");
|
||||||
|
JSValue blob_id = JS_GetPropertyStr(context, files, data->file);
|
||||||
|
if (JS_IsUndefined(blob_id))
|
||||||
|
{
|
||||||
|
blob_id = JS_GetPropertyStr(context, files, "handler.js");
|
||||||
|
if (!JS_IsUndefined(blob_id))
|
||||||
|
{
|
||||||
|
data->use_handler = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const char* blob_id_str = JS_ToCString(context, blob_id);
|
||||||
|
if (blob_id_str)
|
||||||
|
{
|
||||||
|
snprintf(data->etag, sizeof(data->etag), "\"%s\"", blob_id_str);
|
||||||
|
const char* match = tf_http_request_get_header(data->request, "if-none-match");
|
||||||
|
if (match && strcmp(match, data->etag) == 0)
|
||||||
|
{
|
||||||
|
data->not_modified = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
data->found = tf_ssb_db_blob_get(ssb, blob_id_str, (uint8_t**)&data->data, &data->size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JS_FreeCString(context, blob_id_str);
|
||||||
|
}
|
||||||
|
JS_FreeValue(context, blob_id);
|
||||||
|
JS_FreeValue(context, files);
|
||||||
|
JS_FreeValue(context, app_object);
|
||||||
|
|
||||||
|
JS_FreeContext(context);
|
||||||
|
JS_FreeRuntime(runtime);
|
||||||
|
tf_free(app_blob);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_call_app_handler(tf_ssb_t* ssb, tf_http_request_t* request, const char* app_blob_id, const char* path, const char* package_owner, const char* app)
|
||||||
|
{
|
||||||
|
JSContext* context = tf_ssb_get_context(ssb);
|
||||||
|
JSValue global = JS_GetGlobalObject(context);
|
||||||
|
JSValue exports = JS_GetPropertyStr(context, global, "exports");
|
||||||
|
JSValue call_app_handler = JS_GetPropertyStr(context, exports, "callAppHandler");
|
||||||
|
|
||||||
|
JSValue response = _httpd_make_response_object(context, request);
|
||||||
|
tf_http_request_ref(request);
|
||||||
|
JSValue handler_blob_id = JS_NewString(context, app_blob_id);
|
||||||
|
JSValue path_value = JS_NewString(context, path);
|
||||||
|
JSValue package_owner_value = JS_NewString(context, package_owner);
|
||||||
|
JSValue app_value = JS_NewString(context, app);
|
||||||
|
JSValue query_value = request->query ? JS_NewString(context, request->query) : JS_UNDEFINED;
|
||||||
|
|
||||||
|
JSValue headers = JS_NewObject(context);
|
||||||
|
for (int i = 0; i < request->headers_count; i++)
|
||||||
|
{
|
||||||
|
char name[256] = "";
|
||||||
|
snprintf(name, sizeof(name), "%.*s", (int)request->headers[i].name_len, request->headers[i].name);
|
||||||
|
JS_SetPropertyStr(context, headers, name, JS_NewStringLen(context, request->headers[i].value, request->headers[i].value_len));
|
||||||
|
}
|
||||||
|
|
||||||
|
JSValue args[] = {
|
||||||
|
response,
|
||||||
|
handler_blob_id,
|
||||||
|
path_value,
|
||||||
|
query_value,
|
||||||
|
headers,
|
||||||
|
package_owner_value,
|
||||||
|
app_value,
|
||||||
|
};
|
||||||
|
|
||||||
|
JSValue result = JS_Call(context, call_app_handler, JS_NULL, tf_countof(args), args);
|
||||||
|
tf_util_report_error(context, result);
|
||||||
|
JS_FreeValue(context, result);
|
||||||
|
|
||||||
|
JS_FreeValue(context, headers);
|
||||||
|
JS_FreeValue(context, query_value);
|
||||||
|
JS_FreeValue(context, app_value);
|
||||||
|
JS_FreeValue(context, package_owner_value);
|
||||||
|
JS_FreeValue(context, handler_blob_id);
|
||||||
|
JS_FreeValue(context, path_value);
|
||||||
|
JS_FreeValue(context, response);
|
||||||
|
JS_FreeValue(context, call_app_handler);
|
||||||
|
JS_FreeValue(context, exports);
|
||||||
|
JS_FreeValue(context, global);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_app_blob_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
|
{
|
||||||
|
app_blob_t* data = user_data;
|
||||||
|
if (data->not_modified)
|
||||||
|
{
|
||||||
|
tf_http_respond(data->request, 304, NULL, 0, NULL, 0);
|
||||||
|
}
|
||||||
|
else if (data->use_handler)
|
||||||
|
{
|
||||||
|
_httpd_call_app_handler(ssb, data->request, data->app_blob_id, data->file, data->user_app->user, data->user_app->app);
|
||||||
|
}
|
||||||
|
else if (data->found)
|
||||||
|
{
|
||||||
|
const char* mime_type = _ext_to_content_type(strrchr(data->request->path, '.'), false);
|
||||||
|
if (!mime_type)
|
||||||
|
{
|
||||||
|
mime_type = _httpd_mime_type_from_magic_bytes(data->data, data->size);
|
||||||
|
}
|
||||||
|
const char* headers[] = {
|
||||||
|
"Access-Control-Allow-Origin",
|
||||||
|
"*",
|
||||||
|
"Content-Security-Policy",
|
||||||
|
"sandbox allow-downloads allow-top-navigation-by-user-activation",
|
||||||
|
"Content-Type",
|
||||||
|
mime_type ? mime_type : "application/binary",
|
||||||
|
"etag",
|
||||||
|
data->etag,
|
||||||
|
};
|
||||||
|
tf_http_respond(data->request, 200, headers, tf_countof(headers) / 2, data->data, data->size);
|
||||||
|
}
|
||||||
|
tf_free(data->user_app);
|
||||||
|
tf_free(data->data);
|
||||||
|
tf_http_request_unref(data->request);
|
||||||
|
tf_free(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_app_blob(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
tf_http_request_ref(request);
|
||||||
|
tf_task_t* task = request->user_data;
|
||||||
|
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||||
|
app_blob_t* data = tf_malloc(sizeof(app_blob_t));
|
||||||
|
*data = (app_blob_t) { .request = request };
|
||||||
|
tf_ssb_run_work(ssb, _httpd_endpoint_app_blob_work, _httpd_endpoint_app_blob_after_work, data);
|
||||||
|
}
|
||||||
|
|
||||||
typedef struct _view_t
|
typedef struct _view_t
|
||||||
{
|
{
|
||||||
tf_http_request_t* request;
|
tf_http_request_t* request;
|
||||||
const char** form_data;
|
const char** form_data;
|
||||||
void* data;
|
void* data;
|
||||||
size_t size;
|
size_t size;
|
||||||
|
char etag[256];
|
||||||
bool not_modified;
|
bool not_modified;
|
||||||
} view_t;
|
} view_t;
|
||||||
|
|
||||||
@ -1045,7 +1246,7 @@ static void _httpd_endpoint_view_work(tf_ssb_t* ssb, void* user_data)
|
|||||||
{
|
{
|
||||||
view_t* view = user_data;
|
view_t* view = user_data;
|
||||||
tf_http_request_t* request = view->request;
|
tf_http_request_t* request = view->request;
|
||||||
char blob_id[256] = "";
|
char blob_id[128] = "";
|
||||||
|
|
||||||
user_app_t* user_app = _parse_user_app_from_path(request->path, "/view");
|
user_app_t* user_app = _parse_user_app_from_path(request->path, "/view");
|
||||||
if (user_app)
|
if (user_app)
|
||||||
@ -1066,10 +1267,11 @@ static void _httpd_endpoint_view_work(tf_ssb_t* ssb, void* user_data)
|
|||||||
|
|
||||||
if (*blob_id)
|
if (*blob_id)
|
||||||
{
|
{
|
||||||
|
snprintf(view->etag, sizeof(view->etag), "\"%s\"", blob_id);
|
||||||
const char* if_none_match = tf_http_request_get_header(request, "if-none-match");
|
const char* if_none_match = tf_http_request_get_header(request, "if-none-match");
|
||||||
char match[258];
|
char match[258];
|
||||||
snprintf(match, sizeof(match), "\"%s\"", blob_id);
|
snprintf(match, sizeof(match), "\"%s\"", blob_id);
|
||||||
if (if_none_match && strcmp(if_none_match, match))
|
if (if_none_match && strcmp(if_none_match, match) == 0)
|
||||||
{
|
{
|
||||||
view->not_modified = true;
|
view->not_modified = true;
|
||||||
}
|
}
|
||||||
@ -1097,7 +1299,9 @@ static void _httpd_endpoint_view_after_work(tf_ssb_t* ssb, int status, void* use
|
|||||||
"Content-Security-Policy",
|
"Content-Security-Policy",
|
||||||
"sandbox allow-downloads allow-top-navigation-by-user-activation",
|
"sandbox allow-downloads allow-top-navigation-by-user-activation",
|
||||||
"Content-Type",
|
"Content-Type",
|
||||||
view->data ? _httpd_mime_type_from_magic_bytes_internal(view->data, view->size) : "text/plain",
|
view->data ? _httpd_mime_type_from_magic_bytes(view->data, view->size) : "text/plain",
|
||||||
|
"etag",
|
||||||
|
view->etag,
|
||||||
filename ? "Content-Disposition" : NULL,
|
filename ? "Content-Disposition" : NULL,
|
||||||
filename ? content_disposition : NULL,
|
filename ? content_disposition : NULL,
|
||||||
};
|
};
|
||||||
@ -2099,11 +2303,16 @@ void tf_httpd_register(JSContext* context)
|
|||||||
tf_http_add_handler(http, "/speedscope/*", _httpd_endpoint_static, NULL, task);
|
tf_http_add_handler(http, "/speedscope/*", _httpd_endpoint_static, NULL, task);
|
||||||
tf_http_add_handler(http, "/static/*", _httpd_endpoint_static, NULL, task);
|
tf_http_add_handler(http, "/static/*", _httpd_endpoint_static, NULL, task);
|
||||||
tf_http_add_handler(http, "/.well-known/*", _httpd_endpoint_static, NULL, task);
|
tf_http_add_handler(http, "/.well-known/*", _httpd_endpoint_static, NULL, task);
|
||||||
tf_http_add_handler(http, "/~*/*/", _httpd_endpoint_static, NULL, task);
|
tf_http_add_handler(http, "/&*.sha256", _httpd_endpoint_add_slash, NULL, task);
|
||||||
tf_http_add_handler(http, "/&*.sha256/", _httpd_endpoint_static, NULL, task);
|
tf_http_add_handler(http, "/&*.sha256/", _httpd_endpoint_static, NULL, task);
|
||||||
tf_http_add_handler(http, "/*/view", _httpd_endpoint_view, NULL, task);
|
tf_http_add_handler(http, "/&*.sha256/view", _httpd_endpoint_view, NULL, task);
|
||||||
tf_http_add_handler(http, "/~*/*/save", _httpd_endpoint_save, NULL, task);
|
tf_http_add_handler(http, "/&*.sha256/*", _httpd_endpoint_app_blob, NULL, task);
|
||||||
tf_http_add_handler(http, "/~*/*/delete", _httpd_endpoint_delete, NULL, task);
|
tf_http_add_handler(http, "/~{word}/{word}", _httpd_endpoint_add_slash, NULL, task);
|
||||||
|
tf_http_add_handler(http, "/~{word}/{word}/", _httpd_endpoint_static, NULL, task);
|
||||||
|
tf_http_add_handler(http, "/~{word}/{word}/save", _httpd_endpoint_save, NULL, task);
|
||||||
|
tf_http_add_handler(http, "/~{word}/{word}/delete", _httpd_endpoint_delete, NULL, task);
|
||||||
|
tf_http_add_handler(http, "/~{word}/{word}/view", _httpd_endpoint_view, NULL, task);
|
||||||
|
tf_http_add_handler(http, "/~{word}/{word}/*", _httpd_endpoint_app_blob, NULL, task);
|
||||||
tf_http_add_handler(http, "/save", _httpd_endpoint_save, NULL, task);
|
tf_http_add_handler(http, "/save", _httpd_endpoint_save, NULL, task);
|
||||||
|
|
||||||
tf_http_add_handler(http, "/robots.txt", _httpd_endpoint_robots_txt, NULL, NULL);
|
tf_http_add_handler(http, "/robots.txt", _httpd_endpoint_robots_txt, NULL, NULL);
|
||||||
@ -2116,13 +2325,10 @@ void tf_httpd_register(JSContext* context)
|
|||||||
tf_http_add_handler(http, "/login/logout", _httpd_endpoint_logout, NULL, task);
|
tf_http_add_handler(http, "/login/logout", _httpd_endpoint_logout, NULL, task);
|
||||||
tf_http_add_handler(http, "/login", _httpd_endpoint_login, NULL, task);
|
tf_http_add_handler(http, "/login", _httpd_endpoint_login, NULL, task);
|
||||||
|
|
||||||
JS_SetPropertyStr(context, httpd, "handlers", JS_NewObject(context));
|
|
||||||
JS_SetPropertyStr(context, httpd, "all", JS_NewCFunction(context, _httpd_endpoint_all, "all", 2));
|
JS_SetPropertyStr(context, httpd, "all", JS_NewCFunction(context, _httpd_endpoint_all, "all", 2));
|
||||||
JS_SetPropertyStr(context, httpd, "start", JS_NewCFunction(context, _httpd_endpoint_start, "start", 2));
|
JS_SetPropertyStr(context, httpd, "start", JS_NewCFunction(context, _httpd_endpoint_start, "start", 2));
|
||||||
JS_SetPropertyStr(context, httpd, "set_http_redirect", JS_NewCFunction(context, _httpd_set_http_redirect, "set_http_redirect", 1));
|
JS_SetPropertyStr(context, httpd, "set_http_redirect", JS_NewCFunction(context, _httpd_set_http_redirect, "set_http_redirect", 1));
|
||||||
JS_SetPropertyStr(context, httpd, "auth_query", JS_NewCFunction(context, _httpd_auth_query, "auth_query", 1));
|
JS_SetPropertyStr(context, httpd, "auth_query", JS_NewCFunction(context, _httpd_auth_query, "auth_query", 1));
|
||||||
JS_SetPropertyStr(context, httpd, "mime_type_from_magic_bytes", JS_NewCFunction(context, _httpd_mime_type_from_magic_bytes, "mime_type_from_magic_bytes", 1));
|
|
||||||
JS_SetPropertyStr(context, httpd, "mime_type_from_extension", JS_NewCFunction(context, _httpd_mime_type_from_extension, "mime_type_from_extension", 1));
|
|
||||||
JS_SetPropertyStr(context, global, "httpd", httpd);
|
JS_SetPropertyStr(context, global, "httpd", httpd);
|
||||||
JS_FreeValue(context, global);
|
JS_FreeValue(context, global);
|
||||||
}
|
}
|
||||||
|
@ -1015,6 +1015,8 @@ void tf_ssb_test_publish(const tf_test_options_t* options)
|
|||||||
char id[k_id_base64_len] = { 0 };
|
char id[k_id_base64_len] = { 0 };
|
||||||
tf_ssb_whoami(ssb, id, sizeof(id));
|
tf_ssb_whoami(ssb, id, sizeof(id));
|
||||||
|
|
||||||
|
tf_ssb_destroy(ssb);
|
||||||
|
|
||||||
char executable[1024];
|
char executable[1024];
|
||||||
size_t size = sizeof(executable);
|
size_t size = sizeof(executable);
|
||||||
uv_exepath(executable, &size);
|
uv_exepath(executable, &size);
|
||||||
@ -1033,8 +1035,6 @@ void tf_ssb_test_publish(const tf_test_options_t* options)
|
|||||||
printf("returned %d\n", WEXITSTATUS(result));
|
printf("returned %d\n", WEXITSTATUS(result));
|
||||||
assert(WEXITSTATUS(result) == 0);
|
assert(WEXITSTATUS(result) == 0);
|
||||||
|
|
||||||
tf_ssb_destroy(ssb);
|
|
||||||
|
|
||||||
uv_run(&loop, UV_RUN_DEFAULT);
|
uv_run(&loop, UV_RUN_DEFAULT);
|
||||||
uv_loop_close(&loop);
|
uv_loop_close(&loop);
|
||||||
}
|
}
|
||||||
|
@ -1869,7 +1869,6 @@ void tf_task_destroy(tf_task_t* task)
|
|||||||
{
|
{
|
||||||
JSValue global = JS_GetGlobalObject(task->_context);
|
JSValue global = JS_GetGlobalObject(task->_context);
|
||||||
JS_SetPropertyStr(task->_context, global, "httpd", JS_UNDEFINED);
|
JS_SetPropertyStr(task->_context, global, "httpd", JS_UNDEFINED);
|
||||||
JS_SetPropertyStr(task->_context, global, "gProcesses", JS_NewObject(task->_context));
|
|
||||||
JS_FreeValue(task->_context, global);
|
JS_FreeValue(task->_context, global);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
138
src/tests.c
138
src/tests.c
@ -4,6 +4,8 @@
|
|||||||
#include "http.h"
|
#include "http.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "mem.h"
|
#include "mem.h"
|
||||||
|
#include "ssb.db.h"
|
||||||
|
#include "ssb.h"
|
||||||
#include "ssb.tests.h"
|
#include "ssb.tests.h"
|
||||||
#include "util.js.h"
|
#include "util.js.h"
|
||||||
|
|
||||||
@ -782,6 +784,140 @@ static void _test_http(const tf_test_options_t* options)
|
|||||||
uv_thread_join(&thread);
|
uv_thread_join(&thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int _http_get_status_code(const char* url)
|
||||||
|
{
|
||||||
|
char command[1024];
|
||||||
|
snprintf(command, sizeof(command), "curl -s -o /dev/null -w '%%{http_code}' \"%s\"", url);
|
||||||
|
char buffer[256] = "";
|
||||||
|
FILE* file = popen(command, "r");
|
||||||
|
char* result = fgets(buffer, sizeof(buffer), file);
|
||||||
|
pclose(file);
|
||||||
|
return result ? atoi(result) : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _http_check_status_code(const char* url, int expected_code)
|
||||||
|
{
|
||||||
|
char command[1024];
|
||||||
|
snprintf(command, sizeof(command), "curl -s -o /dev/null -w '%%{http_code}' \"%s\"", url);
|
||||||
|
char buffer[256] = "";
|
||||||
|
FILE* file = popen(command, "r");
|
||||||
|
char* result = fgets(buffer, sizeof(buffer), file);
|
||||||
|
tf_printf("%s => %s\n", command, result);
|
||||||
|
assert(atoi(buffer) == expected_code);
|
||||||
|
assert(file);
|
||||||
|
int status = pclose(file);
|
||||||
|
(void)status;
|
||||||
|
assert(WEXITSTATUS(status) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _http_check_body_contains(const char* url, const char* expected)
|
||||||
|
{
|
||||||
|
char command[1024];
|
||||||
|
snprintf(command, sizeof(command), "curl -s \"%s\"", url);
|
||||||
|
char buffer[1024] = "";
|
||||||
|
FILE* file = popen(command, "r");
|
||||||
|
bool found = false;
|
||||||
|
while (!found)
|
||||||
|
{
|
||||||
|
char* result = fgets(buffer, sizeof(buffer), file);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
found = strstr(buffer, expected) != NULL;
|
||||||
|
if (found)
|
||||||
|
{
|
||||||
|
tf_printf("%s => found: \"%s\"\n", url, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
tf_printf("Didn't find \"%s\" in %s.\n", expected, url);
|
||||||
|
}
|
||||||
|
assert(found);
|
||||||
|
assert(file);
|
||||||
|
int status = pclose(file);
|
||||||
|
(void)status;
|
||||||
|
assert(WEXITSTATUS(status) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _test_httpd(const tf_test_options_t* options)
|
||||||
|
{
|
||||||
|
uv_loop_t loop = { 0 };
|
||||||
|
uv_loop_init(&loop);
|
||||||
|
|
||||||
|
unlink("out/test_db0.sqlite");
|
||||||
|
char command[256];
|
||||||
|
snprintf(command, sizeof(command), "%s run -b 0 --db-path=out/test_db0.sqlite" TEST_ARGS, options->exe_path);
|
||||||
|
|
||||||
|
uv_stdio_container_t stdio[] = {
|
||||||
|
[STDIN_FILENO] = { .flags = UV_IGNORE },
|
||||||
|
[STDOUT_FILENO] = { .flags = UV_INHERIT_FD },
|
||||||
|
[STDERR_FILENO] = { .flags = UV_INHERIT_FD },
|
||||||
|
};
|
||||||
|
uv_process_t process = { 0 };
|
||||||
|
uv_spawn(&loop, &process,
|
||||||
|
&(uv_process_options_t) {
|
||||||
|
.file = options->exe_path,
|
||||||
|
.args = (char*[]) { (char*)options->exe_path, "run", "-b0", "--db-path=out/test_db0.sqlite", "--http-port=8080", "--https-port=0", NULL },
|
||||||
|
.stdio_count = sizeof(stdio) / sizeof(*stdio),
|
||||||
|
.stdio = stdio,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
if (_http_get_status_code("http://localhost:8080/debug") == 200)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
uv_sleep(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
tf_ssb_t* ssb = tf_ssb_create(&loop, NULL, "file:out/test_db0.sqlite", NULL);
|
||||||
|
const char* app_id = tf_ssb_db_get_property(ssb, "core", "path:test");
|
||||||
|
tf_ssb_destroy(ssb);
|
||||||
|
|
||||||
|
_http_check_status_code("http://localhost:8080/404", 404);
|
||||||
|
_http_check_status_code("http://localhost:8080/", 303);
|
||||||
|
_http_check_status_code("http://localhost:8080/~core/apps/", 200);
|
||||||
|
_http_check_status_code("http://localhost:8080/~core/apps", 303);
|
||||||
|
_http_check_status_code("http://localhost:8080/~core/apps/view", 200);
|
||||||
|
_http_check_body_contains("http://localhost:8080/~core/apps/", "<title>Tilde Friends</title>");
|
||||||
|
_http_check_body_contains("http://localhost:8080/~core/apps/view", "\"type\":\"tildefriends-app\"");
|
||||||
|
_http_check_body_contains("http://localhost:8080/~core/test/hello.txt", "Hello, world!");
|
||||||
|
_http_check_status_code("http://localhost:8080/~core/test/nonexistent.txt", 404);
|
||||||
|
_http_check_body_contains("http://localhost:8080/&MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=.sha256/view", "Hello, world!");
|
||||||
|
|
||||||
|
char url[1024];
|
||||||
|
snprintf(url, sizeof(url), "http://localhost:8080/%s/", app_id);
|
||||||
|
_http_check_body_contains(url, "<title>Tilde Friends</title>");
|
||||||
|
snprintf(url, sizeof(url), "http://localhost:8080/%s/view", app_id);
|
||||||
|
_http_check_body_contains(url, "\"type\":\"tildefriends-app\"");
|
||||||
|
snprintf(url, sizeof(url), "http://localhost:8080/%s/hello.txt", app_id);
|
||||||
|
_http_check_body_contains(url, "Hello, world!");
|
||||||
|
tf_free((void*)app_id);
|
||||||
|
|
||||||
|
uv_process_kill(&process, SIGTERM);
|
||||||
|
uv_close((uv_handle_t*)&process, NULL);
|
||||||
|
uv_run(&loop, UV_RUN_ONCE);
|
||||||
|
|
||||||
|
uv_loop_close(&loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _test_pattern(const tf_test_options_t* options)
|
||||||
|
{
|
||||||
|
assert(tf_http_pattern_matches("/~core/test/", "/~core/test/"));
|
||||||
|
assert(tf_http_pattern_matches("/~core/test/*", "/~core/test/"));
|
||||||
|
assert(tf_http_pattern_matches("/~core/test/*", "/~core/test/blah"));
|
||||||
|
assert(tf_http_pattern_matches("*/~core/test/", "/~core/test/"));
|
||||||
|
assert(tf_http_pattern_matches("*/~core/test/", "blah/~core/test/"));
|
||||||
|
assert(tf_http_pattern_matches("/~*/*/", "/~core/test/"));
|
||||||
|
assert(tf_http_pattern_matches("/~{word}/*", "/~core/test"));
|
||||||
|
assert(tf_http_pattern_matches("/~{word}/{word}/", "/~core/test/"));
|
||||||
|
assert(tf_http_pattern_matches("/~{word}/{word}", "/~core/test"));
|
||||||
|
assert(!tf_http_pattern_matches("/~{word}/{word}", "/~foo/bar/baz"));
|
||||||
|
}
|
||||||
|
|
||||||
static void _test_auto_process_exit(uv_process_t* process, int64_t status, int termination_signal)
|
static void _test_auto_process_exit(uv_process_t* process, int64_t status, int termination_signal)
|
||||||
{
|
{
|
||||||
tf_printf("Process exit %d signal=%d.\n", (int)WEXITSTATUS(status), termination_signal);
|
tf_printf("Process exit %d signal=%d.\n", (int)WEXITSTATUS(status), termination_signal);
|
||||||
@ -887,6 +1023,8 @@ void tf_tests(const tf_test_options_t* options)
|
|||||||
#if !TARGET_OS_IPHONE
|
#if !TARGET_OS_IPHONE
|
||||||
_tf_test_run(options, "bip39", _test_bip39, false);
|
_tf_test_run(options, "bip39", _test_bip39, false);
|
||||||
_tf_test_run(options, "http", _test_http, false);
|
_tf_test_run(options, "http", _test_http, false);
|
||||||
|
_tf_test_run(options, "httpd", _test_httpd, false);
|
||||||
|
_tf_test_run(options, "pattern", _test_pattern, false);
|
||||||
_tf_test_run(options, "ssb", tf_ssb_test_ssb, false);
|
_tf_test_run(options, "ssb", tf_ssb_test_ssb, false);
|
||||||
_tf_test_run(options, "ssb_id", tf_ssb_test_id_conversion, false);
|
_tf_test_run(options, "ssb_id", tf_ssb_test_id_conversion, false);
|
||||||
_tf_test_run(options, "ssb_following", tf_ssb_test_following, false);
|
_tf_test_run(options, "ssb_following", tf_ssb_test_following, false);
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
#include <execinfo.h>
|
#include <execinfo.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define tf_countof(a) ((int)(sizeof((a)) / sizeof(*(a))))
|
||||||
|
|
||||||
static JSValue _util_utf8_encode(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
static JSValue _util_utf8_encode(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||||
{
|
{
|
||||||
size_t length = 0;
|
size_t length = 0;
|
||||||
@ -304,6 +306,66 @@ static JSValue _util_parseHttpResponse(JSContext* context, JSValueConst this_val
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
#include <TargetConditionals.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static bool _is_mobile()
|
||||||
|
{
|
||||||
|
#if defined(__ANDROID__) || (defined(__APPLE__) && TARGET_OS_IPHONE)
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSValue _util_defaultGlobalSettings(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||||
|
{
|
||||||
|
typedef struct _setting_t
|
||||||
|
{
|
||||||
|
const char* name;
|
||||||
|
const char* type;
|
||||||
|
const char* description;
|
||||||
|
JSValue default_value;
|
||||||
|
} setting_t;
|
||||||
|
|
||||||
|
const setting_t k_settings[] = {
|
||||||
|
{ .name = "code_of_conduct", .type = "textarea", .description = "Code of conduct presented at sign-in." },
|
||||||
|
{ .name = "blob_fetch_age_seconds",
|
||||||
|
.type = "integer",
|
||||||
|
.description = "Only blobs mentioned more recently than this age will be automatically fetched.",
|
||||||
|
.default_value = _is_mobile() ? JS_NewInt32(context, (int)(0.5f * 365 * 24 * 60 * 60)) : JS_UNDEFINED },
|
||||||
|
{ .name = "blob_expire_age_seconds",
|
||||||
|
.type = "integer",
|
||||||
|
.description = "Blobs older than this will be automatically deleted.",
|
||||||
|
.default_value = _is_mobile() ? JS_NewInt32(context, (int)(1.0f * 365 * 24 * 60 * 60)) : JS_UNDEFINED },
|
||||||
|
{ .name = "fetch_hosts", .type = "string", .description = "Comma-separated list of host names to which HTTP fetch requests are allowed. None if empty." },
|
||||||
|
{ .name = "http_redirect", .type = "string", .description = "If connecting by HTTP and HTTPS is configured, Location header prefix (ie, \"http://example.com\")" },
|
||||||
|
{ .name = "index", .type = "string", .description = "Default path.", .default_value = JS_NewString(context, "/~core/apps") },
|
||||||
|
{ .name = "index_map", .type = "textarea", .description = "Mappings from hostname to redirect path, one per line, as in: \"www.tildefriends.net=/~core/index/\"" },
|
||||||
|
{ .name = "peer_exchange",
|
||||||
|
.type = "boolean",
|
||||||
|
.description = "Enable discovery of, sharing of, and connecting to internet peer strangers, including announcing this instance.",
|
||||||
|
.default_value = JS_FALSE },
|
||||||
|
{ .name = "replicator", .type = "boolean", .description = "Enable message and blob replication.", .default_value = JS_TRUE },
|
||||||
|
{ .name = "room", .type = "boolean", .description = "Enable peers to tunnel through this instance as a room.", .default_value = JS_TRUE },
|
||||||
|
{ .name = "room_name", .type = "string", .description = "Name of the room.", .default_value = JS_NewString(context, "tilde friends tunnel") },
|
||||||
|
{ .name = "seeds_host", .type = "string", .description = "Hostname for seed connections.", .default_value = JS_NewString(context, "seeds.tildefriends.net") },
|
||||||
|
{ .name = "account_registration", .type = "boolean", .description = "Allow registration of new accounts.", .default_value = JS_TRUE },
|
||||||
|
};
|
||||||
|
|
||||||
|
JSValue settings = JS_NewObject(context);
|
||||||
|
for (int i = 0; i < tf_countof(k_settings); i++)
|
||||||
|
{
|
||||||
|
JSValue entry = JS_NewObject(context);
|
||||||
|
JS_SetPropertyStr(context, entry, "type", JS_NewString(context, k_settings[i].type));
|
||||||
|
JS_SetPropertyStr(context, entry, "description", JS_NewString(context, k_settings[i].description));
|
||||||
|
JS_SetPropertyStr(context, entry, "default_value", k_settings[i].default_value);
|
||||||
|
JS_SetPropertyStr(context, settings, k_settings[i].name, entry);
|
||||||
|
}
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
JSValue tf_util_new_uint8_array(JSContext* context, const uint8_t* data, size_t size)
|
JSValue tf_util_new_uint8_array(JSContext* context, const uint8_t* data, size_t size)
|
||||||
{
|
{
|
||||||
JSValue array_buffer = JS_NewArrayBufferCopy(context, data, size);
|
JSValue array_buffer = JS_NewArrayBufferCopy(context, data, size);
|
||||||
@ -327,6 +389,7 @@ void tf_util_register(JSContext* context)
|
|||||||
JS_SetPropertyStr(context, global, "bip39Bytes", JS_NewCFunction(context, _util_bip39_bytes, "bip39Bytes", 1));
|
JS_SetPropertyStr(context, global, "bip39Bytes", JS_NewCFunction(context, _util_bip39_bytes, "bip39Bytes", 1));
|
||||||
JS_SetPropertyStr(context, global, "print", JS_NewCFunction(context, _util_print, "print", 1));
|
JS_SetPropertyStr(context, global, "print", JS_NewCFunction(context, _util_print, "print", 1));
|
||||||
JS_SetPropertyStr(context, global, "parseHttpResponse", JS_NewCFunction(context, _util_parseHttpResponse, "parseHttpResponse", 2));
|
JS_SetPropertyStr(context, global, "parseHttpResponse", JS_NewCFunction(context, _util_parseHttpResponse, "parseHttpResponse", 2));
|
||||||
|
JS_SetPropertyStr(context, global, "defaultGlobalSettings", JS_NewCFunction(context, _util_defaultGlobalSettings, "defaultGlobalSettings", 2));
|
||||||
JS_FreeValue(context, global);
|
JS_FreeValue(context, global);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
#define VERSION_NUMBER "0.0.24"
|
#define VERSION_NUMBER "0.0.25-wip"
|
||||||
#define VERSION_NAME "Honey bunches of boats."
|
#define VERSION_NAME "Normalize sharing scrappy fiddles."
|
||||||
|
Reference in New Issue
Block a user