25 Commits

Author SHA1 Message Date
5d8d02515d ssb: This query is slightly faster but not fast enough.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m48s
2025-10-02 12:47:57 -04:00
859fe1feb0 ssb: Exclude followed mentions from the general channel, too.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m18s
2025-10-01 20:02:37 -04:00
8f61d83f41 ssb: Exclude subscribed channels from general posts. 2025-10-01 19:52:02 -04:00
6423b3e479 ssb: Consolidate the connection sidebar options. Three copies on-screen is too many.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m47s
2025-10-01 19:12:31 -04:00
2bc8cec8a2 core: Move users()+permissionsForUser() from JS to C. 2025-10-01 18:36:50 -04:00
b49a6cd685 ssb: Place the message collapse better with the messages it collapses.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m8s
2025-10-01 18:06:45 -04:00
2885380f40 core: Remove some unused events.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-10-01 17:55:32 -04:00
2ec3b6a249 http: Add some logging to try to diagnose an intermittent shutdown issue. 2025-10-01 17:48:23 -04:00
3ef795452d ssb: Condense child messages a bit.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m55s
2025-10-01 12:27:04 -04:00
479d87c8b8 update: OpenSSL 3.6.0. 2025-10-01 12:10:07 -04:00
a56077dcc7 update: libbacktrace.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 34m24s
2025-09-30 12:56:51 -04:00
d3f4587c3b update: OpenSSL 3.5.4. 2025-09-30 12:52:22 -04:00
623705b7a1 core: Fix a crash in the apps app.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m36s
2025-09-28 21:15:28 -04:00
8f87f4751d ios: Add iTunesArtwork icon.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m19s
2025-09-28 17:55:36 -04:00
2ac6dfde9d test: Make the testing replicating a blob check the correct client, and add another test that does it while already connected.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-09-28 17:35:54 -04:00
81ade7a400 core: Make the internal ssb.* API more explicitly not exposed to apps.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-09-28 17:19:58 -04:00
63f7ff9f27 update: CodeMirror.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m20s
2025-09-28 16:01:02 -04:00
8a0fa17a79 build: Remove stale include. Fix clean builds.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m56s
2025-09-28 15:23:44 -04:00
0ead5ed967 cleanup: Remove server-side JS socket and HTTP request support. Not used/useful enough to justify keeping all this code around.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 5m6s
2025-09-28 13:43:28 -04:00
53261a6fbc docs: Remove some stale docs from the api app. 2025-09-28 13:27:37 -04:00
c60ff86a4d core: Use FreeBSD's public domain SHA1 instead of OpenSSL's so that jettisoning OpenSSL is an option.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m49s
2025-09-28 13:06:03 -04:00
83a0b017c5 cleanup: Remove the bcrypt JS API. Apps that need it should find their own implementation. Core does it all in C. 2025-09-28 12:48:25 -04:00
3746622a11 ssb: Give channel subscribe/unsubscribe similar grouping treatment to follows/blocks.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m16s
2025-09-27 16:16:34 -04:00
ccd50cf59f welcome: No longer just open testing.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m51s
2025-09-25 10:51:22 -04:00
93680eb43d build: Update nix, and start work on 0.2025.10.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m18s
2025-09-24 19:18:52 -04:00
45 changed files with 1059 additions and 1945 deletions

View File

@@ -910,7 +910,6 @@ INPUT = README.md \
core/app.js \
core/client.js \
core/core.js \
core/http.js \
core/tfrpc.js \
docs/ \
src/

View File

