fix: update NetSurf port & montauk frontend to use new paths for fonts and icons

This commit is contained in:
daniel committed 2026-09-30 08:47:44 +02:00
1 parent 6f3cf99667
commit 35e3d1a0bb
21 files changed
+3376 -34

No files matched your search

+477
View File
@@ -0,0 +1,477 @@
/* Native HTTP/HTTPS fetcher for MontaukOS. The OS exposes TCP and DNS as
* syscalls rather than BSD sockets; HTTPS is provided by the OS BearSSL
* helper and its system certificate bundle. */
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <montauk.h>
#include <libwapcaplet/libwapcaplet.h>
#include "utils/corestrings.h"
#include "utils/log.h"
#include "utils/nsurl.h"
#include "utils/ring.h"
#include "content/fetch.h"
#include "content/fetchers.h"
#include "content/fetchers/montauk.h"
#include "content/fetchers/montauk_bridge.h"
#define RESPONSE_LIMIT (8u * 1024u * 1024u)
#define IO_TIMEOUT_MS 30000u
struct fetch_montauk_context {
struct fetch *parent;
nsurl *url;
char *post;
char **headers;
volatile bool aborted;
bool locked;
volatile int worker_state; /* 0 queued, 1 running, 2 complete */
int worker_tid;
void *worker_stack;
char *response;
size_t response_len;
const char *transport_error;
struct fetch_montauk_context *r_next, *r_prev;
};
static struct fetch_montauk_context *ring;
static bool worker_busy;
static void callback(struct fetch_montauk_context *ctx, const fetch_msg *msg)
{
ctx->locked = true;
fetch_send_callback(msg, ctx->parent);
ctx->locked = false;
}
static void fail(struct fetch_montauk_context *ctx, const char *reason)
{
fetch_msg msg = { .type = FETCH_ERROR };
msg.data.error = reason;
callback(ctx, &msg);
}
static bool send_all(int fd, const uint8_t *data, size_t len)
{
size_t off = 0;
while (off < len) {
int n = mtk_send(fd, data + off, len - off);
if (n <= 0) return false;
off += (size_t)n;
}
return true;
}
static bool append(char **buf, size_t *len, size_t *cap,
const void *data, size_t amount)
{
if (amount > RESPONSE_LIMIT - *len) return false;
if (*len + amount + 1 > *cap) {
size_t next = *cap ? *cap * 2 : 32768;
while (next < *len + amount + 1) next *= 2;
if (next > RESPONSE_LIMIT + 1) next = RESPONSE_LIMIT + 1;
char *replacement = realloc(*buf, next);
if (replacement == NULL) return false;
*buf = replacement;
*cap = next;
}
memcpy(*buf + *len, data, amount);
*len += amount;
(*buf)[*len] = 0;
return true;
}
static bool read_response(struct fetch_montauk_context *ctx, int fd,
char **response, size_t *response_len)
{
char chunk[8192];
size_t cap = 0;
uint64_t deadline = mtk_get_ms() + IO_TIMEOUT_MS;
*response = NULL;
*response_len = 0;
for (;;) {
if (ctx->aborted) return false;
int n = mtk_recv(fd, chunk, sizeof(chunk));
if (n > 0) {
if (!append(response, response_len, &cap, chunk, (size_t)n))
return false;
deadline = mtk_get_ms() + IO_TIMEOUT_MS;
} else if (n < 0) {
return *response_len != 0;
} else {
if (mtk_get_ms() >= deadline) return false;
mtk_sleep_ms(1);
}
}
}
static char *find_header_end(char *response)
{
char *p = strstr(response, "\r\n\r\n");
return p == NULL ? NULL : p + 4;
}
static bool header_value(const char *headers, const char *name,
char *value, size_t value_size)
{
size_t name_len = strlen(name);
const char *first_end = strstr(headers, "\r\n");
const char *line = first_end == NULL ? NULL : first_end + 2;
while (line != NULL && *line != '\r' && *line != 0) {
const char *end = strstr(line, "\r\n");
if (end == NULL) break;
if ((size_t)(end - line) > name_len &&
strncasecmp(line, name, name_len) == 0 && line[name_len] == ':') {
const char *start = line + name_len + 1;
size_t len;
while (*start == ' ' || *start == '\t') start++;
len = (size_t)(end - start);
if (len >= value_size) len = value_size - 1;
memcpy(value, start, len);
value[len] = 0;
return true;
}
line = end + 2;
}
return false;
}
static bool dechunk(char *body, size_t body_len, size_t *decoded_len)
{
char *src = body, *dst = body, *limit = body + body_len;
while (src < limit) {
char *line_end = strstr(src, "\r\n");
char *endptr;
unsigned long amount;
if (line_end == NULL || line_end >= limit) return false;
*line_end = 0;
amount = strtoul(src, &endptr, 16);
if (endptr == src) return false;
src = line_end + 2;
if (amount == 0) {
*decoded_len = (size_t)(dst - body);
return true;
}
if (amount > (unsigned long)(limit - src) ||
amount + 2 > (unsigned long)(limit - src)) return false;
memmove(dst, src, amount);
dst += amount;
src += amount;
if (src[0] != '\r' || src[1] != '\n') return false;
src += 2;
}
return false;
}
static bool transport(struct fetch_montauk_context *ctx)
{
char *scheme = NULL, *host = NULL, *port_text = NULL, *path = NULL;
size_t ignored;
uint16_t port = 80;
bool https = false;
uint32_t ip;
int fd = -1;
char *request = NULL, *response = NULL;
size_t request_len = 0, request_cap = 0, response_len = 0;
char line[1024];
bool ok = false;
if (nsurl_get(ctx->url, NSURL_SCHEME, &scheme, &ignored) != NSERROR_OK ||
nsurl_get(ctx->url, NSURL_HOST, &host, &ignored) != NSERROR_OK ||
nsurl_get(ctx->url, NSURL_PATH | NSURL_QUERY, &path, &ignored) != NSERROR_OK) {
ctx->transport_error = "Invalid HTTP(S) URL";
goto done;
}
https = strcasecmp(scheme, "https") == 0;
port = https ? 443 : 80;
if (nsurl_has_component(ctx->url, NSURL_PORT) &&
nsurl_get(ctx->url, NSURL_PORT, &port_text, &ignored) == NSERROR_OK)
port = (uint16_t)strtoul(port_text, NULL, 10);
if (*path == 0) { free(path); path = strdup("/"); }
ip = mtk_resolve(host);
if (ip == 0) { ctx->transport_error = "DNS resolution failed"; goto done; }
if (!https) {
fd = mtk_socket(MTK_SOCK_TCP);
if (fd < 0 || mtk_connect(fd, ip, port) < 0) {
ctx->transport_error = "Could not connect to server"; goto done;
}
}
int n = snprintf(line, sizeof(line), "%s %s HTTP/1.1\r\nHost: %s%s%s\r\nUser-Agent: NetSurf-MontaukOS\r\nAccept: */*\r\nAccept-Encoding: identity\r\nConnection: close\r\n",
ctx->post ? "POST" : "GET", path, host,
port == (https ? 443 : 80) ? "" : ":",
port == (https ? 443 : 80) ? "" : (port_text ? port_text : (https ? "443" : "80")));
if (n < 0 || !append(&request, &request_len, &request_cap, line, (size_t)n)) goto oom;
if (ctx->headers != NULL) {
for (char **h = ctx->headers; *h != NULL; h++) {
if (!append(&request, &request_len, &request_cap, *h, strlen(*h)) ||
!append(&request, &request_len, &request_cap, "\r\n", 2)) goto oom;
}
}
if (ctx->post != NULL) {
n = snprintf(line, sizeof(line), "Content-Type: application/x-www-form-urlencoded\r\nContent-Length: %zu\r\n", strlen(ctx->post));
if (n < 0 || !append(&request, &request_len, &request_cap, line, (size_t)n)) goto oom;
}
if (!append(&request, &request_len, &request_cap, "\r\n", 2) ||
(ctx->post != NULL && !append(&request, &request_len, &request_cap, ctx->post, strlen(ctx->post)))) goto oom;
if (https) {
response = malloc(RESPONSE_LIMIT + 1);
if (response == NULL) goto oom;
int received = mtk_netsurf_https_fetch(host, ip, port, request,
request_len, response, RESPONSE_LIMIT + 1);
if (received <= 0) {
ctx->transport_error = "HTTPS transport or certificate verification failed";
goto done;
}
if ((size_t)received >= RESPONSE_LIMIT) {
ctx->transport_error = "HTTP response exceeds size limit";
goto done;
}
response_len = (size_t)received;
response[response_len] = 0;
} else if (!send_all(fd, (const uint8_t *)request, request_len) ||
!read_response(ctx, fd, &response, &response_len)) {
ctx->transport_error = ctx->aborted ? "Request cancelled" : "HTTP transport failed";
goto done;
}
ctx->response = response;
ctx->response_len = response_len;
response = NULL;
ok = true;
goto done;
oom:
ctx->transport_error = "Out of memory while fetching HTTP URL";
done:
if (fd >= 0) mtk_closesocket(fd);
free(scheme); free(host); free(port_text); free(path); free(request); free(response);
return ok;
}
static bool deliver_response(struct fetch_montauk_context *ctx)
{
char *response = ctx->response;
size_t response_len = ctx->response_len, body_len;
char *body;
char location[2048], transfer[64];
int status;
fetch_msg msg;
bool ok = false;
body = find_header_end(response);
char *status_space = strncmp(response, "HTTP/", 5) == 0 ? strchr(response, ' ') : NULL;
if (body == NULL || status_space == NULL) {
fail(ctx, "Malformed HTTP response"); goto done;
}
status = (int)strtoul(status_space + 1, NULL, 10);
if (status < 100 || status > 599) {
fail(ctx, "Malformed HTTP status"); goto done;
}
body_len = response_len - (size_t)(body - response);
fetch_set_http_code(ctx->parent, status);
if (status >= 300 && status < 400 &&
header_value(response, "Location", location, sizeof(location))) {
msg.type = FETCH_REDIRECT;
msg.data.redirect = location;
callback(ctx, &msg);
ok = true;
goto done;
}
/* header_value temporarily terminates its matching line; find the header
* boundary again only after looking up all values we need. */
if (header_value(response, "Transfer-Encoding", transfer, sizeof(transfer)) &&
strcasecmp(transfer, "chunked") == 0 &&
!dechunk(body, body_len, &body_len)) {
fail(ctx, "Malformed chunked HTTP response"); goto done;
}
/* Match libcurl's header callback contract exactly: the status line, each
* header, and the terminating blank line arrive separately and retain
* their CRLF. llcache uses the status and blank lines to drive its header
* state machine; omitting either makes an otherwise valid fetch fail. */
char *h = response;
while (h != NULL && h < body && !ctx->aborted) {
char *end = strstr(h, "\r\n");
if (end == NULL || end > body) break;
msg.type = FETCH_HEADER;
msg.data.header_or_data.buf = (const uint8_t *)h;
msg.data.header_or_data.len = (size_t)(end - h) + 2;
callback(ctx, &msg);
h = end + 2;
}
if (!ctx->aborted && body_len != 0) {
msg.type = FETCH_DATA;
msg.data.header_or_data.buf = (const uint8_t *)body;
msg.data.header_or_data.len = body_len;
callback(ctx, &msg);
}
if (!ctx->aborted) {
msg.type = FETCH_FINISHED;
callback(ctx, &msg);
}
ok = true;
goto done;
done:
return ok;
}
static void worker_entry(struct fetch_montauk_context *ctx)
{
transport(ctx);
__atomic_store_n(&ctx->worker_state, 2, __ATOMIC_RELEASE);
_mtk_syscall1(MTK_SYS_THREAD_EXIT, 0);
__builtin_unreachable();
}
static bool start_worker(struct fetch_montauk_context *ctx)
{
const size_t stack_size = 256u * 1024u;
ctx->worker_stack = malloc(stack_size);
if (ctx->worker_stack == NULL) return false;
uintptr_t top = ((uintptr_t)ctx->worker_stack + stack_size) & ~(uintptr_t)15;
ctx->worker_state = 1;
ctx->worker_tid = (int)_mtk_syscall3(MTK_SYS_THREAD_SPAWN,
(long)worker_entry, (long)ctx, (long)top);
if (ctx->worker_tid < 0) {
ctx->worker_state = 0;
free(ctx->worker_stack);
ctx->worker_stack = NULL;
return false;
}
worker_busy = true;
return true;
}
static void join_worker(struct fetch_montauk_context *ctx)
{
if (ctx->worker_tid > 0) {
_mtk_syscall2(MTK_SYS_THREAD_JOIN, ctx->worker_tid, 0);
ctx->worker_tid = 0;
}
free(ctx->worker_stack);
ctx->worker_stack = NULL;
worker_busy = false;
}
static bool initialise(lwc_string *scheme) { (void)scheme; return true; }
static void finalise(lwc_string *scheme) { (void)scheme; }
static bool acceptable(const nsurl *url) { (void)url; return true; }
static void *setup(struct fetch *parent, nsurl *url, bool only_2xx,
bool downgrade_tls, const char *post_urlenc,
const struct fetch_multipart_data *post_multipart, const char **headers)
{
struct fetch_montauk_context *ctx = calloc(1, sizeof(*ctx));
(void)only_2xx; (void)downgrade_tls; (void)post_multipart;
if (ctx == NULL) return NULL;
ctx->parent = parent;
ctx->url = nsurl_ref(url);
ctx->post = post_urlenc == NULL ? NULL : strdup(post_urlenc);
if (post_urlenc != NULL && ctx->post == NULL) {
nsurl_unref(ctx->url);
free(ctx);
return NULL;
}
if (headers != NULL) {
size_t count = 0;
while (headers[count] != NULL) count++;
ctx->headers = calloc(count + 1, sizeof(*ctx->headers));
if (ctx->headers == NULL) {
nsurl_unref(ctx->url); free(ctx->post); free(ctx);
return NULL;
}
for (size_t i = 0; i < count; i++) {
ctx->headers[i] = strdup(headers[i]);
if (ctx->headers[i] == NULL) {
while (i != 0) free(ctx->headers[--i]);
free(ctx->headers); nsurl_unref(ctx->url);
free(ctx->post); free(ctx);
return NULL;
}
}
}
RING_INSERT(ring, ctx);
return ctx;
}
static bool start(void *ctx) { (void)ctx; return true; }
static void abort_fetch(void *vctx) { ((struct fetch_montauk_context *)vctx)->aborted = true; }
static void free_fetch(void *vctx)
{
struct fetch_montauk_context *ctx = vctx;
if (__atomic_load_n(&ctx->worker_state, __ATOMIC_ACQUIRE) == 1) {
ctx->aborted = true;
_mtk_syscall2(MTK_SYS_THREAD_JOIN, ctx->worker_tid, 0);
ctx->worker_tid = 0;
free(ctx->worker_stack);
ctx->worker_stack = NULL;
worker_busy = false;
} else if (ctx->worker_tid > 0) {
join_worker(ctx);
}
nsurl_unref(ctx->url);
free(ctx->post);
free(ctx->response);
if (ctx->headers != NULL) {
for (size_t i = 0; ctx->headers[i] != NULL; i++)
free(ctx->headers[i]);
free(ctx->headers);
}
free(ctx);
}
static void poll_fetch(lwc_string *scheme)
{
struct fetch_montauk_context *ctx, *saved = NULL;
(void)scheme;
while (ring != NULL) {
ctx = ring;
RING_REMOVE(ring, ctx);
if (ctx->locked) { RING_INSERT(saved, ctx); continue; }
int state = __atomic_load_n(&ctx->worker_state, __ATOMIC_ACQUIRE);
if (state == 2) {
join_worker(ctx);
if (!ctx->aborted) {
if (ctx->transport_error != NULL) fail(ctx, ctx->transport_error);
else deliver_response(ctx);
}
assert(!ctx->locked);
fetch_remove_from_queues(ctx->parent);
fetch_free(ctx->parent);
} else if (state == 0 && ctx->aborted) {
fetch_remove_from_queues(ctx->parent);
fetch_free(ctx->parent);
} else if (state == 0 && !worker_busy) {
if (start_worker(ctx)) {
RING_INSERT(saved, ctx);
} else {
fail(ctx, "Could not start network worker");
fetch_remove_from_queues(ctx->parent);
fetch_free(ctx->parent);
}
} else {
RING_INSERT(saved, ctx);
}
}
ring = saved;
}
nserror fetch_montauk_register(void)
{
const struct fetcher_operation_table ops = {
.initialise = initialise, .acceptable = acceptable, .setup = setup,
.start = start, .abort = abort_fetch, .free = free_fetch,
.poll = poll_fetch, .finalise = finalise
};
nserror err = fetcher_add(lwc_string_ref(corestring_lwc_http), &ops);
if (err != NSERROR_OK) return err;
return fetcher_add(lwc_string_ref(corestring_lwc_https), &ops);
}