Compare commits
7 Commits
latest_rel
...
0ead5ed967
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ead5ed967 | |||
| 53261a6fbc | |||
| c60ff86a4d | |||
| 83a0b017c5 | |||
| 3746622a11 | |||
| ccd50cf59f | |||
| 93680eb43d |
1
Doxyfile
1
Doxyfile
@@ -910,7 +910,6 @@ INPUT = README.md \
|
|||||||
core/app.js \
|
core/app.js \
|
||||||
core/client.js \
|
core/client.js \
|
||||||
core/core.js \
|
core/core.js \
|
||||||
core/http.js \
|
|
||||||
core/tfrpc.js \
|
core/tfrpc.js \
|
||||||
docs/ \
|
docs/ \
|
||||||
src/
|
src/
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ MAKEFLAGS += --no-builtin-rules
|
|||||||
## LD := Linker.
|
## LD := Linker.
|
||||||
## ANDROID_SDK := Path to the Android SDK.
|
## ANDROID_SDK := Path to the Android SDK.
|
||||||
|
|
||||||
VERSION_CODE := 43
|
VERSION_CODE := 44
|
||||||
VERSION_CODE_IOS := 17
|
VERSION_CODE_IOS := 18
|
||||||
VERSION_NUMBER := 0.2025.9
|
VERSION_NUMBER := 0.2025.10-wip
|
||||||
VERSION_NAME := This program kills fascists.
|
VERSION_NAME := This program kills fascists.
|
||||||
|
|
||||||
IPHONEOS_VERSION_MIN=14.0
|
IPHONEOS_VERSION_MIN=14.0
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "📜",
|
"emoji": "📜",
|
||||||
"previous": "&BEf0nraBdHk/+PWqx6tOSu5rheWVaxaL7orAOz3285M=.sha256"
|
"previous": "&sJqeyYjHys6Z8IqqtZ2ij2ZC1E2xieu/FU/u2hE+O1U=.sha256"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,9 @@ app.setDocument(`<head>
|
|||||||
</head>
|
</head>
|
||||||
<body style="color:#fff">
|
<body style="color:#fff">
|
||||||
${markdown(docs.docs.global)}
|
${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')}
|
${[...treeify('', globalThis)].map(x => document(x)).join('\n')}
|
||||||
<a id="Database"></a>
|
<a id="Database"></a>
|
||||||
${markdown(docs.docs.database)}
|
${markdown(docs.docs.database)}
|
||||||
|
|||||||
@@ -195,51 +195,6 @@ Call a function after some delay.
|
|||||||
* *Number* **timeout** Number of milliseconds to wait before calling the callback function.
|
* *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()'] = `
|
docs['exit()'] = `
|
||||||
Exits the app. But why would you want to do that?
|
Exits the app. But why would you want to do that?
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "🦀",
|
"emoji": "🦀",
|
||||||
"previous": "&IDzjVQjtPyhesUrl45qkZFjzWl0xVlj+2M/XXQRvXO0=.sha256"
|
"previous": "&01jXxJgs24zTcJk+csXeUWfm/MQ/+94Zy7K0r2OYmWw=.sha256"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -644,6 +644,35 @@ class TfMessageElement extends LitElement {
|
|||||||
return result;
|
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() {
|
allow_unread() {
|
||||||
return (
|
return (
|
||||||
this.channel == '@' ||
|
this.channel == '@' ||
|
||||||
@@ -719,6 +748,55 @@ class TfMessageElement extends LitElement {
|
|||||||
</button>
|
</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}
|
||||||
|
></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) {
|
} else if (this.message.placeholder) {
|
||||||
return this.render_frame(
|
return this.render_frame(
|
||||||
html`<div>
|
html`<div>
|
||||||
|
|||||||
@@ -160,11 +160,29 @@ class TfNewsElement extends LitElement {
|
|||||||
return recursive_sort(roots, true);
|
return recursive_sort(roots, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
group_following(messages) {
|
group_messages(messages) {
|
||||||
let result = [];
|
let result = [];
|
||||||
let group = [];
|
let group = [];
|
||||||
|
let type = undefined;
|
||||||
for (let message of messages) {
|
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);
|
group.push(message);
|
||||||
} else {
|
} else {
|
||||||
if (group.length == 1) {
|
if (group.length == 1) {
|
||||||
@@ -173,12 +191,13 @@ class TfNewsElement extends LitElement {
|
|||||||
} else if (group.length > 1) {
|
} else if (group.length > 1) {
|
||||||
result.push({
|
result.push({
|
||||||
rowid: Math.max(...group.map((x) => x.rowid)),
|
rowid: Math.max(...group.map((x) => x.rowid)),
|
||||||
type: 'contact_group',
|
type: `${type}_group`,
|
||||||
messages: group,
|
messages: group,
|
||||||
});
|
});
|
||||||
group = [];
|
group = [];
|
||||||
}
|
}
|
||||||
result.push(message);
|
result.push(message);
|
||||||
|
type = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (group.length == 1) {
|
if (group.length == 1) {
|
||||||
@@ -187,7 +206,7 @@ class TfNewsElement extends LitElement {
|
|||||||
} else if (group.length > 1) {
|
} else if (group.length > 1) {
|
||||||
result.push({
|
result.push({
|
||||||
rowid: Math.max(...group.map((x) => x.rowid)),
|
rowid: Math.max(...group.map((x) => x.rowid)),
|
||||||
type: 'contact_group',
|
type: `${type}_group`,
|
||||||
messages: group,
|
messages: group,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -200,7 +219,7 @@ class TfNewsElement extends LitElement {
|
|||||||
|
|
||||||
load_and_render(messages) {
|
load_and_render(messages) {
|
||||||
let messages_by_id = this.process_messages(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)
|
this.finalize_messages(messages_by_id)
|
||||||
);
|
);
|
||||||
let unread_rowid = -1;
|
let unread_rowid = -1;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "👋",
|
"emoji": "👋",
|
||||||
"previous": "&5NkMRSgcMqCYF3xcLOBmaytkoxfV9zx4br7JladKPTs=.sha256"
|
"previous": "&ijyL/pyTwguBd9njagU7Vpc/1EyRermZuzrlq1mnzbY=.sha256"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,7 +104,7 @@
|
|||||||
src="googleplay.svg"
|
src="googleplay.svg"
|
||||||
style="height: 2em; margin: 0"
|
style="height: 2em; margin: 0"
|
||||||
/>
|
/>
|
||||||
Get it on Google Play (Open Testing)
|
Get it on Google Play
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
|
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
|
|
||||||
<!-- Technlology Section -->
|
<!-- Technlology Section -->
|
||||||
<div class="w3-container w3-padding-64 w3-light-grey w3-center">
|
<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>
|
<p>
|
||||||
Tilde Friends strives to use only simple and widely adopted dependencies
|
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
|
in order to keep it easy to build for all sorts of platforms and
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
/** \cond */
|
/** \cond */
|
||||||
import * as app from './app.js';
|
import * as app from './app.js';
|
||||||
import * as http from './http.js';
|
|
||||||
|
|
||||||
export {invoke, getProcessBlob};
|
export {invoke, getProcessBlob};
|
||||||
/** \endcond */
|
/** \endcond */
|
||||||
@@ -240,7 +239,6 @@ async function getProcessBlob(blobId, key, options) {
|
|||||||
let settings = await loadSettings();
|
let settings = await loadSettings();
|
||||||
return settings?.permissions?.[user] ?? [];
|
return settings?.permissions?.[user] ?? [];
|
||||||
},
|
},
|
||||||
getSockets: getSockets,
|
|
||||||
permissionTest: async function (permission) {
|
permissionTest: async function (permission) {
|
||||||
let user = process?.credentials?.session?.name;
|
let user = process?.credentials?.session?.name;
|
||||||
let settings = await loadSettings();
|
let settings = await loadSettings();
|
||||||
@@ -556,10 +554,6 @@ async function getProcessBlob(blobId, key, options) {
|
|||||||
imports.ssb.addEventListener = undefined;
|
imports.ssb.addEventListener = undefined;
|
||||||
imports.ssb.removeEventListener = undefined;
|
imports.ssb.removeEventListener = undefined;
|
||||||
imports.ssb.getIdentityInfo = undefined;
|
imports.ssb.getIdentityInfo = undefined;
|
||||||
imports.fetch = async function (url, options) {
|
|
||||||
let settings = await loadSettings();
|
|
||||||
return http.fetch(url, options, settings?.fetch_hosts);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
process.credentials &&
|
process.credentials &&
|
||||||
|
|||||||
121
core/http.js
121
core/http.js
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @} */
|
|
||||||
@@ -25,14 +25,14 @@
|
|||||||
}:
|
}:
|
||||||
pkgs.stdenv.mkDerivation rec {
|
pkgs.stdenv.mkDerivation rec {
|
||||||
pname = "tildefriends";
|
pname = "tildefriends";
|
||||||
version = "0.2025.8";
|
version = "0.2025.9";
|
||||||
|
|
||||||
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-N/5lp8RL19B6Z43kRxx7c01WVJkK44a/wwNgRJPk5uI=";
|
hash = "sha256-1nhsfhdOO5HIiiTMb+uROB8nDPL/UpOYm52hZ/OpPyk=";
|
||||||
fetchSubmodules = true;
|
fetchSubmodules = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ options:
|
|||||||
out_http_port_file (default: ""): File to which to write bound HTTP port.
|
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_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.
|
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")
|
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 (default: "/~core/intro/"): Default path.
|
||||||
index_map (default: ""): Mappings from hostname to redirect path, one per line, as in: "www.tildefriends.net=/~core/index/"
|
index_map (default: ""): Mappings from hostname to redirect path, one per line, as in: "www.tildefriends.net=/~core/index/"
|
||||||
|
|||||||
6
flake.lock
generated
6
flake.lock
generated
@@ -20,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1756217674,
|
"lastModified": 1758589230,
|
||||||
"narHash": "sha256-TH1SfSP523QI7kcPiNtMAEuwZR3Jdz0MCDXPs7TS8uo=",
|
"narHash": "sha256-zMTCFGe8aVGTEr2RqUi/QzC1nOIQ0N1HRsbqB4f646k=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "4e7667a90c167f7a81d906e5a75cba4ad8bee620",
|
"rev": "d1d883129b193f0b495d75c148c2c3a7d95789a0",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -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="43"
|
android:versionCode="44"
|
||||||
android:versionName="0.2025.9">
|
android:versionName="0.2025.10-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
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
|
|
||||||
/** @} */
|
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "http.h"
|
#include "http.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "mem.h"
|
#include "mem.h"
|
||||||
|
#include "sha1.h"
|
||||||
#include "ssb.db.h"
|
#include "ssb.db.h"
|
||||||
#include "task.h"
|
#include "task.h"
|
||||||
#include "tls.h"
|
#include "tls.h"
|
||||||
@@ -14,8 +15,6 @@
|
|||||||
#include "sodium/crypto_sign.h"
|
#include "sodium/crypto_sign.h"
|
||||||
#include "sodium/utils.h"
|
#include "sodium/utils.h"
|
||||||
|
|
||||||
#include <openssl/sha.h>
|
|
||||||
|
|
||||||
#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 YELLOW "\e[1;33m"
|
||||||
@@ -169,8 +168,13 @@ static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_va
|
|||||||
uint8_t* key_magic = alloca(size);
|
uint8_t* key_magic = alloca(size);
|
||||||
memcpy(key_magic, header_sec_websocket_key, key_length);
|
memcpy(key_magic, header_sec_websocket_key, key_length);
|
||||||
memcpy(key_magic + key_length, k_magic, 36);
|
memcpy(key_magic + key_length, k_magic, 36);
|
||||||
|
|
||||||
uint8_t digest[20];
|
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 };
|
char key[41] = { 0 };
|
||||||
tf_base64_encode(digest, sizeof(digest), key, sizeof(key));
|
tf_base64_encode(digest, sizeof(digest), key, sizeof(key));
|
||||||
|
|
||||||
|
|||||||
@@ -13,13 +13,13 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>0.2025.9</string>
|
<string>0.2025.10</string>
|
||||||
<key>CFBundleSupportedPlatforms</key>
|
<key>CFBundleSupportedPlatforms</key>
|
||||||
<array>
|
<array>
|
||||||
<string>iPhoneOS</string>
|
<string>iPhoneOS</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>17</string>
|
<string>18</string>
|
||||||
<key>DTPlatformName</key>
|
<key>DTPlatformName</key>
|
||||||
<string>iphoneos</string>
|
<string>iphoneos</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
|||||||
329
src/sha1.c
Normal file
329
src/sha1.c
Normal 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
71
src/sha1.h
Normal 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 */
|
||||||
|
|
||||||
|
/** @} */
|
||||||
1165
src/socket.js.c
1165
src/socket.js.c
File diff suppressed because it is too large
Load Diff
@@ -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();
|
|
||||||
|
|
||||||
/** @} */
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
#include "task.h"
|
#include "task.h"
|
||||||
|
|
||||||
#include "api.js.h"
|
#include "api.js.h"
|
||||||
#include "bcrypt.js.h"
|
|
||||||
#include "database.js.h"
|
#include "database.js.h"
|
||||||
#include "file.js.h"
|
#include "file.js.h"
|
||||||
#include "httpd.js.h"
|
#include "httpd.js.h"
|
||||||
@@ -9,7 +8,6 @@
|
|||||||
#include "mem.h"
|
#include "mem.h"
|
||||||
#include "packetstream.h"
|
#include "packetstream.h"
|
||||||
#include "serialize.h"
|
#include "serialize.h"
|
||||||
#include "socket.js.h"
|
|
||||||
#include "ssb.db.h"
|
#include "ssb.db.h"
|
||||||
#include "ssb.h"
|
#include "ssb.h"
|
||||||
#include "ssb.js.h"
|
#include "ssb.js.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, "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, "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)
|
if (task->_ssb)
|
||||||
{
|
{
|
||||||
tf_ssb_stats_t ssb_stats = { 0 };
|
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);
|
sqlite3_open(task->_db_path, &task->_db);
|
||||||
|
|
||||||
JS_SetPropertyStr(context, global, "Task", tf_taskstub_register(context));
|
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_file_register(context);
|
||||||
tf_database_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_trace_set_write_callback(task->_trace, _tf_task_trace_to_parent, task);
|
||||||
}
|
}
|
||||||
|
|
||||||
tf_bcrypt_register(context);
|
|
||||||
tf_util_register(context);
|
tf_util_register(context);
|
||||||
JS_SetPropertyStr(context, global, "exit", JS_NewCFunction(context, _tf_task_exit, "exit", 1));
|
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));
|
JS_SetPropertyStr(context, global, "version", JS_NewCFunction(context, _tf_task_version, "version", 0));
|
||||||
|
|||||||
88
src/tests.c
88
src/tests.c
@@ -549,93 +549,6 @@ static void _test_float(const tf_test_options_t* options)
|
|||||||
unlink("out/child.js");
|
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)
|
static void _test_file(const tf_test_options_t* options)
|
||||||
{
|
{
|
||||||
_write_file("out/test.js",
|
_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, "icu", _test_icu, false);
|
||||||
_tf_test_run(options, "uint8array", _test_uint8array, false);
|
_tf_test_run(options, "uint8array", _test_uint8array, false);
|
||||||
_tf_test_run(options, "float", _test_float, 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, "file", _test_file, false);
|
||||||
_tf_test_run(options, "b64", _test_b64, false);
|
_tf_test_run(options, "b64", _test_b64, false);
|
||||||
_tf_test_run(options, "rooms", tf_ssb_test_rooms, false);
|
_tf_test_run(options, "rooms", tf_ssb_test_rooms, false);
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
|
|
||||||
/** @} */
|
|
||||||
@@ -253,66 +253,6 @@ bool tf_util_report_error(JSContext* context, JSValue value)
|
|||||||
return is_error;
|
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[] = {
|
static const char* k_kind_name[] = {
|
||||||
[k_kind_bool] = "bool",
|
[k_kind_bool] = "bool",
|
||||||
[k_kind_int] = "int",
|
[k_kind_int] = "int",
|
||||||
@@ -359,10 +299,6 @@ static const setting_t k_settings[] = {
|
|||||||
.type = "integer",
|
.type = "integer",
|
||||||
.description = "Blobs older than this will be automatically deleted.",
|
.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 } },
|
.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",
|
{ .name = "http_redirect",
|
||||||
.type = "string",
|
.type = "string",
|
||||||
.description = "If connecting by HTTP and HTTPS is configured, Location header prefix (ie, \"http://example.com\")",
|
.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, "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, "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, "defaultGlobalSettings", JS_NewCFunction(context, _util_defaultGlobalSettings, "defaultGlobalSettings", 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.2025.9"
|
#define VERSION_NUMBER "0.2025.10-wip"
|
||||||
#define VERSION_NAME "This program kills fascists."
|
#define VERSION_NAME "This program kills fascists."
|
||||||
|
|||||||
Reference in New Issue
Block a user