@@ -16,9 +16,9 @@ MAKEFLAGS += --no-builtin-rules
## LD := Linker.
## ANDROID_SDK := Path to the Android SDK.
VERSION_CODE := 43
VERSION_CODE_IOS := 17
VERSION_NUMBER := 0.2025.9
VERSION_CODE := 44
VERSION_CODE_IOS := 18
VERSION_NUMBER := 0.2025.10-wip
VERSION_NAME := This program kills fascists.
IPHONEOS_VERSION_MIN=14.0
@@ -1193,6 +1193,7 @@ out/tildefriends-%.ipa: out/tildefriends-ios%.app/tildefriends
@echo "[ipa] $@"
@rm -rf $@.tmp $@
@mkdir -p $@.tmp/Payload/tildefriends.app/
@cp src/ios/tildefriends512.png $@.tmp/iTunesArtwork
@cp -R $(dir $<)/* $@.tmp/Payload/tildefriends.app/
@cd $@.tmp/ && zip -u ../../$@ -q -9 -r ./
@rm -rf $@.tmp/

View File

@@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "📜",
"previous": "&BEf0nraBdHk/+PWqx6tOSu5rheWVaxaL7orAOz3285M=.sha256"
"previous": "&sJqeyYjHys6Z8IqqtZ2ij2ZC1E2xieu/FU/u2hE+O1U=.sha256"
}

View File

@@ -55,6 +55,9 @@ app.setDocument(`<head>
</head>
<body style="color:#fff">
${markdown(docs.docs.global)}
<!--
${Object.keys(docs.docs).filter(x => [...treeify('', globalThis)].indexOf(x) == -1).map(x => `<p>STALE: ${x}</p>`).join('')}
-->
${[...treeify('', globalThis)].map(x => document(x)).join('\n')}
<a id="Database"></a>
${markdown(docs.docs.database)}

View File

@@ -195,51 +195,6 @@ Call a function after some delay.
* *Number* **timeout** Number of milliseconds to wait before calling the callback function.
`;
docs['parseHttpRequest()'] = `
Parses an HTTP request.
### Parameters
* *Uint8Array* **request** The request data. Maybe be partial or contain extra data. The return value will
indicate when and where it is complete.
* *Number* **last_length** The length of the data passed on a previous attempt for the same request, or 0 initially.
### Returns
* *Integer* **-2** if the request is incomplete.
* *Integer* **-1** if the request could not be parsed.
* *Object* An object with **bytes_parsed**, **minor_version**, **path**, and **headers** fields on successful parse.
`;
docs['parseHttpResponse()'] = `
Parses an HTTP response.
### Parameters
* *Uint8Array* **response** The response data. Maybe be partial or contain extra data. The return value will
indicate when and where it is complete.
* *Number* **last_length** The length of the data passed on a previous attempt for the same response, or 0 initially.
### Returns
* *Integer* **-2** if the response is incomplete.
* *Integer* **-1** if the response could not be parsed.
* *Object* An object with **bytes_parsed**, **minor_version**, **status**, **message**, and **headers** fields on successful parse.
`;
docs['sha1Digest()'] = `
Calculates a SHA1 digest.
Completes synchronously.
### Parameters
* *String* **value** The value for which to calculate the digest.
### Returns
*String* The SHA1 digest of UTF-8 encoded \`value\`.
`;
docs['maskBytes()'] = `
Masks bytes for WebSocket communication.
Completes synchronously.
### Parameters
* *Uint8Array* **bytes** The byte array of data to mask.
* *Uint32* **mask** The mask to apply.
### Returns
*Uint32Array* The masked bytes.
`;
docs['exit()'] = `
Exits the app. But why would you want to do that?

View File

@@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🦀",
"previous": "&IDzjVQjtPyhesUrl45qkZFjzWl0xVlj+2M/XXQRvXO0=.sha256"
"previous": "&t1siEPL4Q0S9k11mGhYOdMwQyLPYARhL+0hsvbDAFgs=.sha256"
}

View File

@@ -24,6 +24,7 @@ class TfMessageElement extends LitElement {
channel: {type: String},
channel_unread: {type: Number},
recent_reactions: {type: Array},
depth: {type: Number},
};
}
@@ -40,6 +41,7 @@ class TfMessageElement extends LitElement {
this.expanded = {};
this.channel_unread = -1;
this.recent_reactions = [];
this.depth = 0;
}
connectedCallback() {
@@ -362,12 +364,13 @@ class TfMessageElement extends LitElement {
</button>
`;
} else {
return html` <div class="w3-container w3-margin-bottom">
${repeat(
this.message.child_messages || [],
(x) => x.id,
(x) =>
html`<tf-message
return html` <ul class="w3-container w3-margin-bottom w3-ul w3-card-4">
${repeat(
this.message.child_messages || [],
(x) => x.id,
(x) =>
html`<li style="padding: 0">
<tf-message
.message=${x}
whoami=${this.whoami}
.users=${this.users}
@@ -376,16 +379,20 @@ class TfMessageElement extends LitElement {
channel=${this.channel}
channel_unread=${this.channel_unread}
.recent_reactions=${this.recent_reactions}
></tf-message>`
)}
</div>
<button
class="w3-button w3-theme-d1 w3-block w3-bar"
style="box-sizing: border-box"
@click=${() => self.set_expanded(false)}
>
Collapse
</button>`;
depth=${this.depth + 1}
></tf-message>
</li>`
)}
<li style="padding: 0" class="w3-margin-bottom">
<button
class="w3-button w3-theme-d1 w3-block w3-bar"
style="box-sizing: border-box"
@click=${() => self.set_expanded(false)}
>
Collapse
</button>
</li>
</ul>`;
}
} else {
return undefined;
@@ -549,7 +556,10 @@ class TfMessageElement extends LitElement {
}
</style>
<div
class="w3-card-4 ${this.class_background()} w3-border-theme w3-margin-top"
class="w3-card-4 ${this.class_background()} w3-border-theme ${this
.depth == 0
? 'w3-margin-top'
: ''}"
style="overflow-wrap: anywhere; display: block; max-width: 100%"
>
${inner}
@@ -576,6 +586,7 @@ class TfMessageElement extends LitElement {
channel=${self.channel}
channel_unread=${self.channel_unread}
.recent_reactions=${self.recent_reactions}
depth=${self.depth + 1}
></tf-message>
`
)}
@@ -644,6 +655,35 @@ class TfMessageElement extends LitElement {
return result;
}
channel_group_by_author() {
let sorted = this.message.messages
.map((x) => [
x.author,
x.content.subscribed ? 'subscribed to' : 'unsubscribed from',
x.content.channel,
x,
])
.sort();
let result = [];
let last;
let group;
for (let row of sorted) {
if (last && last[0] == row[0] && last[1] == row[1]) {
group.push(row[2]);
} else {
if (group) {
result.push({author: last[0], action: last[1], channels: group});
}
last = row;
group = [row[2]];
}
}
if (group) {
result.push({author: last[0], action: last[1], channels: group});
}
return result;
}
allow_unread() {
return (
this.channel == '@' ||
@@ -678,6 +718,7 @@ class TfMessageElement extends LitElement {
.expanded=${this.expanded}
channel=${this.channel}
channel_unread=${this.channel_unread}
depth=${this.depth + 1}
></tf-message>`
)}
</div>
@@ -719,6 +760,56 @@ class TfMessageElement extends LitElement {
</button>
`);
}
} else if (this.message?.type === 'channel_group') {
if (this.expanded[this.expanded_key()]) {
return this.render_frame(html`
<div class="w3-padding">
${this.message.messages.map(
(x) =>
html`<tf-message
.message=${x}
whoami=${this.whoami}
.users=${this.users}
.drafts=${this.drafts}
.expanded=${this.expanded}
channel=${this.channel}
channel_unread=${this.channel_unread}
depth=${this.depth + 1}
></tf-message>`
)}
</div>
<button
class="w3-button w3-theme-d1 w3-block w3-bar"
style="box-sizing: border-box"
@click=${() => self.set_expanded(false)}
>
Collapse
</button>
`);
} else {
return this.render_frame(html`
<div class="w3-padding">
${this.channel_group_by_author().map(
(x) => html`
<div>
<tf-user id=${x.author} .users=${this.users}></tf-user>
${x.action}
${x.channels.map(
(y) => html` <tf-tag tag=${'#' + y}></tf-tag> `
)}
</div>
`
)}
</div>
<button
class="w3-button w3-theme-d1 w3-block w3-bar"
style="box-sizing: border-box"
@click=${() => self.set_expanded(true)}
>
Expand
</button>
`);
}
} else if (this.message.placeholder) {
return this.render_frame(
html`<div>
@@ -764,6 +855,7 @@ class TfMessageElement extends LitElement {
.expanded=${this.expanded}
channel=${this.channel}
channel_unread=${this.channel_unread}
depth=${this.depth + 1}
></tf-message>
`
)}

View File

@@ -160,11 +160,29 @@ class TfNewsElement extends LitElement {
return recursive_sort(roots, true);
}
group_following(messages) {
group_messages(messages) {
let result = [];
let group = [];
let type = undefined;
for (let message of messages) {
if (message?.content?.type === 'contact') {
if (
message?.content?.type === 'contact' ||
message?.content?.type === 'channel'
) {
if (type && message.content.type !== type) {
if (group.length == 1) {
result.push(group[0]);
group = [];
} else if (group.length > 1) {
result.push({
rowid: Math.max(...group.map((x) => x.rowid)),
type: `${type}_group`,
messages: group,
});
group = [];
}
}
type = message.content.type;
group.push(message);
} else {
if (group.length == 1) {
@@ -173,12 +191,13 @@ class TfNewsElement extends LitElement {
} else if (group.length > 1) {
result.push({
rowid: Math.max(...group.map((x) => x.rowid)),
type: 'contact_group',
type: `${type}_group`,
messages: group,
});
group = [];
}
result.push(message);
type = undefined;
}
}
if (group.length == 1) {
@@ -187,7 +206,7 @@ class TfNewsElement extends LitElement {
} else if (group.length > 1) {
result.push({
rowid: Math.max(...group.map((x) => x.rowid)),
type: 'contact_group',
type: `${type}_group`,
messages: group,
});
}
@@ -200,7 +219,7 @@ class TfNewsElement extends LitElement {
load_and_render(messages) {
let messages_by_id = this.process_messages(messages);
let final_messages = this.group_following(
let final_messages = this.group_messages(
this.finalize_messages(messages_by_id)
);
let unread_rowid = -1;

View File

@@ -256,14 +256,23 @@ class TfTabNewsFeedElement extends LitElement {
let t0 = new Date();
let initial_messages = await tfrpc.rpc.query(
`
SELECT TRUE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages
JOIN json_each(?) AS following ON messages.author = following.value
WHERE messages.timestamp < ?3 AND (?2 IS NULL OR messages.timestamp >= ?2) AND
messages.content ->> 'type' != 'vote'
WITH news AS (SELECT TRUE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages
JOIN json_each(?1) AS following ON messages.author = following.value
LEFT OUTER JOIN json_each(?5) AS channels ON messages.content ->> 'channel' = channels.value
WHERE messages.timestamp < ?3 AND (?2 IS NULL OR messages.timestamp >= ?2) AND
messages.content ->> 'type' != 'vote' AND channels.value IS NULL)
SELECT * FROM news
WHERE NOT EXISTS (SELECT * FROM messages_refs JOIN json_each(?5) AS channels ON messages_refs.message = news.id AND messages_refs.ref = '#' || channels.value)
ORDER BY timestamp DESC LIMIT ?4
`,
[JSON.stringify(this.following), start_time, end_time, k_max_results]
[
JSON.stringify(this.following),
start_time,
end_time,
k_max_results,
JSON.stringify(Object.keys(this.channels_latest)),
]
);
let t1 = new Date();
result = await this._fetch_related_messages(initial_messages);
@@ -396,6 +405,9 @@ class TfTabNewsFeedElement extends LitElement {
this._private_messages =
JSON.stringify(this.private_messages) +
JSON.stringify(this.grouped_private_messages);
this._channels_latest = JSON.stringify(
Object.keys(this.channels_latest ?? {})
);
let now = new Date().valueOf();
let start_time = now - 24 * 60 * 60 * 1000;
this.start_time = start_time;
@@ -471,7 +483,9 @@ class TfTabNewsFeedElement extends LitElement {
this._messages_following !== JSON.stringify(this.following) ||
this._private_messages !==
JSON.stringify(this.private_messages) +
JSON.stringify(this.grouped_private_messages)
JSON.stringify(this.grouped_private_messages) ||
this._channels_latest !==
JSON.stringify(Object.keys(this.channels_latest))
) {
console.log(
`loading messages for ${this.whoami} (following ${this.following.length})`

View File

@@ -211,35 +211,6 @@ class TfTabNewsElement extends LitElement {
>
&times;
</div>
${this.is_administrator
? html`
<button
class="w3-bar-item w3-button"
@click=${() =>
this.dispatchEvent(
new Event('refresh', {bubbles: true, composed: true})
)}
>
<span style="display: inline-block; width: 1.8em">↻</span>
Sync now
</button>
<button
class="w3-bar-item w3-button w3-ripple"
@click=${() =>
this.dispatchEvent(
new Event('toggle_stay_connected', {
bubbles: true,
composed: true,
})
)}
>
<span style="display: inline-block; width: 1.8em"
>${this.stay_connected ? '🔗' : '⛓️‍💥'}</span
>
${this.stay_connected ? 'Online mode' : 'Passive mode'}
</button>
`
: undefined}
${this.hash.startsWith('##') &&
this.channels.indexOf(this.hash.substring(2)) == -1
? html`
@@ -334,6 +305,21 @@ class TfTabNewsElement extends LitElement {
>
↻ Sync now
</button>
<button
class="w3-bar-item w3-button w3-ripple"
@click=${() =>
this.dispatchEvent(
new Event('toggle_stay_connected', {
bubbles: true,
composed: true,
})
)}
>
<span style="display: inline-block; width: 1.8em"
>${this.stay_connected ? '🔗' : '⛓️‍💥'}</span
>
${this.stay_connected ? 'Online mode' : 'Passive mode'}
</button>
<button
class=${'w3-bar-item w3-button' +
(this.peer_exchange !== false ? ' w3-hide' : '')}

View File

@@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "👋",
"previous": "&5NkMRSgcMqCYF3xcLOBmaytkoxfV9zx4br7JladKPTs=.sha256"
"previous": "&ijyL/pyTwguBd9njagU7Vpc/1EyRermZuzrlq1mnzbY=.sha256"
}

View File

@@ -104,7 +104,7 @@
src="googleplay.svg"
style="height: 2em; margin: 0"
/>
Get it on Google Play (Open Testing)
Get it on Google Play
</a>
<a
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
@@ -298,7 +298,7 @@
<!-- Technlology Section -->
<div class="w3-container w3-padding-64 w3-light-grey w3-center">
<h1 class="w3-jumbo"><b>Built the Old Fashioned Way</b></h1>
<h1 class="w3-jumbo"><b>Built to Last</b></h1>
<p>
Tilde Friends strives to use only simple and widely adopted dependencies
in order to keep it easy to build for all sorts of platforms and

View File

@@ -149,7 +149,7 @@ exports.app_socket = async function socket(request, response) {
parentApp: parentApp,
id: blobId,
},
await ssb.getIdentityInfo(
await ssb_internal.getIdentityInfo(
credentials?.session?.name,
packageOwner,
packageName

View File

@@ -7,7 +7,6 @@
/** \cond */
import * as app from './app.js';
import * as http from './http.js';
export {invoke, getProcessBlob};
/** \endcond */
@@ -192,7 +191,6 @@ async function getProcessBlob(blobId, key, options) {
});
gProcesses[key] = process;
process.task.onExit = function (exitCode, terminationSignal) {
broadcastEvent('onSessionEnd', [getUser(process, process)]);
process.task = null;
delete gProcesses[key];
};
@@ -200,13 +198,6 @@ async function getProcessBlob(blobId, key, options) {
core: {
broadcast: broadcast.bind(process),
user: getUser(process, process),
users: async function () {
try {
return JSON.parse(await new Database('auth').get('users'));
} catch {
return [];
}
},
permissionsGranted: async function () {
let user = process?.credentials?.session?.name;
let settings = await loadSettings();
@@ -236,11 +227,6 @@ async function getProcessBlob(blobId, key, options) {
return settings.userPermissions[user];
}
},
permissionsForUser: async function (user) {
let settings = await loadSettings();
return settings?.permissions?.[user] ?? [];
},
getSockets: getSockets,
permissionTest: async function (permission) {
let user = process?.credentials?.session?.name;
let settings = await loadSettings();
@@ -314,7 +300,7 @@ async function getProcessBlob(blobId, key, options) {
{
action: 'identities',
},
await ssb.getIdentityInfo(
await ssb_internal.getIdentityInfo(
process?.credentials?.session?.name,
options?.packageOwner,
options?.packageName
@@ -553,13 +539,6 @@ async function getProcessBlob(blobId, key, options) {
);
}
};
imports.ssb.addEventListener = undefined;
imports.ssb.removeEventListener = undefined;
imports.ssb.getIdentityInfo = undefined;
imports.fetch = async function (url, options) {
let settings = await loadSettings();
return http.fetch(url, options, settings?.fetch_hosts);
};
if (
process.credentials &&
@@ -663,7 +642,6 @@ async function getProcessBlob(blobId, key, options) {
} catch (e) {
printError(e);
}
broadcastEvent('onSessionBegin', [getUser(process, process)]);
if (process.app) {
process.app.send({action: 'ready', version: version()});
await process.sendPermissions();
@@ -689,19 +667,19 @@ async function getProcessBlob(blobId, key, options) {
/**
* SSB message added callback.
*/
ssb.addEventListener('message', function () {
ssb_internal.addEventListener('message', function () {
broadcastEvent('onMessage', [...arguments]);
});
ssb.addEventListener('blob', function () {
ssb_internal.addEventListener('blob', function () {
broadcastEvent('onBlob', [...arguments]);
});
ssb.addEventListener('broadcasts', function () {
ssb_internal.addEventListener('broadcasts', function () {
broadcastEvent('onBroadcastsChanged', []);
});
ssb.addEventListener('connections', function () {
ssb_internal.addEventListener('connections', function () {
broadcastEvent('onConnectionsChanged', []);
});

View File

@@ -1,121 +0,0 @@
/**
* \file
* \defgroup tfhttp Tilde Friends HTTP Client JS
* Tilde Friends server-side HTTP client.
* @{
*/
/**
* Parse a URL into protocol, host, path, and port parts.
* @param url
* @return An object of the URL parts.
*/
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,
};
}
/**
* Parse an HTTP response into headers and body content.
* @param data The response data, headers and body included.
* @return headers and body data.
*/
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 {headers: headers, body: data};
}
/**
* Make an HTTP request.
* @param url The URL.
* @param options Request options.
* @param allowed_hosts List of allowed hosts.
* @return A promise resolved with the response headers and body.
*/
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);
});
});
}
/** @} */

View File

@@ -25,14 +25,14 @@
}:
pkgs.stdenv.mkDerivation rec {
pname = "tildefriends";
version = "0.2025.8";
version = "0.2025.9";
src = pkgs.fetchFromGitea {
domain = "dev.tildefriends.net";
owner = "cory";
repo = "tildefriends";
rev = "v${version}";
hash = "sha256-N/5lp8RL19B6Z43kRxx7c01WVJkK44a/wwNgRJPk5uI=";
hash = "sha256-1nhsfhdOO5HIiiTMb+uROB8nDPL/UpOYm52hZ/OpPyk=";
fetchSubmodules = true;
};

File diff suppressed because one or more lines are too long

200
deps/codemirror_src/package-lock.json generated vendored
View File

@@ -19,9 +19,9 @@
}
},
"node_modules/@codemirror/autocomplete": {
"version": "6.18.7",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.7.tgz",
"integrity": "sha512-8EzdeIoWPJDsMBwz3zdzwXnUpCzMiCyz5/A3FIPpriaclFCGDkAzK13sMcnsu5rowqiyeQN2Vs2TsOcoDPZirQ==",
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.19.0.tgz",
"integrity": "sha512-61Hfv3cF07XvUxNeC3E7jhG8XNi1Yom1G0lRC936oLnlF+jrbrv8rc/J98XlYzcsAoTVupfsf5fLej1aI8kyIg==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
@@ -155,9 +155,9 @@
}
},
"node_modules/@codemirror/view": {
"version": "6.38.3",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.3.tgz",
"integrity": "sha512-x2t87+oqwB1mduiQZ6huIghjMt4uZKFEdj66IcXw7+a5iBEvv9lh7EWDRHI7crnD4BMGpnyq/RzmCGbiEZLcvQ==",
"version": "6.38.4",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.4.tgz",
"integrity": "sha512-hduz0suCcUSC/kM8Fq3A9iLwInJDl8fD1xLpTIk+5xkNm8z/FT7UsIa9sOXrkpChh+XXc18RzswE8QqELsVl+g==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.5.0",
@@ -243,9 +243,9 @@
}
},
"node_modules/@lezer/html": {
"version": "1.3.10",
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz",
"integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==",
"version": "1.3.12",
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.12.tgz",
"integrity": "sha512-RJ7eRWdaJe3bsiiLLHjCFT1JMk8m1YP9kaUbvu2rMLEoOnke9mcTVDyfOslsln0LtujdWespjJ39w6zo+RsQYw==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
@@ -360,9 +360,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.2.tgz",
"integrity": "sha512-o3pcKzJgSGt4d74lSZ+OCnHwkKBeAbFDmbEm5gg70eA8VkyCuC/zV9TwBnmw6VjDlRdF4Pshfb+WE9E6XY1PoQ==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz",
"integrity": "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==",
"cpu": [
"arm"
],
@@ -373,9 +373,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.2.tgz",
"integrity": "sha512-cqFSWO5tX2vhC9hJTK8WAiPIm4Q8q/cU8j2HQA0L3E1uXvBYbOZMhE2oFL8n2pKB5sOCHY6bBuHaRwG7TkfJyw==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.3.tgz",
"integrity": "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==",
"cpu": [
"arm64"
],
@@ -386,9 +386,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.2.tgz",
"integrity": "sha512-vngduywkkv8Fkh3wIZf5nFPXzWsNsVu1kvtLETWxTFf/5opZmflgVSeLgdHR56RQh71xhPhWoOkEBvbehwTlVA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.3.tgz",
"integrity": "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==",
"cpu": [
"arm64"
],
@@ -399,9 +399,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.2.tgz",
"integrity": "sha512-h11KikYrUCYTrDj6h939hhMNlqU2fo/X4NB0OZcys3fya49o1hmFaczAiJWVAFgrM1NCP6RrO7lQKeVYSKBPSQ==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.3.tgz",
"integrity": "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==",
"cpu": [
"x64"
],
@@ -412,9 +412,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.2.tgz",
"integrity": "sha512-/eg4CI61ZUkLXxMHyVlmlGrSQZ34xqWlZNW43IAU4RmdzWEx0mQJ2mN/Cx4IHLVZFL6UBGAh+/GXhgvGb+nVxw==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.3.tgz",
"integrity": "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==",
"cpu": [
"arm64"
],
@@ -425,9 +425,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.2.tgz",
"integrity": "sha512-QOWgFH5X9+p+S1NAfOqc0z8qEpJIoUHf7OWjNUGOeW18Mx22lAUOiA9b6r2/vpzLdfxi/f+VWsYjUOMCcYh0Ng==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.3.tgz",
"integrity": "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==",
"cpu": [
"x64"
],
@@ -438,9 +438,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.2.tgz",
"integrity": "sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.3.tgz",
"integrity": "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==",
"cpu": [
"arm"
],
@@ -451,9 +451,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.2.tgz",
"integrity": "sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.3.tgz",
"integrity": "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==",
"cpu": [
"arm"
],
@@ -464,9 +464,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.2.tgz",
"integrity": "sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.3.tgz",
"integrity": "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==",
"cpu": [
"arm64"
],
@@ -477,9 +477,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.2.tgz",
"integrity": "sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.3.tgz",
"integrity": "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==",
"cpu": [
"arm64"
],
@@ -490,9 +490,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.2.tgz",
"integrity": "sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.3.tgz",
"integrity": "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==",
"cpu": [
"loong64"
],
@@ -503,9 +503,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.2.tgz",
"integrity": "sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.3.tgz",
"integrity": "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==",
"cpu": [
"ppc64"
],
@@ -516,9 +516,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.2.tgz",
"integrity": "sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.3.tgz",
"integrity": "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==",
"cpu": [
"riscv64"
],
@@ -529,9 +529,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.2.tgz",
"integrity": "sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.3.tgz",
"integrity": "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==",
"cpu": [
"riscv64"
],
@@ -542,9 +542,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.2.tgz",
"integrity": "sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.3.tgz",
"integrity": "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==",
"cpu": [
"s390x"
],
@@ -555,9 +555,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.2.tgz",
"integrity": "sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.3.tgz",
"integrity": "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==",
"cpu": [
"x64"
],
@@ -568,9 +568,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.2.tgz",
"integrity": "sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.3.tgz",
"integrity": "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==",
"cpu": [
"x64"
],
@@ -581,9 +581,9 @@
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.2.tgz",
"integrity": "sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.3.tgz",
"integrity": "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==",
"cpu": [
"arm64"
],
@@ -594,9 +594,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.2.tgz",
"integrity": "sha512-Z9MUCrSgIaUeeHAiNkm3cQyst2UhzjPraR3gYYfOjAuZI7tcFRTOD+4cHLPoS/3qinchth+V56vtqz1Tv+6KPA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.3.tgz",
"integrity": "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==",
"cpu": [
"arm64"
],
@@ -607,9 +607,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.2.tgz",
"integrity": "sha512-+GnYBmpjldD3XQd+HMejo+0gJGwYIOfFeoBQv32xF/RUIvccUz20/V6Otdv+57NE70D5pa8W/jVGDoGq0oON4A==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.3.tgz",
"integrity": "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==",
"cpu": [
"ia32"
],
@@ -620,9 +620,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.2.tgz",
"integrity": "sha512-ApXFKluSB6kDQkAqZOKXBjiaqdF1BlKi+/eqnYe9Ee7U2K3pUDKsIyr8EYm/QDHTJIM+4X+lI0gJc3TTRhd+dA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.3.tgz",
"integrity": "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==",
"cpu": [
"x64"
],
@@ -633,9 +633,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.2.tgz",
"integrity": "sha512-ARz+Bs8kY6FtitYM96PqPEVvPXqEZmPZsSkXvyX19YzDqkCaIlhCieLLMI5hxO9SRZ2XtCtm8wxhy0iJ2jxNfw==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.3.tgz",
"integrity": "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==",
"cpu": [
"x64"
],
@@ -825,9 +825,9 @@
}
},
"node_modules/rollup": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz",
"integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.3.tgz",
"integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==",
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.8"
@@ -840,28 +840,28 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.52.2",
"@rollup/rollup-android-arm64": "4.52.2",
"@rollup/rollup-darwin-arm64": "4.52.2",
"@rollup/rollup-darwin-x64": "4.52.2",
"@rollup/rollup-freebsd-arm64": "4.52.2",
"@rollup/rollup-freebsd-x64": "4.52.2",
"@rollup/rollup-linux-arm-gnueabihf": "4.52.2",
"@rollup/rollup-linux-arm-musleabihf": "4.52.2",
"@rollup/rollup-linux-arm64-gnu": "4.52.2",
"@rollup/rollup-linux-arm64-musl": "4.52.2",
"@rollup/rollup-linux-loong64-gnu": "4.52.2",
"@rollup/rollup-linux-ppc64-gnu": "4.52.2",
"@rollup/rollup-linux-riscv64-gnu": "4.52.2",
"@rollup/rollup-linux-riscv64-musl": "4.52.2",
"@rollup/rollup-linux-s390x-gnu": "4.52.2",
"@rollup/rollup-linux-x64-gnu": "4.52.2",
"@rollup/rollup-linux-x64-musl": "4.52.2",
"@rollup/rollup-openharmony-arm64": "4.52.2",
"@rollup/rollup-win32-arm64-msvc": "4.52.2",
"@rollup/rollup-win32-ia32-msvc": "4.52.2",
"@rollup/rollup-win32-x64-gnu": "4.52.2",
"@rollup/rollup-win32-x64-msvc": "4.52.2",
"@rollup/rollup-android-arm-eabi": "4.52.3",
"@rollup/rollup-android-arm64": "4.52.3",
"@rollup/rollup-darwin-arm64": "4.52.3",
"@rollup/rollup-darwin-x64": "4.52.3",
"@rollup/rollup-freebsd-arm64": "4.52.3",
"@rollup/rollup-freebsd-x64": "4.52.3",
"@rollup/rollup-linux-arm-gnueabihf": "4.52.3",
"@rollup/rollup-linux-arm-musleabihf": "4.52.3",
"@rollup/rollup-linux-arm64-gnu": "4.52.3",
"@rollup/rollup-linux-arm64-musl": "4.52.3",
"@rollup/rollup-linux-loong64-gnu": "4.52.3",
"@rollup/rollup-linux-ppc64-gnu": "4.52.3",
"@rollup/rollup-linux-riscv64-gnu": "4.52.3",
"@rollup/rollup-linux-riscv64-musl": "4.52.3",
"@rollup/rollup-linux-s390x-gnu": "4.52.3",
"@rollup/rollup-linux-x64-gnu": "4.52.3",
"@rollup/rollup-linux-x64-musl": "4.52.3",
"@rollup/rollup-openharmony-arm64": "4.52.3",
"@rollup/rollup-win32-arm64-msvc": "4.52.3",
"@rollup/rollup-win32-ia32-msvc": "4.52.3",
"@rollup/rollup-win32-x64-gnu": "4.52.3",
"@rollup/rollup-win32-x64-msvc": "4.52.3",
"fsevents": "~2.3.2"
}
},

