From f4dce0cdd6371d693c6cd195473c9aee90934d04 Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Fri, 7 Aug 2026 21:26:32 +0200 Subject: [PATCH] feat: ports - netsurf with the monkey frontend Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_018Ae69wJS7sueQMUwNxV3nX --- .gitmodules | 3 + Makefile | 1 + mk/port.mk | 29 +++ net-libs/libdom/Makefile | 18 ++ www-client/netsurf/Makefile | 121 +++++++++ www-client/netsurf/compat/arpa/inet.h | 210 ++++++++++++++++ www-client/netsurf/compat/curl/curl.h | 28 +++ www-client/netsurf/compat/iconv.c | 235 ++++++++++++++++++ www-client/netsurf/compat/iconv.h | 41 +++ www-client/netsurf/compat/netinet/in.h | 21 ++ www-client/netsurf/compat/sys/select.h | 107 ++++++++ www-client/netsurf/compat/sys/socket.h | 42 ++++ .../0001-config-detect-montaukos.patch | 30 +++ .../0002-respath-separator-montaukos.patch | 66 +++++ www-client/netsurf/upstream | 1 + 15 files changed, 953 insertions(+) create mode 100644 www-client/netsurf/Makefile create mode 100644 www-client/netsurf/compat/arpa/inet.h create mode 100644 www-client/netsurf/compat/curl/curl.h create mode 100644 www-client/netsurf/compat/iconv.c create mode 100644 www-client/netsurf/compat/iconv.h create mode 100644 www-client/netsurf/compat/netinet/in.h create mode 100644 www-client/netsurf/compat/sys/select.h create mode 100644 www-client/netsurf/compat/sys/socket.h create mode 100644 www-client/netsurf/patches/0001-config-detect-montaukos.patch create mode 100644 www-client/netsurf/patches/0002-respath-separator-montaukos.patch create mode 160000 www-client/netsurf/upstream diff --git a/.gitmodules b/.gitmodules index 26c5932..0b016eb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -40,3 +40,6 @@ [submodule "dev-libs/libutf8proc/upstream"] path = dev-libs/libutf8proc/upstream url = git://git.netsurf-browser.org/libutf8proc.git +[submodule "www-client/netsurf/upstream"] + path = www-client/netsurf/upstream + url = https://github.com/netsurf-browser/netsurf.git diff --git a/Makefile b/Makefile index 189e1eb..31d3973 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,7 @@ PORTS := \ net-libs/libnspsl \ media-libs/libnsgif \ media-libs/libnsbmp \ + www-client/netsurf \ games-fps/doom # Bundle directories are keyed by port name, not category path. diff --git a/mk/port.mk b/mk/port.mk index 00654b5..f275a5d 100644 --- a/mk/port.mk +++ b/mk/port.mk @@ -175,6 +175,24 @@ NS_ENV := \ # Build a NetSurf library in $(1) (its checkout dir) and copy the archive to # $(PORT_ARCHIVE). Usage in a recipe: $(call ns_build,upstream,wapcaplet) +# ==== Patch series ==================================================== +# Most ports need no patches -- a stub header or a build variable is usually +# enough, and both survive an upstream bump for free. Where upstream genuinely +# has to change (platform detection inside its own headers, say), put unified +# diffs in the port's patches/ directory and call ns_patch before building. +# +# The checkout is reset to its pinned commit first, so applying is idempotent +# and the series always lands on a known state. NOTE: this discards any manual +# edits inside the submodule -- edit the patch, not the checkout. +ns_patch = \ + if ls patches/*.patch >/dev/null 2>&1; then \ + git -C $(1) checkout -- . && \ + for p in $(CURDIR)/patches/*.patch; do \ + echo " PATCH: $$(basename $$p)"; \ + git -C $(1) apply "$$p" || exit 1; \ + done; \ + fi + # A port may add its own flags by setting NS_CFLAGS_EXTRA (expanded at call # time, so it can be set after this file is included). ns_build = \ @@ -214,11 +232,22 @@ $(PORT_SYSROOT_INC)/%: $(PORT_HEADER_ROOT)/% ifneq ($(PORT_PC_IN),) INSTALLED_PC := $(PORT_SYSROOT_LIB)/pkgconfig/$(PORT_NAME).pc +# Several .pc.in files carry a LIBRARIES placeholder that upstream's install +# expands to the link flags (Makefile.top: 's#LIBRARIES#$(__libraries)#'). For +# libnsutils, libnslog and libnspsl that placeholder holds the library's OWN +# -l flag, so leaving it unexpanded produces a .pc that names no library at +# all and puts a bare "LIBRARIES" token on the link line. +# +# Default to the port's own -l flag; a port whose placeholder means "my +# dependencies" (libdom) overrides PORT_PC_LIBS. +PORT_PC_LIBS ?= -l$(patsubst lib%,%,$(PORT_NAME)) + $(INSTALLED_PC): $(PORT_PC_IN) Makefile @mkdir -p $(dir $@) sed -e 's|PREFIX|$(PORT_SYSROOT)|' \ -e 's|LIBDIR|lib|' \ -e 's|INCLUDEDIR|include|' \ + -e 's|LIBRARIES|$(PORT_PC_LIBS)|' \ -e 's|VERSION|$(PORT_PC_VERSION)|' $< > $@ endif diff --git a/net-libs/libdom/Makefile b/net-libs/libdom/Makefile index 52fa825..795e1b7 100644 --- a/net-libs/libdom/Makefile +++ b/net-libs/libdom/Makefile @@ -32,6 +32,10 @@ PORT_PC_VERSION := 0.4.2 PORT_HEADER_ROOT := $(UPSTREAM)/include PORT_HEADERS := $(shell find $(UPSTREAM)/include -name '*.h' 2>/dev/null) +# libdom.pc.in already names -ldom itself, so its LIBRARIES placeholder is for +# the dependencies the enabled bindings pull in -- here, the hubbub binding. +PORT_PC_LIBS := -lhubbub -lparserutils -lwapcaplet + include ../../mk/port.mk NS_MAKE_ARGS_EXTRA := \ @@ -39,6 +43,20 @@ NS_MAKE_ARGS_EXTRA := \ WITH_EXPAT_BINDING=no \ WITH_LIBXML_BINDING=no +# The hubbub binding's public headers live under bindings/, not include/, and +# upstream installs them to include/dom/bindings/hubbub/ (see +# bindings/hubbub/Makefile). PORT_HEADERS only walks include/, so stage these +# explicitly -- NetSurf's content/handlers/html includes dom/bindings/hubbub/parser.h. +BINDING_HDRS := \ + $(PORT_SYSROOT_INC)/dom/bindings/hubbub/parser.h \ + $(PORT_SYSROOT_INC)/dom/bindings/hubbub/errors.h + +$(PORT_SYSROOT_INC)/dom/bindings/hubbub/%.h: $(UPSTREAM)/bindings/hubbub/%.h + @mkdir -p $(dir $@) + cp $< $@ + +install-lib: $(BINDING_HDRS) + .PHONY: all clean all: install-lib diff --git a/www-client/netsurf/Makefile b/www-client/netsurf/Makefile new file mode 100644 index 0000000..1951dfe --- /dev/null +++ b/www-client/netsurf/Makefile @@ -0,0 +1,121 @@ +# NetSurf for MontaukOS -- monkey frontend +# +# Upstream: https://github.com/netsurf-browser/netsurf (official mirror of +# git.netsurf-browser.org; submodule at upstream/, pinned). +# +# monkey is NetSurf's headless, script-driven frontend: it drives the full +# browser core (HTML parse -> CSS cascade -> layout) and reports through a +# line protocol on stdout instead of drawing. That makes it the right first +# target -- no graphics, no fonts, and no fetcher required. +# +# CONFIGURATION +# NETSURF_USE_CURL=NO no libcurl port; the built-in file/about/data +# fetchers remain, which is enough for file:// URLs. +# fetch_montauk will register alongside them later. +# NETSURF_USE_OPENSSL=NO reached only through curl. BearSSL is the eventual +# answer, via the fetcher rather than through here. +# NETSURF_USE_DUKTAPE=NO no JavaScript; also avoids the nsgenbind host tool. +# NETSURF_USE_PNG/JPEG/ no ports for these codecs yet. GIF and BMP are on, +# JPEGXL/WEBP=NO since libnsgif and libnsbmp are in the sysroot. +# NETSURF_FS_BACKING_STORE=YES NOT optional for monkey: frontends/monkey/ +# main.c references filesystem_llcache_table +# unconditionally, and that lives in +# content/fs_backing_store.c. The config.h patch +# already tells NetSurf we lack dirfd/unlinkat/ +# fstatat, so it uses its portable fallbacks. +# NETSURF_USE_LIBICONV_PLUG=YES means "iconv is already available, do not +# link -liconv" -- true here, since compat/iconv.c +# provides it inside libnscompat.a. +# +# COMPAT SHIMS (compat/) stand in for headers MontaukOS has no equivalent of: +# sockets, select, inet, and a stub that exists only because +# content/fetch.c includes curl's header unguarded. See those files -- each +# says what it does and does not do. +# +# HOST TOOLS: perl, python3. (gperf is needed by libhubbub, not here.) + +UPSTREAM := upstream +# Absolute: the sub-make runs with -C upstream, so a relative include path +# would resolve against the checkout rather than this port. +COMPAT := $(CURDIR)/compat + +PORT_NAME := netsurf + +include ../../mk/port.mk + +# NetSurf's own Makefile derives HOST from uname and picks its own build +# directory, so BUILDDIR/HOST from port.mk do not apply here. The binary is +# dropped at the top of the checkout, not inside build/. +NS_BUILD_OUT := $(UPSTREAM)/nsmonkey + +# Where the binary and its resources land on MontaukOS. NetSurf cannot start +# without its resources (Messages, default.css, quirks.css ...), so the +# resource path is compiled in and the tree is installed alongside. +NS_RESPATH := 0:/os/netsurf/ +NS_INSTALL := $(MONTAUKOS)/programs/bin/os + +# libutf8proc installs its header to include/libutf8proc/ but its .pc +# advertises only -I${includedir}, while utils/idna.c includes . +# Add the subdirectory here rather than staging a duplicate copy of the header. +NETSURF_CFLAGS := \ + $(PORT_CFLAGS_COMMON) \ + -I $(COMPAT) \ + -I $(PORT_SYSROOT_INC)/libutf8proc + +NETSURF_ARGS := \ + TARGET=monkey \ + NSSHARED=$(NS_BUILDSYSTEM) \ + CC=$(CC) \ + AR=$(AR) \ + NETSURF_USE_CURL=NO \ + NETSURF_USE_OPENSSL=NO \ + NETSURF_USE_DUKTAPE=NO \ + NETSURF_USE_PNG=NO \ + NETSURF_USE_JPEG=NO \ + NETSURF_USE_JPEGXL=NO \ + NETSURF_USE_WEBP=NO \ + NETSURF_USE_VIDEO=NO \ + NETSURF_USE_LIBICONV_PLUG=YES \ + NETSURF_MONKEY_RESOURCES=$(NS_RESPATH) \ + NETSURF_FS_BACKING_STORE=YES + +# compat/iconv.c is the only shim needing a translation unit of its own (the +# rest are header-only). It goes into a small archive placed FIRST in LDFLAGS: +# NetSurf appends its own pkg-config libraries after the environment's value, +# so -lparserutils lands to the right of this archive and resolves the symbols +# it references. +COMPAT_LIB := $(CURDIR)/compat/libnscompat.a + +$(COMPAT_LIB): compat/iconv.c compat/iconv.h + $(CC) $(NETSURF_CFLAGS) -std=gnu99 -c compat/iconv.c -o compat/iconv.o + $(AR) rcs $@ compat/iconv.o + @echo "Built: $@" + +.PHONY: all clean netsurf-build +all: netsurf-build + +netsurf-build: $(COMPAT_LIB) + @$(call ns_patch,$(UPSTREAM)) + CFLAGS="$(NETSURF_CFLAGS)" LDFLAGS="$(COMPAT_LIB)" $(NS_ENV) \ + $(NS_MAKE) -C $(UPSTREAM) $(NETSURF_ARGS) + @test -f $(NS_BUILD_OUT) || { echo "netsurf: expected $(NS_BUILD_OUT)" >&2; exit 1; } + @echo "Built: $(NS_BUILD_OUT) ($$(stat -c%s $(NS_BUILD_OUT)) bytes)" + +# Install as a MontaukOS system program plus its resource tree. Not an app +# bundle: monkey is a console REPL with no GUI, so it carries no manifest and +# no .port marker -- remember to uninstall before cutting a release ISO. +.PHONY: install uninstall +install: netsurf-build + mkdir -p $(NS_INSTALL)/netsurf + cp $(NS_BUILD_OUT) $(NS_INSTALL)/nsmonkey.elf + cp -r $(UPSTREAM)/frontends/monkey/res/. $(NS_INSTALL)/netsurf/ + @echo "installed: 0:/os/nsmonkey.elf + 0:/os/netsurf/ resources" + +uninstall: + rm -rf $(NS_INSTALL)/nsmonkey.elf $(NS_INSTALL)/netsurf + @echo "removed nsmonkey.elf and its resources" + +clean: + @$(NS_ENV) $(NS_MAKE) -C $(UPSTREAM) $(NETSURF_ARGS) clean >/dev/null 2>&1 || true + @git -C $(UPSTREAM) checkout -- . 2>/dev/null || true + rm -rf $(UPSTREAM)/build-Linux-monkey diff --git a/www-client/netsurf/compat/arpa/inet.h b/www-client/netsurf/compat/arpa/inet.h new file mode 100644 index 0000000..e514e8e --- /dev/null +++ b/www-client/netsurf/compat/arpa/inet.h @@ -0,0 +1,210 @@ +/* + * Minimal for the MontaukOS NetSurf port. + * + * NetSurf uses inet_pton() only to recognise IP literals in URLs + * (content/urldb.c, utils/utils.c) -- it never opens a socket with the result. + * A self-contained parser is therefore enough, and keeps this out of the + * MontaukOS libc, where a partial implementation would be misleading. + * + * static inline throughout: these headers are included by a handful of + * translation units and NetSurf builds with -Werror, so a non-static + * definition would either collide at link time or warn when unused. + */ +#ifndef _MONTAUK_COMPAT_ARPA_INET_H_ +#define _MONTAUK_COMPAT_ARPA_INET_H_ + +#include +#include +#include +#include + +/* Parse a dotted-quad into 4 network-order bytes. Returns 1 on success. */ +static inline int montauk_inet_pton4(const char *src, uint8_t *dst) +{ + uint32_t octet = 0; + int digits = 0; + int seen = 0; + uint8_t out[4]; + + for (;;) { + char c = *src++; + + if (c >= '0' && c <= '9') { + if (digits == 3) + return 0; + /* a leading zero is not accepted (023 is ambiguous) */ + if (digits == 1 && out[seen] == 0) + return 0; + octet = octet * 10 + (uint32_t)(c - '0'); + if (octet > 255) + return 0; + out[seen] = (uint8_t)octet; + digits++; + } else if (c == '.' || c == '\0') { + if (digits == 0) + return 0; + if (c == '\0') { + if (seen != 3) + return 0; + memcpy(dst, out, 4); + return 1; + } + if (seen == 3) + return 0; + seen++; + octet = 0; + digits = 0; + out[seen] = 0; + } else { + return 0; + } + } +} + +/* + * Parse an IPv6 literal, including "::" compression and a trailing + * dotted-quad (e.g. ::ffff:192.0.2.1). Returns 1 on success. + */ +static inline int montauk_inet_pton6(const char *src, uint8_t *dst) +{ + uint8_t out[16]; + int head = 0; /* bytes written before "::" */ + int tail = 0; /* bytes buffered after "::" */ + uint8_t tailbuf[16]; + int seen_compress = 0; + uint8_t *cur = out; + int *curlen = &head; + + memset(out, 0, sizeof(out)); + + if (src[0] == ':') { + if (src[1] != ':') + return 0; + src += 2; + seen_compress = 1; + cur = tailbuf; + curlen = &tail; + if (*src == '\0') + goto done; + } + + for (;;) { + uint32_t group = 0; + int digits = 0; + + /* a dotted-quad may only appear as the final component */ + if (strchr(src, '.') != NULL) { + const char *p = src; + int dots = 0; + while (*p != '\0' && *p != ':') { + if (*p == '.') + dots++; + p++; + } + if (dots == 3 && *p == '\0') { + if (*curlen + 4 > 16) + return 0; + if (montauk_inet_pton4(src, cur + *curlen) == 0) + return 0; + *curlen += 4; + break; + } + } + + while (*src != '\0' && *src != ':') { + char c = *src; + uint32_t v; + + if (c >= '0' && c <= '9') + v = (uint32_t)(c - '0'); + else if (c >= 'a' && c <= 'f') + v = (uint32_t)(c - 'a' + 10); + else if (c >= 'A' && c <= 'F') + v = (uint32_t)(c - 'A' + 10); + else + return 0; + + group = group * 16 + v; + if (++digits > 4) + return 0; + src++; + } + + if (digits == 0) + return 0; + if (*curlen + 2 > 16) + return 0; + + cur[(*curlen)++] = (uint8_t)(group >> 8); + cur[(*curlen)++] = (uint8_t)(group & 0xff); + + if (*src == '\0') + break; + + src++; /* consume ':' */ + + if (*src == ':') { + if (seen_compress) + return 0; + seen_compress = 1; + src++; + cur = tailbuf; + curlen = &tail; + if (*src == '\0') + break; + } else if (*src == '\0') { + /* trailing single colon */ + return 0; + } + } + +done: + if (seen_compress) { + if (head + tail > 15) /* "::" must stand for >= 1 zero group */ + return 0; + memmove(out + 16 - tail, tailbuf, (size_t)tail); + memset(out + head, 0, (size_t)(16 - tail - head)); + } else if (head != 16) { + return 0; + } + + memcpy(dst, out, 16); + return 1; +} + +/* + * inet_aton(). Returns non-zero on success, as BSD does. + * + * Only the full dotted-quad form is accepted. Historic inet_aton also takes + * "a", "a.b", "a.b.c" and octal/hex octets, but content/urldb.c immediately + * rejects anything without three dots (see the comment at its call site), so + * the narrower behaviour matches how NetSurf actually uses it -- and refusing + * octal-looking octets like "1.2.3.04" is the safer reading for a URL host. + */ +static inline int inet_aton(const char *cp, struct in_addr *addr) +{ + uint8_t out[4]; + + if (cp == NULL || addr == NULL) + return 0; + if (montauk_inet_pton4(cp, out) != 1) + return 0; + + memcpy(&addr->s_addr, out, 4); + return 1; +} + +static inline int inet_pton(int af, const char *src, void *dst) +{ + if (src == NULL || dst == NULL) + return -1; + + if (af == AF_INET) + return montauk_inet_pton4(src, (uint8_t *) dst); + if (af == AF_INET6) + return montauk_inet_pton6(src, (uint8_t *) dst); + + return -1; /* EAFNOSUPPORT */ +} + +#endif diff --git a/www-client/netsurf/compat/curl/curl.h b/www-client/netsurf/compat/curl/curl.h new file mode 100644 index 0000000..b89a0a3 --- /dev/null +++ b/www-client/netsurf/compat/curl/curl.h @@ -0,0 +1,28 @@ +/* + * Stub for the MontaukOS NetSurf port. + * + * This is NOT a curl implementation and nothing here can perform a fetch. + * + * NetSurf is built with NETSURF_USE_CURL=NO, but that configuration is not + * well exercised upstream: content/fetch.c includes content/fetchers/curl.h + * unconditionally and only guards the *use* of fetch_curl_register() behind + * #ifdef WITH_CURL. That header in turn includes and declares + * `extern CURLM *fetch_curl_multi`, so the type must exist for the + * translation unit to compile even though no curl code is built. + * + * Supplying this stub keeps the port free of patches against upstream. The + * alternative -- guarding the include in content/fetch.c -- is a one-line + * change but starts a patch series that every future NetSurf update has to + * be rebased onto. + * + * When fetch_montauk lands it registers itself the same way fetch_curl would, + * and this file stays exactly as it is. + */ +#ifndef _MONTAUK_COMPAT_CURL_CURL_H_ +#define _MONTAUK_COMPAT_CURL_CURL_H_ + +/* Opaque: only ever named in a declaration that is never defined or used. */ +typedef void CURLM; +typedef void CURL; + +#endif diff --git a/www-client/netsurf/compat/iconv.c b/www-client/netsurf/compat/iconv.c new file mode 100644 index 0000000..46b7afc --- /dev/null +++ b/www-client/netsurf/compat/iconv.c @@ -0,0 +1,235 @@ +/* + * iconv() for the MontaukOS NetSurf port, implemented over libparserutils' + * charset codecs. See iconv.h for why. + * + * Structure: one codec decodes the source charset to UCS-4, a second encodes + * UCS-4 to the destination charset. iconv() pumps a small stack buffer of + * UCS-4 between them so neither side needs an unbounded intermediate. + */ + +#include +#include +#include + +#include +#include + +#include "iconv.h" + +/* UCS-4 code points staged between decode and encode, per inner iteration. */ +#define STAGE_POINTS 64 + +struct montauk_iconv { + parserutils_charset_codec *from; /* source charset -> UCS-4 */ + parserutils_charset_codec *to; /* UCS-4 -> dest charset */ +}; + +/* + * Strip glibc's "//TRANSLIT" / "//IGNORE" suffixes, which libparserutils does + * not understand. Returns a malloc'd bare charset name. + */ +static char *strip_suffix(const char *name) +{ + const char *sep; + size_t len; + char *out; + + if (name == NULL) + return NULL; + + sep = strstr(name, "//"); + len = (sep != NULL) ? (size_t) (sep - name) : strlen(name); + + out = malloc(len + 1); + if (out == NULL) + return NULL; + + memcpy(out, name, len); + out[len] = '\0'; + return out; +} + +static parserutils_charset_codec *make_codec(const char *name) +{ + parserutils_charset_codec *codec = NULL; + parserutils_charset_codec_optparams params; + char *bare; + + bare = strip_suffix(name); + if (bare == NULL) + return NULL; + + if (parserutils_charset_codec_create(bare, &codec) != PARSERUTILS_OK) { + free(bare); + return NULL; + } + free(bare); + + /* + * LOOSE: substitute unrepresentable characters instead of aborting. + * NetSurf renders best-effort; a hard failure mid-document is worse + * than a replacement character. + */ + params.error_mode.mode = PARSERUTILS_CHARSET_CODEC_ERROR_LOOSE; + (void) parserutils_charset_codec_setopt(codec, + PARSERUTILS_CHARSET_CODEC_ERROR_MODE, ¶ms); + + return codec; +} + +iconv_t iconv_open(const char *tocode, const char *fromcode) +{ + struct montauk_iconv *cd; + + cd = calloc(1, sizeof(*cd)); + if (cd == NULL) { + errno = ENOMEM; + return (iconv_t) -1; + } + + cd->from = make_codec(fromcode); + cd->to = make_codec(tocode); + + if (cd->from == NULL || cd->to == NULL) { + if (cd->from != NULL) + parserutils_charset_codec_destroy(cd->from); + if (cd->to != NULL) + parserutils_charset_codec_destroy(cd->to); + free(cd); + errno = EINVAL; /* unsupported conversion */ + return (iconv_t) -1; + } + + return (iconv_t) cd; +} + +int iconv_close(iconv_t handle) +{ + struct montauk_iconv *cd = (struct montauk_iconv *) handle; + + if (cd == NULL || handle == (iconv_t) -1) { + errno = EBADF; + return -1; + } + + parserutils_charset_codec_destroy(cd->from); + parserutils_charset_codec_destroy(cd->to); + free(cd); + return 0; +} + +size_t iconv(iconv_t handle, char **inbuf, size_t *inbytesleft, + char **outbuf, size_t *outbytesleft) +{ + struct montauk_iconv *cd = (struct montauk_iconv *) handle; + size_t converted = 0; + + if (cd == NULL || handle == (iconv_t) -1) { + errno = EBADF; + return (size_t) -1; + } + + /* Reset request: iconv(cd, NULL, NULL, ...) returns to initial state. */ + if (inbuf == NULL || *inbuf == NULL) { + parserutils_charset_codec_reset(cd->from); + parserutils_charset_codec_reset(cd->to); + return 0; + } + + while (*inbytesleft > 0) { + uint8_t stage[STAGE_POINTS * 4]; + const uint8_t *src = (const uint8_t *) *inbuf; + size_t srclen = *inbytesleft; + uint8_t *mid = stage; + size_t midlen = sizeof(stage); + size_t staged; + const uint8_t *midsrc; + size_t midsrclen; + uint8_t *dst; + size_t dstlen; + parserutils_error perr; + + /* ---- source charset -> UCS-4 ---- */ + perr = parserutils_charset_codec_decode(cd->from, + &src, &srclen, &mid, &midlen); + + staged = sizeof(stage) - midlen; + + if (staged == 0) { + /* nothing decoded: classify why and stop */ + if (perr == PARSERUTILS_NEEDDATA) + errno = EINVAL; /* incomplete tail sequence */ + else if (perr == PARSERUTILS_INVALID) + errno = EILSEQ; /* malformed input */ + else if (perr == PARSERUTILS_NOMEM) + errno = E2BIG; + else + errno = EILSEQ; + return (size_t) -1; + } + + /* ---- UCS-4 -> destination charset ---- */ + midsrc = stage; + midsrclen = staged; + dst = (uint8_t *) *outbuf; + dstlen = *outbytesleft; + + perr = parserutils_charset_codec_encode(cd->to, + &midsrc, &midsrclen, &dst, &dstlen); + + /* + * Advance the caller's input only by what was both decoded AND + * encoded. If the encoder ran out of output room mid-stage, + * the undrained UCS-4 must not be counted as consumed -- the + * caller will grow its buffer and call again. + */ + { + size_t encoded_points = (staged - midsrclen) / 4; + size_t consumed_in; + + *outbytesleft = dstlen; + *outbuf = (char *) dst; + + if (midsrclen == 0) { + /* whole stage drained: input consumed as decoded */ + consumed_in = (size_t) ((const char *) src - *inbuf); + } else { + /* + * Partial drain. Re-decode from the original + * position next time; consume nothing this + * round beyond what actually made it out. + */ + if (encoded_points == 0) { + errno = E2BIG; + return (size_t) -1; + } + consumed_in = 0; + } + + if (consumed_in > 0) { + *inbuf += consumed_in; + *inbytesleft -= consumed_in; + } + + converted += encoded_points; + + if (midsrclen != 0) { + /* output was too small to take the rest */ + errno = E2BIG; + return (size_t) -1; + } + } + + if (perr == PARSERUTILS_NOMEM) { + errno = E2BIG; + return (size_t) -1; + } + + if (*outbytesleft == 0 && *inbytesleft > 0) { + errno = E2BIG; + return (size_t) -1; + } + } + + return converted; +} diff --git a/www-client/netsurf/compat/iconv.h b/www-client/netsurf/compat/iconv.h new file mode 100644 index 0000000..79e8db0 --- /dev/null +++ b/www-client/netsurf/compat/iconv.h @@ -0,0 +1,41 @@ +/* + * for the MontaukOS NetSurf port. + * + * MontaukOS has no iconv. Rather than port GNU libiconv, this implements the + * three-function iconv API on top of libparserutils' charset codecs, which are + * already in the ports sysroot. + * + * That is a good fit rather than a bodge: libparserutils decodes a charset to + * UCS-4 and encodes UCS-4 back out, so iconv() is decode-then-encode through a + * UCS-4 staging buffer. The supported set is whatever libparserutils supports + * -- UTF-8, UTF-16, ASCII, ISO-8859-x and the Windows-125x range -- which is + * exactly the set the HTML parser can handle anyway, so nothing is lost that + * would otherwise have worked. + * + * NetSurf touches iconv only from utils/utf8.c. + * + * NOT SUPPORTED: the "//TRANSLIT" and "//IGNORE" suffixes glibc accepts. They + * are stripped and ignored; conversion uses libparserutils' LOOSE error mode, + * which substitutes rather than failing. + */ +#ifndef _MONTAUK_COMPAT_ICONV_H_ +#define _MONTAUK_COMPAT_ICONV_H_ + +#include + +typedef void *iconv_t; + +#ifdef __cplusplus +extern "C" { +#endif + +iconv_t iconv_open(const char *tocode, const char *fromcode); +size_t iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, + char **outbuf, size_t *outbytesleft); +int iconv_close(iconv_t cd); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/www-client/netsurf/compat/netinet/in.h b/www-client/netsurf/compat/netinet/in.h new file mode 100644 index 0000000..614748b --- /dev/null +++ b/www-client/netsurf/compat/netinet/in.h @@ -0,0 +1,21 @@ +/* + * Minimal for the MontaukOS NetSurf port. + * Only the address structures urldb.c and utils.c use for IP-literal parsing. + */ +#ifndef _MONTAUK_COMPAT_NETINET_IN_H_ +#define _MONTAUK_COMPAT_NETINET_IN_H_ + +#include + +struct in_addr { + uint32_t s_addr; +}; + +struct in6_addr { + uint8_t s6_addr[16]; +}; + +#define INET_ADDRSTRLEN 16 +#define INET6_ADDRSTRLEN 46 + +#endif diff --git a/www-client/netsurf/compat/sys/select.h b/www-client/netsurf/compat/sys/select.h new file mode 100644 index 0000000..fc10683 --- /dev/null +++ b/www-client/netsurf/compat/sys/select.h @@ -0,0 +1,107 @@ +/* + * Minimal for the MontaukOS NetSurf port. + * + * MontaukOS has no select(). Readiness on a socket is expressed through the + * kernel's waitset (SYS_WAITSET_*), which is a different shape entirely, and + * stdio descriptors are not waitable objects at all. + * + * NetSurf's core uses select() in exactly one place -- content/fetch.c's + * fetch_fdset() collects descriptors from the registered fetchers, and the + * frontend's main loop selects over them plus its own input. With + * NETSURF_USE_CURL=NO no fetcher registers a descriptor, so the only fd that + * ever appears is the monkey frontend's stdin (fd 0). + * + * That makes a useful shim possible: + * + * - if any read descriptor is requested, report them ready immediately and + * let the caller block in read(). monkey is a line-driven REPL, so + * blocking on stdin is what it wants anyway. + * - otherwise sleep for the timeout so an idle loop does not spin. + * + * LIMITATION: because a requested read fd is always reported ready, scheduled + * callbacks do not fire while the process sits idle waiting for a command. + * That is fine for driving monkey by hand or by script, and it is wrong for + * anything with real network activity. When fetch_montauk lands it will + * register descriptors here and this needs replacing with a genuine + * waitset-backed implementation. + */ +#ifndef _MONTAUK_COMPAT_SYS_SELECT_H_ +#define _MONTAUK_COMPAT_SYS_SELECT_H_ + +#include +#include +#include + +#define FD_SETSIZE 64 + +typedef struct { + unsigned long fds_bits[(FD_SETSIZE + (8 * sizeof(unsigned long)) - 1) / + (8 * sizeof(unsigned long))]; +} fd_set; + +#define __NFDBITS (8 * (int) sizeof(unsigned long)) + +#define FD_ZERO(s) memset((s), 0, sizeof(fd_set)) +#define FD_SET(fd, s) \ + ((void) ((s)->fds_bits[(fd) / __NFDBITS] |= \ + (1UL << ((fd) % __NFDBITS)))) +#define FD_CLR(fd, s) \ + ((void) ((s)->fds_bits[(fd) / __NFDBITS] &= \ + ~(1UL << ((fd) % __NFDBITS)))) +#define FD_ISSET(fd, s) \ + (((s)->fds_bits[(fd) / __NFDBITS] & (1UL << ((fd) % __NFDBITS))) != 0) + +static inline int montauk_fdset_empty(const fd_set *s) +{ + unsigned i; + + if (s == NULL) + return 1; + for (i = 0; i < sizeof(s->fds_bits) / sizeof(s->fds_bits[0]); i++) { + if (s->fds_bits[i] != 0) + return 0; + } + return 1; +} + +static inline int montauk_fdset_count(const fd_set *s, int nfds) +{ + int fd, n = 0; + + if (s == NULL) + return 0; + for (fd = 0; fd < nfds && fd < FD_SETSIZE; fd++) { + if (FD_ISSET(fd, s)) + n++; + } + return n; +} + +static inline int select(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, struct timeval *timeout) +{ + int ready; + + if (writefds != NULL) + FD_ZERO(writefds); + if (exceptfds != NULL) + FD_ZERO(exceptfds); + + if (montauk_fdset_empty(readfds) == 0) { + /* leave readfds as-is: every requested fd is "ready" */ + return montauk_fdset_count(readfds, nfds); + } + + ready = 0; + + if (timeout != NULL) { + unsigned long usec = (unsigned long) timeout->tv_sec * 1000000UL + + (unsigned long) timeout->tv_usec; + if (usec != 0) + usleep(usec); + } + + return ready; +} + +#endif diff --git a/www-client/netsurf/compat/sys/socket.h b/www-client/netsurf/compat/sys/socket.h new file mode 100644 index 0000000..02a64aa --- /dev/null +++ b/www-client/netsurf/compat/sys/socket.h @@ -0,0 +1,42 @@ +/* + * Minimal for the MontaukOS NetSurf port. + * + * NetSurf includes this via utils/inet.h. MontaukOS has no BSD socket layer + * (the kernel exposes sockets as IPC handles through montauk::socket), so this + * supplies only the few declarations NetSurf's non-curl paths actually touch: + * the address families and socklen_t. Nothing here opens a socket. + */ +#ifndef _MONTAUK_COMPAT_SYS_SOCKET_H_ +#define _MONTAUK_COMPAT_SYS_SOCKET_H_ + +#include +#include + +#define AF_UNSPEC 0 +#define AF_INET 2 +#define AF_INET6 10 + +typedef uint32_t socklen_t; + +#define SOCK_STREAM 1 +#define SOCK_DGRAM 2 + +/* + * socket() exists only so desktop/gui_factory.c compiles: it installs + * gui_default_socket_open() into the fetch table, and that default is reached + * only by a fetcher that opens sockets -- i.e. curl, which is not built here. + * + * It deliberately FAILS rather than pretending. MontaukOS sockets are IPC + * handles obtained through montauk::socket() and are not interchangeable with + * BSD descriptors, so any caller reaching this wants the real fetcher instead. + * fetch_montauk will provide its own socket_open in the fetch table. + */ +static inline int socket(int domain, int type, int protocol) +{ + (void) domain; + (void) type; + (void) protocol; + return -1; +} + +#endif diff --git a/www-client/netsurf/patches/0001-config-detect-montaukos.patch b/www-client/netsurf/patches/0001-config-detect-montaukos.patch new file mode 100644 index 0000000..c8b7008 --- /dev/null +++ b/www-client/netsurf/patches/0001-config-detect-montaukos.patch @@ -0,0 +1,30 @@ +diff --git a/utils/config.h b/utils/config.h +index 3914771fd..09f2659d6 100644 +--- a/utils/config.h ++++ b/utils/config.h +@@ -119,7 +119,7 @@ char *strchrnul(const char *s, int c); + #endif + + #define HAVE_UTSNAME +-#if (defined(_WIN32)) ++#if (defined(_WIN32) || defined(__montauk__)) + #undef HAVE_UTSNAME + #endif + +@@ -158,14 +158,14 @@ char *realpath(const char *path, char *resolved_path); + #define HAVE_DIRFD + #define HAVE_UNLINKAT + #define HAVE_FSTATAT +-#if (defined(_WIN32) || defined(__riscos__) || defined(__HAIKU__) || defined(__BEOS__) || defined(__amigaos4__) || defined(__AMIGA__) || defined(__MINT__)) ++#if (defined(_WIN32) || defined(__riscos__) || defined(__HAIKU__) || defined(__BEOS__) || defined(__amigaos4__) || defined(__AMIGA__) || defined(__MINT__) || defined(__montauk__)) + #undef HAVE_DIRFD + #undef HAVE_UNLINKAT + #undef HAVE_FSTATAT + #endif + + #define HAVE_REGEX +-#if (defined(__serenity__)) ++#if (defined(__serenity__) || defined(__montauk__)) + #undef HAVE_REGEX + #endif + diff --git a/www-client/netsurf/patches/0002-respath-separator-montaukos.patch b/www-client/netsurf/patches/0002-respath-separator-montaukos.patch new file mode 100644 index 0000000..358aee0 --- /dev/null +++ b/www-client/netsurf/patches/0002-respath-separator-montaukos.patch @@ -0,0 +1,66 @@ +diff --git a/frontends/monkey/main.c b/frontends/monkey/main.c +index 463f0bea6..578d148e4 100644 +--- a/frontends/monkey/main.c ++++ b/frontends/monkey/main.c +@@ -422,7 +422,12 @@ main(int argc, char **argv) + setbuf(stderr, NULL); + + /* Prep the search paths */ ++#if defined(__montauk__) ++ /* see NS_RESPATH_SEP: ':' is part of a MontaukOS drive path */ ++ respaths = nsmonkey_init_resource("${HOME}/.netsurf/;${NETSURFRES};"MONKEY_RESPATH";./frontends/monkey/res"); ++#else + respaths = nsmonkey_init_resource("${HOME}/.netsurf/:${NETSURFRES}:"MONKEY_RESPATH":./frontends/monkey/res"); ++#endif + + /* initialise logging. Not fatal if it fails but not much we can do + * about it either. +diff --git a/utils/filepath.c b/utils/filepath.c +index b87e2bf0d..9c4bcd34b 100644 +--- a/utils/filepath.c ++++ b/utils/filepath.c +@@ -325,7 +325,7 @@ filepath_path_to_strvec(const char *path) + estart = eend = path; + + while (strc < (MAX_RESPATH - 2)) { +- while ( (*eend != 0) && (*eend != ':') ) ++ while ( (*eend != 0) && (*eend != NS_RESPATH_SEP) ) + eend++; + elen = eend - estart; + +@@ -339,7 +339,7 @@ filepath_path_to_strvec(const char *path) + } + + /* skip colons */ +- while (*eend == ':') ++ while (*eend == NS_RESPATH_SEP) + eend++; + + /* check for termination */ +diff --git a/utils/filepath.h b/utils/filepath.h +index 784264b33..469830a9d 100644 +--- a/utils/filepath.h ++++ b/utils/filepath.h +@@ -24,6 +24,22 @@ + #ifndef _NETSURF_UTILS_FILEPATH_H_ + #define _NETSURF_UTILS_FILEPATH_H_ + ++/** ++ * Character separating elements of a resource path list. ++ * ++ * A colon cannot double as the list separator on platforms whose absolute ++ * paths already contain one -- MontaukOS names drives as "N:/dir", so ++ * "0:/os/netsurf/" would split into "0" and "/os/netsurf/", neither of which ++ * resolves. ++ */ ++#ifndef NS_RESPATH_SEP ++#if defined(__montauk__) ++#define NS_RESPATH_SEP ';' ++#else ++#define NS_RESPATH_SEP ':' ++#endif ++#endif ++ + #include + + #include "utils/errors.h" diff --git a/www-client/netsurf/upstream b/www-client/netsurf/upstream new file mode 160000 index 0000000..39da3c3 --- /dev/null +++ b/www-client/netsurf/upstream @@ -0,0 +1 @@ +Subproject commit 39da3c3a40af4566d86500ff3052dfdc7f9a0378