fix: update NetSurf port & montauk frontend to use new paths for fonts and icons
This commit is contained in:
1 parent
6f3cf99667
commit
35e3d1a0bb
21 files changed
+3376
-34
No files matched your search
@@ -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);
|
||||
}
|
||||
Reference in new issue
Block a user