View File

@@ -45,7 +45,6 @@ options:
out_http_port_file (default: ""): File to which to write bound HTTP port.
blob_fetch_age_seconds (default: -1): Only blobs mentioned more recently than this age will be automatically fetched.
blob_expire_age_seconds (default: -1): Blobs older than this will be automatically deleted.
fetch_hosts (default: ""): Comma-separated list of host names to which HTTP fetch requests are allowed. None if empty.
http_redirect (default: ""): If connecting by HTTP and HTTPS is configured, Location header prefix (ie, "http://example.com")
index (default: "/~core/intro/"): Default path.
index_map (default: ""): Mappings from hostname to redirect path, one per line, as in: "www.tildefriends.net=/~core/index/"

6
flake.lock generated
View File

@@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1756217674,
"narHash": "sha256-TH1SfSP523QI7kcPiNtMAEuwZR3Jdz0MCDXPs7TS8uo=",
"lastModified": 1758589230,
"narHash": "sha256-zMTCFGe8aVGTEr2RqUi/QzC1nOIQ0N1HRsbqB4f646k=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4e7667a90c167f7a81d906e5a75cba4ad8bee620",
"rev": "d1d883129b193f0b495d75c148c2c3a7d95789a0",
"type": "github"
},
"original": {

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.unprompted.tildefriends"
android:versionCode="43"
android:versionName="0.2025.9">
android:versionCode="44"
android:versionName="0.2025.10-wip">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application

View File

@@ -74,7 +74,7 @@ static void _tf_api_core_apps_after_work(tf_ssb_t* ssb, int status, void* user_d
JSValue result = JS_NewObject(context);
for (int i = 0; i < work->count; i++)
{
JS_SetPropertyStr(context, result, work->apps[i].app, JS_NewString(context, work->apps[i].path));
JS_SetPropertyStr(context, result, work->apps[i].app, JS_NewString(context, work->apps[i].path ? work->apps[i].path : ""));
}
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
tf_util_report_error(context, error);
@@ -147,6 +147,55 @@ static JSValue _tf_api_core_apps(JSContext* context, JSValueConst this_val, int
return result;
}
typedef struct _users_t
{
const char* users;
JSContext* context;
JSValue promise[2];
} users_t;
static void _tf_api_core_users_work(tf_ssb_t* ssb, void* user_data)
{
users_t* work = user_data;
work->users = tf_ssb_db_get_property(ssb, "auth", "users");
}
static void _tf_api_core_users_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
users_t* work = user_data;
JSContext* context = work->context;
JSValue result = JS_UNDEFINED;
if (work->users)
{
result = JS_ParseJSON(context, work->users, strlen(work->users), NULL);
tf_free((void*)work->users);
}
if (JS_IsUndefined(result))
{
result = JS_NewArray(context);
}
JSValue error = JS_Call(context, JS_IsArray(context, result) ? work->promise[0] : work->promise[1], JS_UNDEFINED, 1, &result);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
JS_FreeValue(context, result);
JS_FreeValue(context, work->promise[0]);
JS_FreeValue(context, work->promise[1]);
tf_free(work);
}
static JSValue _tf_api_core_users(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
{
tf_task_t* task = tf_task_get(context);
tf_ssb_t* ssb = tf_task_get_ssb(task);
users_t* work = tf_malloc(sizeof(users_t));
*work = (users_t) {
.context = context,
};
JSValue result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(ssb, _tf_api_core_users_work, _tf_api_core_users_after_work, work);
return result;
}
static JSValue _tf_api_core_register(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
{
JSValue event_name = argv[0];
@@ -210,6 +259,68 @@ static JSValue _tf_api_core_unregister(JSContext* context, JSValueConst this_val
return JS_UNDEFINED;
}
typedef struct _permissions_for_user_t
{
const char* user;
const char* settings;
JSContext* context;
JSValue promise[2];
} permissions_for_user_t;
static void _tf_api_core_permissions_for_user_work(tf_ssb_t* ssb, void* user_data)
{
permissions_for_user_t* work = user_data;
work->settings = tf_ssb_db_get_property(ssb, "core", "settings");
}
static void _tf_api_core_permissions_for_user_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
permissions_for_user_t* work = user_data;
JSContext* context = work->context;
JSValue result = JS_UNDEFINED;
if (work->settings)
{
JSValue json = JS_ParseJSON(context, work->settings, strlen(work->settings), NULL);
if (JS_IsObject(json))
{
JSValue permissions = JS_GetPropertyStr(context, json, "permissions");
if (JS_IsObject(permissions))
{
result = JS_GetPropertyStr(context, permissions, work->user);
}
JS_FreeValue(context, permissions);
}
JS_FreeValue(context, json);
tf_free((void*)work->settings);
}
if (JS_IsUndefined(result))
{
result = JS_NewArray(context);
}
JSValue error = JS_Call(context, JS_IsArray(context, result) ? work->promise[0] : work->promise[1], JS_UNDEFINED, 1, &result);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
JS_FreeValue(context, result);
JS_FreeValue(context, work->promise[0]);
JS_FreeValue(context, work->promise[1]);
JS_FreeCString(context, work->user);
tf_free(work);
}
static JSValue _tf_api_core_permissionsForUser(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
{
tf_task_t* task = tf_task_get(context);
tf_ssb_t* ssb = tf_task_get_ssb(task);
permissions_for_user_t* work = tf_malloc(sizeof(permissions_for_user_t));
*work = (permissions_for_user_t) {
.context = context,
.user = JS_ToCString(context, argv[0]),
};
JSValue result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(ssb, _tf_api_core_permissions_for_user_work, _tf_api_core_permissions_for_user_after_work, work);
return result;
}
static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue imports = argv[0];
@@ -218,6 +329,10 @@ static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_va
JS_SetPropertyStr(context, core, "apps", JS_NewCFunctionData(context, _tf_api_core_apps, 1, 0, 1, &process));
JS_SetPropertyStr(context, core, "register", JS_NewCFunctionData(context, _tf_api_core_register, 2, 0, 1, &process));
JS_SetPropertyStr(context, core, "unregister", JS_NewCFunctionData(context, _tf_api_core_unregister, 2, 0, 1, &process));
JS_SetPropertyStr(context, core, "users", JS_NewCFunctionData(context, _tf_api_core_users, 0, 0, 1, &process));
JS_SetPropertyStr(context, core, "permissionsForUser", JS_NewCFunctionData(context, _tf_api_core_permissionsForUser, 1, 0, 1, &process));
JS_FreeValue(context, core);
return JS_UNDEFINED;
}

View File

@@ -1,42 +0,0 @@
#include "bcrypt.js.h"
#include "task.h"
#include "ow-crypt.h"
#include "quickjs.h"
#include "uv.h"
static JSValue _crypt_hashpw(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
const char* key = JS_ToCString(context, argv[0]);
const char* salt = JS_ToCString(context, argv[1]);
char output[7 + 22 + 31 + 1];
char* hash = crypt_rn(key, salt, output, sizeof(output));
JSValue result = JS_NewString(context, hash);
JS_FreeCString(context, key);
JS_FreeCString(context, salt);
return result;
}
static JSValue _crypt_gensalt(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
int length = 0;
JS_ToInt32(context, &length, argv[0]);
char buffer[16];
tf_task_t* task = tf_task_get(context);
size_t bytes = uv_random(tf_task_get_loop(task), &(uv_random_t) { 0 }, buffer, sizeof(buffer), 0, NULL) == 0 ? sizeof(buffer) : 0;
char output[7 + 22 + 1];
char* salt = crypt_gensalt_rn("$2b$", length, buffer, bytes, output, sizeof(output));
JSValue result = JS_NewString(context, salt);
return result;
}
void tf_bcrypt_register(JSContext* context)
{
JSValue global = JS_GetGlobalObject(context);
JSValue bcrypt = JS_NewObject(context);
JS_SetPropertyStr(context, global, "bCrypt", bcrypt);
JS_SetPropertyStr(context, bcrypt, "hashpw", JS_NewCFunction(context, _crypt_hashpw, "hashpw", 2));
JS_SetPropertyStr(context, bcrypt, "gensalt", JS_NewCFunction(context, _crypt_gensalt, "gensalt", 1));
JS_FreeValue(context, global);
}

View File

@@ -1,19 +0,0 @@
#pragma once
/**
** \defgroup bcrypt_js bCrypt
** Exposes bcrypt to script, where it is used for hashing and verifying
** passwords.
** @{
*/
/** A JS context. */
typedef struct JSContext JSContext;
/**
** Register the bcrypt script interface.
** @param context The JS context.
*/
void tf_bcrypt_register(JSContext* context);
/** @} */

View File

@@ -20,6 +20,9 @@
static const int k_timeout_ms = 60000;
static tf_http_t** s_http_instances;
int s_http_instance_count;
typedef struct _tf_http_connection_t
{
tf_http_t* http;
@@ -115,6 +118,8 @@ tf_http_t* tf_http_create(uv_loop_t* loop)
*http = (tf_http_t) {
.loop = loop,
};
s_http_instances = tf_resize_vec(s_http_instances, sizeof(tf_http_t*) * (s_http_instance_count + 1));
s_http_instances[s_http_instance_count++] = http;
return http;
}
@@ -872,6 +877,21 @@ void tf_http_destroy(tf_http_t* http)
http->handlers_count = 0;
tf_free(http);
for (int i = 0; i < s_http_instance_count; i++)
{
if (s_http_instances[i] == http)
{
s_http_instances[i] = s_http_instances[--s_http_instance_count];
break;
}
}
if (s_http_instance_count == 0)
{
tf_free(s_http_instances);
s_http_instances = NULL;
}
tf_printf("http %p destroyed\n", http);
}
else
{
@@ -1215,3 +1235,19 @@ const char* tf_http_get_cookie(const char* cookie_header, const char* name)
}
return NULL;
}
void tf_http_debug_destroy()
{
for (int i = 0; i < s_http_instance_count; i++)
{
tf_http_t* http = s_http_instances[i];
tf_printf("http %p[%d]\n", http, i);
tf_printf(" connections = %d\n", http->connections_count);
for (int j = 0; j < http->connections_count; j++)
{
tf_http_connection_t* connection = http->connections[j];
tf_printf(" connection %p[%d] %s tcp=%p timeout=%p shutdown=%p rc=%d\n", connection, j, connection->trace_name, connection->tcp.data, connection->timeout.data,
connection->shutdown.data, connection->ref_count);
}
}
}

View File

@@ -238,4 +238,9 @@ const char* tf_http_status_text(int status);
*/
bool tf_http_pattern_matches(const char* pattern, const char* path);
/**
** Log debug information to diagnose shutdown problems.
*/
void tf_http_debug_destroy();
/** @} */

View File

@@ -4,6 +4,7 @@
#include "http.h"
#include "log.h"
#include "mem.h"
#include "sha1.h"
#include "ssb.db.h"
#include "task.h"
#include "tls.h"
@@ -14,8 +15,6 @@
#include "sodium/crypto_sign.h"
#include "sodium/utils.h"
#include <openssl/sha.h>
#define CYAN "\e[1;36m"
#define MAGENTA "\e[1;35m"
#define YELLOW "\e[1;33m"
@@ -169,8 +168,13 @@ static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_va
uint8_t* key_magic = alloca(size);
memcpy(key_magic, header_sec_websocket_key, key_length);
memcpy(key_magic + key_length, k_magic, 36);
uint8_t digest[20];
SHA1(key_magic, size, digest);
SHA1_CTX sha1 = { 0 };
SHA1Init(&sha1);
SHA1Update(&sha1, key_magic, size);
SHA1Final(digest, &sha1);
char key[41] = { 0 };
tf_base64_encode(digest, sizeof(digest), key, sizeof(key));

View File

@@ -13,13 +13,13 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.2025.9</string>
<string>0.2025.10</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>iPhoneOS</string>
</array>
<key>CFBundleVersion</key>
<string>17</string>
<string>18</string>
<key>DTPlatformName</key>
<string>iphoneos</string>
<key>LSRequiresIPhoneOS</key>

BIN
src/ios/tildefriends512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

329
src/sha1.c Normal file
View File

@@ -0,0 +1,329 @@
/*
* SHA1 hash implementation and interface functions
* Copyright (c) 2003-2005, Jouni Malinen <j@w1.fi>
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#include "sha1.h"
#include <stddef.h>
#include <string.h>
/* ===== start - public domain SHA1 implementation ===== */
/*
SHA-1 in C
By Steve Reid <sreid@sea-to-sky.net>
100% Public Domain
-----------------
Modified 7/98
By James H. Brown <jbrown@burgoyne.com>
Still 100% Public Domain
Corrected a problem which generated improper hash values on 16 bit machines
Routine SHA1Update changed from
void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int
len)
to
void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned
long len)
The 'len' parameter was declared an int which works fine on 32 bit machines.
However, on 16 bit machines an int is too small for the shifts being done
against it. This caused the hash function to generate incorrect values if len
was greater than 8191 (8K - 1) due to the 'len << 3' on line 3 of SHA1Update().
Since the file IO in main() reads 16K at a time, any file 8K or larger would be
guaranteed to generate the wrong hash (e.g. Test Vector #3, a million "a"s).
I also changed the declaration of variables i & j in SHA1Update to unsigned
long from unsigned int for the same reason.
These changes should make no difference to any 32 bit implementations since an
int and a long are the same size in those environments.
--
I also corrected a few compiler warnings generated by Borland C.
1. Added #include <process.h> for exit() prototype
2. Removed unused variable 'j' in SHA1Final
3. Changed exit(0) to return(0) at end of main.
ALL changes I made can be located by searching for comments containing 'JHB'
-----------------
Modified 8/98
By Steve Reid <sreid@sea-to-sky.net>
Still 100% public domain
1- Removed #include <process.h> and used return() instead of exit()
2- Fixed overwriting of finalcount in SHA1Final() (discovered by Chris Hall)
3- Changed email address from steve@edmweb.com to sreid@sea-to-sky.net
-----------------
Modified 4/01
By Saul Kravitz <Saul.Kravitz@celera.com>
Still 100% PD
Modified to run on Compaq Alpha hardware.
-----------------
Modified 4/01
By Jouni Malinen <j@w1.fi>
Minor changes to match the coding style used in Dynamics.
Modified September 24, 2004
By Jouni Malinen <j@w1.fi>
Fixed alignment issue in SHA1Transform when SHA1HANDSOFF is defined.
-----------------
Modified September 29, 2025
By Cory McWilliams <cory@tildefriends.net>
Adapted from
https://web.mit.edu/freebsd/head/contrib/wpa/src/crypto/sha1-internal.c.
Modified to build outside of FreeBSD. Updated with clang-format.
*/
/*
Test Vectors (from FIPS PUB 180-1)
"abc"
A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
A million repetitions of "a"
34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
*/
#define SHA1HANDSOFF
#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
/* blk0() and blk() perform the initial expand. */
/* I got the idea of expanding during the round function from SSLeay */
#ifndef WORDS_BIGENDIAN
#define blk0(i) (block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) | (rol(block->l[i], 8) & 0x00FF00FF))
#else
#define blk0(i) block->l[i]
#endif
#define blk(i) (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ block->l[(i + 2) & 15] ^ block->l[i & 15], 1))
/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
#define R0(v, w, x, y, z, i) \
z += ((w & (x ^ y)) ^ y) + blk0(i) + 0x5A827999 + rol(v, 5); \
w = rol(w, 30);
#define R1(v, w, x, y, z, i) \
z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
w = rol(w, 30);
#define R2(v, w, x, y, z, i) \
z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \
w = rol(w, 30);
#define R3(v, w, x, y, z, i) \
z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \
w = rol(w, 30);
#define R4(v, w, x, y, z, i) \
z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
w = rol(w, 30);
#ifdef VERBOSE /* SAK */
void SHAPrintContext(SHA1_CTX* context, char* msg)
{
printf("%s (%d,%d) %x %x %x %x %x\n", msg, context->count[0], context->count[1], context->state[0], context->state[1], context->state[2], context->state[3], context->state[4]);
}
#endif
/* Hash a single 512-bit block. This is the core of the algorithm. */
void SHA1Transform(uint32_t state[5], const unsigned char buffer[64])
{
uint32_t a, b, c, d, e;
typedef union
{
unsigned char c[64];
uint32_t l[16];
} CHAR64LONG16;
CHAR64LONG16* block;
#ifdef SHA1HANDSOFF
CHAR64LONG16 workspace;
block = &workspace;
memcpy(block, buffer, 64);
#else
block = (CHAR64LONG16*)buffer;
#endif
/* Copy context->state[] to working vars */
a = state[0];
b = state[1];
c = state[2];
d = state[3];
e = state[4];
/* 4 rounds of 20 operations each. Loop unrolled. */
R0(a, b, c, d, e, 0);
R0(e, a, b, c, d, 1);
R0(d, e, a, b, c, 2);
R0(c, d, e, a, b, 3);
R0(b, c, d, e, a, 4);
R0(a, b, c, d, e, 5);
R0(e, a, b, c, d, 6);
R0(d, e, a, b, c, 7);
R0(c, d, e, a, b, 8);
R0(b, c, d, e, a, 9);
R0(a, b, c, d, e, 10);
R0(e, a, b, c, d, 11);
R0(d, e, a, b, c, 12);
R0(c, d, e, a, b, 13);
R0(b, c, d, e, a, 14);
R0(a, b, c, d, e, 15);
R1(e, a, b, c, d, 16);
R1(d, e, a, b, c, 17);
R1(c, d, e, a, b, 18);
R1(b, c, d, e, a, 19);
R2(a, b, c, d, e, 20);
R2(e, a, b, c, d, 21);
R2(d, e, a, b, c, 22);
R2(c, d, e, a, b, 23);
R2(b, c, d, e, a, 24);
R2(a, b, c, d, e, 25);
R2(e, a, b, c, d, 26);
R2(d, e, a, b, c, 27);
R2(c, d, e, a, b, 28);
R2(b, c, d, e, a, 29);
R2(a, b, c, d, e, 30);
R2(e, a, b, c, d, 31);
R2(d, e, a, b, c, 32);
R2(c, d, e, a, b, 33);
R2(b, c, d, e, a, 34);
R2(a, b, c, d, e, 35);
R2(e, a, b, c, d, 36);
R2(d, e, a, b, c, 37);
R2(c, d, e, a, b, 38);
R2(b, c, d, e, a, 39);
R3(a, b, c, d, e, 40);
R3(e, a, b, c, d, 41);
R3(d, e, a, b, c, 42);
R3(c, d, e, a, b, 43);
R3(b, c, d, e, a, 44);
R3(a, b, c, d, e, 45);
R3(e, a, b, c, d, 46);
R3(d, e, a, b, c, 47);
R3(c, d, e, a, b, 48);
R3(b, c, d, e, a, 49);
R3(a, b, c, d, e, 50);
R3(e, a, b, c, d, 51);
R3(d, e, a, b, c, 52);
R3(c, d, e, a, b, 53);
R3(b, c, d, e, a, 54);
R3(a, b, c, d, e, 55);
R3(e, a, b, c, d, 56);
R3(d, e, a, b, c, 57);
R3(c, d, e, a, b, 58);
R3(b, c, d, e, a, 59);
R4(a, b, c, d, e, 60);
R4(e, a, b, c, d, 61);
R4(d, e, a, b, c, 62);
R4(c, d, e, a, b, 63);
R4(b, c, d, e, a, 64);
R4(a, b, c, d, e, 65);
R4(e, a, b, c, d, 66);
R4(d, e, a, b, c, 67);
R4(c, d, e, a, b, 68);
R4(b, c, d, e, a, 69);
R4(a, b, c, d, e, 70);
R4(e, a, b, c, d, 71);
R4(d, e, a, b, c, 72);
R4(c, d, e, a, b, 73);
R4(b, c, d, e, a, 74);
R4(a, b, c, d, e, 75);
R4(e, a, b, c, d, 76);
R4(d, e, a, b, c, 77);
R4(c, d, e, a, b, 78);
R4(b, c, d, e, a, 79);
/* Add the working vars back into context.state[] */
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
state[4] += e;
/* Wipe variables */
a = b = c = d = e = 0;
#ifdef SHA1HANDSOFF
memset(block, 0, 64);
#endif
}
/* SHA1Init - Initialize new context */
void SHA1Init(SHA1_CTX* context)
{
/* SHA1 initialization constants */
context->state[0] = 0x67452301;
context->state[1] = 0xEFCDAB89;
context->state[2] = 0x98BADCFE;
context->state[3] = 0x10325476;
context->state[4] = 0xC3D2E1F0;
context->count[0] = context->count[1] = 0;
}
/* Run your data through this. */
void SHA1Update(SHA1_CTX* context, const void* _data, uint32_t len)
{
uint32_t i, j;
const unsigned char* data = _data;
#ifdef VERBOSE
SHAPrintContext(context, "before");
#endif
j = (context->count[0] >> 3) & 63;
if ((context->count[0] += len << 3) < (len << 3))
context->count[1]++;
context->count[1] += (len >> 29);
if ((j + len) > 63)
{
memcpy(&context->buffer[j], data, (i = 64 - j));
SHA1Transform(context->state, context->buffer);
for (; i + 63 < len; i += 64)
{
SHA1Transform(context->state, &data[i]);
}
j = 0;
}
else
i = 0;
memcpy(&context->buffer[j], &data[i], len - i);
#ifdef VERBOSE
SHAPrintContext(context, "after ");
#endif
}
/* Add padding and return the message digest. */
void SHA1Final(unsigned char digest[20], SHA1_CTX* context)
{
uint32_t i;
unsigned char finalcount[8];
for (i = 0; i < 8; i++)
{
/* Endian independent */
finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255);
}
SHA1Update(context, (unsigned char*)"\200", 1);
while ((context->count[0] & 504) != 448)
{
SHA1Update(context, (unsigned char*)"\0", 1);
}
/* Should cause a SHA1Transform() */
SHA1Update(context, finalcount, 8);
for (i = 0; i < 20; i++)
{
digest[i] = (unsigned char)((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
}
/* Wipe variables */
i = 0;
memset(context->buffer, 0, 64);
memset(context->state, 0, 20);
memset(context->count, 0, 8);
memset(finalcount, 0, 8);
}
/* ===== end - public domain SHA1 implementation ===== */

71
src/sha1.h Normal file
View File

@@ -0,0 +1,71 @@
/*
* SHA1 internal definitions
* Copyright (c) 2003-2005, Jouni Malinen <j@w1.fi>
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
/**
** \defgroup sha1 SHA1
** SHA1 API.
** Adapted from
** https://web.mit.edu/freebsd/head/contrib/wpa/src/crypto/sha1_i.h by Cory
** McWilliams 2025-09-28.
** @{
*/
#ifndef SHA1_I_H
#define SHA1_I_H
#include <inttypes.h>
/**
** SHA1 context struct.
*/
struct SHA1Context
{
/** SHA1 state. */
uint32_t state[5];
/** SHA1 count. */
uint32_t count[2];
/** SHA1 buffer. */
unsigned char buffer[64];
};
/**
** SHA1 context.
*/
typedef struct SHA1Context SHA1_CTX;
/**
** Initialize a SHA1 context.
** @param context The context.
*/
void SHA1Init(struct SHA1Context* context);
/**
** Calculate an ongoing hash for a block of data.
** @param context The SHA1 context.
** @param data The data to hash.
** @param len The length of data.
*/
void SHA1Update(struct SHA1Context* context, const void* data, uint32_t len);
/**
** Calculate the final hash digest.
** @param digest Populated with the digest.
** @param context The SHA1 context.
*/
void SHA1Final(unsigned char digest[20], struct SHA1Context* context);
/**
** Perform a SHA1 transformation.
** @param state The SHA1 state.
** @param buffer The data.
*/
void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]);
#endif /* SHA1_I_H */
/** @} */

File diff suppressed because it is too large Load Diff

View File

@@ -1,30 +0,0 @@
#pragma once
/**
** \defgroup socket_js Socket Interface
** Exposes network sockets to script.
** @{
*/
#include "quickjs.h"
/**
** Register the socket script interface.
** @param context The JS context.
** @return The Socket constructor.
*/
JSValue tf_socket_register(JSContext* context);
/**
** Get the number of active socket objects.
** @return The count.
*/
int tf_socket_get_count();
/**
** Get the number of connected socket objects.
** @return the count.
*/
int tf_socket_get_open_count();
/** @} */

View File

@@ -1,5 +1,6 @@
#include "ssb.h"
#include "http.h"
#include "log.h"
#include "mem.h"
#include "ssb.connections.h"
@@ -2711,6 +2712,7 @@ void tf_ssb_destroy(tf_ssb_t* ssb)
tf_printf("--\n");
uv_print_all_handles(ssb->loop, stdout);
}
tf_http_debug_destroy();
uv_run(ssb->loop, UV_RUN_ONCE);
}

View File

@@ -2370,6 +2370,10 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
JS_SetPropertyStr(context, global, "ssb", object);
JS_SetOpaque(object, ssb);
JSValue object_internal = JS_NewObjectClass(context, _tf_ssb_classId);
JS_SetPropertyStr(context, global, "ssb_internal", object_internal);
JS_SetOpaque(object_internal, ssb);
/* Requires an identity. */
JS_SetPropertyStr(context, object, "createIdentity", JS_NewCFunction(context, _tf_ssb_createIdentity, "createIdentity", 1));
JS_SetPropertyStr(context, object, "addIdentity", JS_NewCFunction(context, _tf_ssb_addIdentity, "addIdentity", 2));
@@ -2387,7 +2391,6 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
JS_SetPropertyStr(context, object, "getServerIdentity", JS_NewCFunction(context, _tf_ssb_getServerIdentity, "getServerIdentity", 0));
JS_SetPropertyStr(context, object, "getAllIdentities", JS_NewCFunction(context, _tf_ssb_getAllIdentities, "getAllIdentities", 0));
JS_SetPropertyStr(context, object, "getActiveIdentity", JS_NewCFunction(context, _tf_ssb_getActiveIdentity, "getActiveIdentity", 3));
JS_SetPropertyStr(context, object, "getIdentityInfo", JS_NewCFunction(context, _tf_ssb_getIdentityInfo, "getIdentityInfo", 3));
JS_SetPropertyStr(context, object, "blobGet", JS_NewCFunction(context, _tf_ssb_blobGet, "blobGet", 1));
JS_SetPropertyStr(context, object, "connections", JS_NewCFunction(context, _tf_ssb_connections, "connections", 0));
JS_SetPropertyStr(context, object, "storedConnections", JS_NewCFunction(context, _tf_ssb_storedConnections, "storedConnections", 0));
@@ -2406,8 +2409,9 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
JS_SetPropertyStr(context, object, "blobStore", JS_NewCFunction(context, _tf_ssb_blobStore, "blobStore", 1));
/* Trusted only. */
JS_SetPropertyStr(context, object, "addEventListener", JS_NewCFunction(context, _tf_ssb_add_event_listener, "addEventListener", 2));
JS_SetPropertyStr(context, object, "removeEventListener", JS_NewCFunction(context, _tf_ssb_remove_event_listener, "removeEventListener", 2));
JS_SetPropertyStr(context, object_internal, "getIdentityInfo", JS_NewCFunction(context, _tf_ssb_getIdentityInfo, "getIdentityInfo", 3));
JS_SetPropertyStr(context, object_internal, "addEventListener", JS_NewCFunction(context, _tf_ssb_add_event_listener, "addEventListener", 2));
JS_SetPropertyStr(context, object_internal, "removeEventListener", JS_NewCFunction(context, _tf_ssb_remove_event_listener, "removeEventListener", 2));
JS_FreeValue(context, global);
}

View File

@@ -1220,7 +1220,181 @@ void tf_ssb_test_replicate(const tf_test_options_t* options)
tf_printf("done\n");
tf_printf("Waiting for blob.\n");
while (!tf_ssb_db_blob_get(ssb0, blob_id, NULL, NULL))
while (!tf_ssb_db_blob_get(ssb1, blob_id, NULL, NULL))
{
tf_ssb_set_main_thread(ssb1, true);
uv_run(&loop, UV_RUN_ONCE);
tf_ssb_set_main_thread(ssb1, false);
}
tf_printf("done\n");
tf_ssb_send_close(ssb1);
uv_close((uv_handle_t*)&idle0, NULL);
uv_close((uv_handle_t*)&idle1, NULL);
tf_printf("final run\n");
tf_ssb_set_main_thread(ssb0, true);
tf_ssb_set_main_thread(ssb1, true);
uv_run(&loop, UV_RUN_DEFAULT);
tf_ssb_set_main_thread(ssb0, false);
tf_ssb_set_main_thread(ssb1, false);
tf_printf("done\n");
tf_printf("destroy 0\n");
tf_ssb_destroy(ssb0);
tf_printf("destroy 1\n");
tf_ssb_destroy(ssb1);
tf_printf("close\n");
uv_loop_close(&loop);
}
void tf_ssb_test_replicate_blob(const tf_test_options_t* options)
{
tf_printf("Testing blob replication.\n");
uv_loop_t loop = { 0 };
uv_loop_init(&loop);
unlink("out/test_db0.sqlite");
tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, "file:out/test_db0.sqlite", NULL);
tf_ssb_register(tf_ssb_get_context(ssb0), ssb0);
unlink("out/test_db1.sqlite");
tf_ssb_t* ssb1 = tf_ssb_create(&loop, NULL, "file:out/test_db1.sqlite", NULL);
tf_ssb_register(tf_ssb_get_context(ssb1), ssb1);
uv_idle_t idle0 = { .data = ssb0 };
uv_idle_init(&loop, &idle0);
uv_idle_start(&idle0, _ssb_test_idle);
uv_idle_t idle1 = { .data = ssb1 };
uv_idle_init(&loop, &idle1);
uv_idle_start(&idle1, _ssb_test_idle);
test_t test = {
.ssb0 = ssb0,
.ssb1 = ssb1,
};
tf_ssb_add_connections_changed_callback(ssb0, _ssb_test_connections_changed, NULL, &test);
tf_ssb_add_connections_changed_callback(ssb1, _ssb_test_connections_changed, NULL, &test);
tf_ssb_generate_keys(ssb0);
tf_ssb_generate_keys(ssb1);
uint8_t priv0[crypto_sign_SECRETKEYBYTES] = { 0 };
uint8_t priv1[crypto_sign_SECRETKEYBYTES] = { 0 };
tf_ssb_get_private_key(ssb0, priv0, sizeof(priv0));
tf_ssb_get_private_key(ssb1, priv1, sizeof(priv1));
char id0[k_id_base64_len] = { 0 };
char id1[k_id_base64_len] = { 0 };
bool b = tf_ssb_whoami(ssb0, id0, sizeof(id0));
(void)b;
assert(b);
b = tf_ssb_whoami(ssb1, id1, sizeof(id1));
assert(b);
tf_printf("ID %s and %s\n", id0, id1);
char priv0_str[512] = { 0 };
char priv1_str[512] = { 0 };
tf_base64_encode(priv0, sizeof(priv0), priv0_str, sizeof(priv0_str));
tf_base64_encode(priv1, sizeof(priv0), priv1_str, sizeof(priv1_str));
tf_ssb_db_identity_add(ssb0, "test", id0 + 1, priv0_str);
tf_ssb_db_identity_add(ssb1, "test", id1 + 1, priv1_str);
static const int k_key_count = 5;
char public[k_key_count][k_id_base64_len - 1];
char private[k_key_count][512];
for (int i = 0; i < k_key_count; i++)
{
tf_ssb_generate_keys_buffer(public[i], sizeof(public[i]), private[i], sizeof(private[i]));
bool added = tf_ssb_db_identity_add(ssb0, "test", public[i], private[i]);
tf_printf("%s user %d = %s private=%s\n", added ? "added" : "failed", i, public[i], private[i]);
}
tf_printf("ssb0\n");
tf_ssb_db_identity_visit_all(ssb0, _test_print_identity, ssb0);
tf_printf("ssb1\n");
tf_ssb_db_identity_visit_all(ssb1, _test_print_identity, ssb1);
tf_ssb_server_open(ssb0, 12347);
uint8_t id0bin[k_id_bin_len];
tf_ssb_id_str_to_bin(id0bin, id0);
tf_ssb_connect(ssb1, "127.0.0.1", 12347, id0bin, 0, NULL, NULL);
tf_printf("Waiting for connection.\n");
while (test.connection_count0 != 1 || test.connection_count1 != 1)
{
tf_ssb_set_main_thread(ssb0, true);
tf_ssb_set_main_thread(ssb1, true);
uv_run(&loop, UV_RUN_ONCE);
tf_ssb_set_main_thread(ssb0, false);
tf_ssb_set_main_thread(ssb1, false);
}
tf_ssb_server_close(ssb0);
char blob_id[k_id_base64_len] = { 0 };
const char* k_blob = "Hello, new blob!";
b = tf_ssb_db_blob_store(ssb0, (const uint8_t*)k_blob, strlen(k_blob), blob_id, sizeof(blob_id), NULL);
assert(b);
JSContext* context0 = tf_ssb_get_context(ssb0);
for (int i = 0; i < k_key_count - 1; i++)
{
JSValue obj = JS_NewObject(context0);
JS_SetPropertyStr(context0, obj, "type", JS_NewString(context0, "contact"));
char self[k_id_base64_len];
snprintf(self, sizeof(self), "@%s", public[i]);
char contact[k_id_base64_len];
snprintf(contact, sizeof(contact), "@%s", public[i + 1]);
JS_SetPropertyStr(context0, obj, "contact", JS_NewString(context0, contact));
JS_SetPropertyStr(context0, obj, "following", JS_TRUE);
bool stored = false;
uint8_t private_bin[512] = { 0 };
tf_base64_decode(private[i], strlen(private[i]) - strlen(".ed25519"), private_bin, sizeof(private_bin));
tf_printf("ssb0 %s following %s\n", self, contact);
JSValue signed_message = tf_ssb_sign_message(ssb0, self, private_bin, obj, NULL, 0);
tf_ssb_verify_strip_and_store_message(ssb0, signed_message, _message_stored, &stored);
JS_FreeValue(context0, signed_message);
_wait_stored(ssb0, &stored);
JS_FreeValue(context0, obj);
obj = JS_NewObject(context0);
JS_SetPropertyStr(context0, obj, "type", JS_NewString(context0, "post"));
JS_SetPropertyStr(context0, obj, "text", JS_NewString(context0, "Hello, world!"));
JS_SetPropertyStr(context0, obj, "arbitrary_reference", JS_NewString(context0, blob_id));
stored = false;
signed_message = tf_ssb_sign_message(ssb0, self, private_bin, obj, NULL, 0);
tf_ssb_verify_strip_and_store_message(ssb0, signed_message, _message_stored, &stored);
JS_FreeValue(context0, signed_message);
_wait_stored(ssb0, &stored);
JS_FreeValue(context0, obj);
}
JSContext* context1 = tf_ssb_get_context(ssb1);
{
JSValue obj = JS_NewObject(context1);
JS_SetPropertyStr(context1, obj, "type", JS_NewString(context1, "contact"));
char self[k_id_base64_len];
tf_string_set(self, sizeof(self), id1);
char contact[k_id_base64_len];
snprintf(contact, sizeof(contact), "@%s", public[0]);
JS_SetPropertyStr(context1, obj, "contact", JS_NewString(context1, contact));
JS_SetPropertyStr(context1, obj, "following", JS_TRUE);
bool stored = false;
tf_printf("ssb1 %s following %s\n", self, contact);
JSValue signed_message = tf_ssb_sign_message(ssb1, self, priv1, obj, NULL, 0);
tf_ssb_verify_strip_and_store_message(ssb1, signed_message, _message_stored, &stored);
JS_FreeValue(context1, signed_message);
_wait_stored(ssb1, &stored);
JS_FreeValue(context1, obj);
}
tf_printf("Waiting for blob.\n");
while (!tf_ssb_db_blob_get(ssb1, blob_id, NULL, NULL))
{
tf_ssb_set_main_thread(ssb1, true);
uv_run(&loop, UV_RUN_ONCE);

View File

@@ -66,13 +66,19 @@ void tf_ssb_test_peer_exchange(const tf_test_options_t* options);
void tf_ssb_test_publish(const tf_test_options_t* options);
/**
** Test connecting by string.
** Test message and replication.
** @param options The test options.
*/
void tf_ssb_test_replicate(const tf_test_options_t* options);
/**
** Test invites.
** Test blob replication for a message received while already connected.
** @param options The test options.
*/
void tf_ssb_test_replicate_blob(const tf_test_options_t* options);
/**
** Test connecting by string.
** @param options The test options.
*/
void tf_ssb_test_connect_str(const tf_test_options_t* options);

View File

@@ -1,20 +1,18 @@
#include "task.h"
#include "api.js.h"
#include "bcrypt.js.h"
#include "database.js.h"
#include "file.js.h"
#include "http.h"
#include "httpd.js.h"
#include "log.h"
#include "mem.h"
#include "packetstream.h"
#include "serialize.h"
#include "socket.js.h"
#include "ssb.db.h"
#include "ssb.h"
#include "ssb.js.h"
#include "taskstub.js.h"
#include "tlscontext.js.h"
#include "trace.h"
#include "util.js.h"
#include "version.h"
@@ -827,9 +825,6 @@ static JSValue _tf_task_getStats(JSContext* context, JSValueConst this_val, int
JS_SetPropertyStr(context, result, "tls_malloc_percent", JS_NewFloat64(context, 100.0 * tf_mem_get_tls_malloc_size() / total_memory));
JS_SetPropertyStr(context, result, "tf_malloc_percent", JS_NewFloat64(context, 100.0 * tf_mem_get_tf_malloc_size() / total_memory));
JS_SetPropertyStr(context, result, "socket_count", JS_NewInt32(context, tf_socket_get_count()));
JS_SetPropertyStr(context, result, "socket_open_count", JS_NewInt32(context, tf_socket_get_open_count()));
if (task->_ssb)
{
tf_ssb_stats_t ssb_stats = { 0 };
@@ -1666,8 +1661,6 @@ void tf_task_activate(tf_task_t* task)
sqlite3_open(task->_db_path, &task->_db);
JS_SetPropertyStr(context, global, "Task", tf_taskstub_register(context));
JS_SetPropertyStr(context, global, "Socket", tf_socket_register(context));
JS_SetPropertyStr(context, global, "TlsContext", tf_tls_context_register(context));
tf_file_register(context);
tf_database_register(context);
@@ -1728,7 +1721,6 @@ void tf_task_activate(tf_task_t* task)
tf_trace_set_write_callback(task->_trace, _tf_task_trace_to_parent, task);
}
tf_bcrypt_register(context);
tf_util_register(context);
JS_SetPropertyStr(context, global, "exit", JS_NewCFunction(context, _tf_task_exit, "exit", 1));
JS_SetPropertyStr(context, global, "version", JS_NewCFunction(context, _tf_task_version, "version", 0));
@@ -1884,6 +1876,7 @@ void tf_task_destroy(tf_task_t* task)
tf_printf("--\n");
uv_print_all_handles(&task->_loop, stdout);
}
tf_http_debug_destroy();
uv_run(&task->_loop, UV_RUN_ONCE);
}
if (task->_trace)

View File

@@ -549,93 +549,6 @@ static void _test_float(const tf_test_options_t* options)
unlink("out/child.js");
}
static void _test_socket(const tf_test_options_t* options)
{
_write_file("out/test.js",
"'use strict';\n"
"\n"
"var s = new Socket();\n"
"print('connecting');\n"
"print('before connect', s.isConnected);\n"
"s.onError(function(e) {\n"
" print(e);\n"
"});\n"
"print('noDelay', s.noDelay);\n"
"s.noDelay = true;\n"
"s.connect('www.unprompted.com', 80).then(function() {\n"
" print('connected', 'www.unprompted.com', 80, s.isConnected);\n"
" print(s.peerName);\n"
" s.read(function(data) {\n"
" print('read', data ? data.length : null);\n"
" });\n"
" s.write('GET / HTTP/1.0\\r\\n\\r\\n');\n"
"}).then(function(e) {\n"
" print('closed 1');\n"
"});\n"
"\n"
"var s2 = new Socket();\n"
"print('connecting');\n"
"print('before connect', s2.isConnected);\n"
"s2.onError(function(e) {\n"
" print('error');\n"
" print(e);\n"
"});\n"
"print('noDelay', s2.noDelay);\n"
"s2.noDelay = true;\n"
"s2.connect('www.unprompted.com', 443).then(function() {\n"
" print('connected', 'www.unprompted.com', 443);\n"
" s2.read(function(data) {\n"
" print('read', data ? data.length : null);\n"
" });\n"
" return s2.startTls();\n"
"}).then(function() {\n"
" print('ready');\n"
" print(s2.peerName);\n"
" s2.write('GET / HTTP/1.0\\r\\nConnection: close\\r\\n\\r\\n').then(function() {\n"
" s2.shutdown();\n"
" });\n"
"}).catch(function(e) {\n"
" print('caught');\n"
" print(e);\n"
"});\n"
"var s3 = new Socket();\n"
"print('connecting s3');\n"
"print('before connect', s3.isConnected);\n"
"s3.onError(function(e) {\n"
" print('error');\n"
" print(e);\n"
"});\n"
"print('noDelay', s3.noDelay);\n"
"s3.noDelay = true;\n"
"s3.connect('0.0.0.0', 443).then(function() {\n"
" print('connected', '0.0.0.0', 443);\n"
" s3.read(function(data) {\n"
" print('read', data ? data.length : null);\n"
" });\n"
" return s3.startTls();\n"
"}).then(function() {\n"
" print('ready');\n"
" print(s3.peerName);\n"
" s3.write('GET / HTTP/1.0\\r\\nConnection: close\\r\\n\\r\\n').then(function() {\n"
" s3.shutdown();\n"
" });\n"
"}).catch(function(e) {\n"
" print('caught');\n"
" print(e);\n"
"});\n");
char command[256];
unlink("out/test_db0.sqlite");
snprintf(command, sizeof(command), "%s run --db-path=out/test_db0.sqlite -s out/test.js" TEST_ARGS, options->exe_path);
tf_printf("%s\n", command);
int result = system(command);
tf_printf("returned %d\n", WEXITSTATUS(result));
assert(WIFEXITED(result));
assert(WEXITSTATUS(result) == 0);
unlink("out/test.js");
}
static void _test_file(const tf_test_options_t* options)
{
_write_file("out/test.js",
@@ -1065,7 +978,6 @@ void tf_tests(const tf_test_options_t* options)
_tf_test_run(options, "icu", _test_icu, false);
_tf_test_run(options, "uint8array", _test_uint8array, false);
_tf_test_run(options, "float", _test_float, false);
_tf_test_run(options, "socket", _test_socket, false);
_tf_test_run(options, "file", _test_file, false);
_tf_test_run(options, "b64", _test_b64, false);
_tf_test_run(options, "rooms", tf_ssb_test_rooms, false);
@@ -1076,6 +988,7 @@ void tf_tests(const tf_test_options_t* options)
_tf_test_run(options, "peer_exchange", tf_ssb_test_peer_exchange, false);
_tf_test_run(options, "publish", tf_ssb_test_publish, false);
_tf_test_run(options, "replicate", tf_ssb_test_replicate, false);
_tf_test_run(options, "replicate_blob", tf_ssb_test_replicate_blob, false);
_tf_test_run(options, "connect_str", tf_ssb_test_connect_str, false);
_tf_test_run(options, "invite", tf_ssb_test_invite, false);
_tf_test_run(options, "triggers", tf_ssb_test_triggers, false);

View File

@@ -1,105 +0,0 @@
#include "tlscontext.js.h"
#include "log.h"
#include "mem.h"
#include "task.h"
#include "tls.h"
#include <stdlib.h>
#include <string.h>
static JSClassID _classId;
static int _count;
typedef struct _tf_tls_context_t
{
tf_tls_context_t* context;
tf_task_t* task;
JSValue object;
} tf_tls_context_t;
static JSValue _tls_context_create(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
static void _tls_context_finalizer(JSRuntime* runtime, JSValue value);
static JSValue _tls_context_set_certificate(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
tf_tls_context_t* tls = JS_GetOpaque(this_val, _classId);
const char* value = JS_ToCString(context, argv[0]);
tf_tls_context_set_certificate(tls->context, value);
JS_FreeCString(context, value);
return JS_UNDEFINED;
}
static JSValue _tls_context_set_private_key(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
tf_tls_context_t* tls = JS_GetOpaque(this_val, _classId);
const char* value = JS_ToCString(context, argv[0]);
tf_tls_context_set_private_key(tls->context, value);
JS_FreeCString(context, value);
return JS_UNDEFINED;
}
static JSValue _tls_context_add_trusted_certificate(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
tf_tls_context_t* tls = JS_GetOpaque(this_val, _classId);
const char* value = JS_ToCString(context, argv[0]);
tf_tls_context_add_trusted_certificate(tls->context, value);
JS_FreeCString(context, value);
return JS_UNDEFINED;
}
JSValue tf_tls_context_register(JSContext* context)
{
JS_NewClassID(&_classId);
JSClassDef def = {
.class_name = "TlsContext",
.finalizer = _tls_context_finalizer,
};
if (JS_NewClass(JS_GetRuntime(context), _classId, &def) != 0)
{
fprintf(stderr, "Failed to register TlsContext.\n");
}
return JS_NewCFunction2(context, _tls_context_create, "TlsContext", 0, JS_CFUNC_constructor, 0);
}
tf_tls_context_t* tf_tls_context_get(JSValue value)
{
tf_tls_context_t* tls = JS_GetOpaque(value, _classId);
return tls ? tls->context : NULL;
}
int tf_tls_context_get_count()
{
return _count;
}
static JSValue _tls_context_create(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
tf_tls_context_t* tls = tf_malloc(sizeof(tf_tls_context_t));
memset(tls, 0, sizeof(*tls));
++_count;
tls->object = JS_NewObjectClass(context, _classId);
JS_SetOpaque(tls->object, tls);
JS_SetPropertyStr(context, tls->object, "setCertificate", JS_NewCFunction(context, _tls_context_set_certificate, "setCertificate", 1));
JS_SetPropertyStr(context, tls->object, "setPrivateKey", JS_NewCFunction(context, _tls_context_set_private_key, "setPrivateKey", 1));
JS_SetPropertyStr(context, tls->object, "addTrustedCertificate", JS_NewCFunction(context, _tls_context_add_trusted_certificate, "addTrustedCertificate", 1));
tls->context = tf_tls_context_create();
tls->task = tf_task_get(context);
return tls->object;
}
static void _tls_context_finalizer(JSRuntime* runtime, JSValue value)
{
tf_tls_context_t* tls = JS_GetOpaque(value, _classId);
if (tls->context)
{
tf_tls_context_destroy(tls->context);
tls->context = NULL;
}
--_count;
tf_free(tls);
}

View File

@@ -1,37 +0,0 @@
#pragma once
/**
** \defgroup tls_js TLS Interface
** Exposes \ref tls to JS.
** @{
*/
#include "quickjs.h"
/**
** A TLS context instance.
*/
typedef struct _tf_tls_context_t tf_tls_context_t;
/**
** Register TLS script interface.
** @param context The TLS context.
** @return the TlsContext constructor.
*/
JSValue tf_tls_context_register(JSContext* context);
/**
** Get a TLS context instance from its JS object.
** @param value A TlsContext JS object.
** @return The corresponding instance.
*/
tf_tls_context_t* tf_tls_context_get(JSValue value);
/**
** Get the number of active TLS context instances.
** @return The number of TlsContext objects created that have not been
** finalized.
*/
int tf_tls_context_get_count();
/** @} */

View File

@@ -253,66 +253,6 @@ bool tf_util_report_error(JSContext* context, JSValue value)
return is_error;
}
static JSValue _util_parseHttpResponse(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue result = JS_UNDEFINED;
int status = 0;
int minor_version = 0;
const char* message = NULL;
size_t message_length = 0;
struct phr_header headers[100];
size_t header_count = sizeof(headers) / sizeof(*headers);
int previous_length = 0;
JS_ToInt32(context, &previous_length, argv[1]);
JSValue buffer = JS_UNDEFINED;
size_t length;
uint8_t* array = tf_util_try_get_array_buffer(context, &length, argv[0]);
if (!array)
{
size_t offset;
size_t element_size;
buffer = tf_util_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size);
if (!JS_IsException(buffer))
{
array = tf_util_try_get_array_buffer(context, &length, buffer);
}
}
if (array)
{
int parse_result = phr_parse_response((const char*)array, length, &minor_version, &status, &message, &message_length, headers, &header_count, previous_length);
if (parse_result > 0)
{
result = JS_NewObject(context);
JS_SetPropertyStr(context, result, "bytes_parsed", JS_NewInt32(context, parse_result));
JS_SetPropertyStr(context, result, "minor_version", JS_NewInt32(context, minor_version));
JS_SetPropertyStr(context, result, "status", JS_NewInt32(context, status));
JS_SetPropertyStr(context, result, "message", JS_NewStringLen(context, message, message_length));
JSValue header_object = JS_NewObject(context);
for (int i = 0; i < (int)header_count; i++)
{
char name[256];
snprintf(name, sizeof(name), "%.*s", (int)headers[i].name_len, headers[i].name);
JS_SetPropertyStr(context, header_object, name, JS_NewStringLen(context, headers[i].value, headers[i].value_len));
}
JS_SetPropertyStr(context, result, "headers", header_object);
}
else
{
result = JS_NewInt32(context, parse_result);
}
}
else
{
result = JS_ThrowTypeError(context, "Could not convert argument to array.");
}
JS_FreeValue(context, buffer);
return result;
}
static const char* k_kind_name[] = {
[k_kind_bool] = "bool",
[k_kind_int] = "int",
@@ -359,10 +299,6 @@ static const setting_t k_settings[] = {
.type = "integer",
.description = "Blobs older than this will be automatically deleted.",
.default_value = { .kind = k_kind_int, .int_value = TF_IS_MOBILE ? (int)(1.0f * 365 * 24 * 60 * 60) : -1 } },
{ .name = "fetch_hosts",
.type = "string",
.description = "Comma-separated list of host names to which HTTP fetch requests are allowed. None if empty.",
.default_value = { .kind = k_kind_string, .string_value = NULL } },
{ .name = "http_redirect",
.type = "string",
.description = "If connecting by HTTP and HTTPS is configured, Location header prefix (ie, \"http://example.com\")",
@@ -523,7 +459,6 @@ void tf_util_register(JSContext* context)
JS_SetPropertyStr(context, global, "bip39Words", JS_NewCFunction(context, _util_bip39_words, "bip39Words", 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, "parseHttpResponse", JS_NewCFunction(context, _util_parseHttpResponse, "parseHttpResponse", 2));
JS_SetPropertyStr(context, global, "defaultGlobalSettings", JS_NewCFunction(context, _util_defaultGlobalSettings, "defaultGlobalSettings", 2));
JS_FreeValue(context, global);
}

View File

@@ -1,2 +1,2 @@
#define VERSION_NUMBER "0.2025.9"
#define VERSION_NUMBER "0.2025.10-wip"
#define VERSION_NAME "This program kills fascists."