mirror of
https://github.com/subhobhai943/SUB-OS.git
synced 2026-10-25 16:52:29 +00:00
feat(net,gui): real internet access -- redirects, HTTPS guard, netcheck, and a live ONLINE indicator
The stack could already reach the real internet once the UDP-checksum and
scheduler fixes landed; this makes it work on real-world URLs and gives the
desktop a first-class "is the internet up?" answer.
HTTP client
- Follows 3xx redirects (Location header, absolute or relative) up to five
hops, so a bare host that 301s to a path or a canonical host resolves to
actual content. Verified: `webget google.com/` follows the 301 to
www.google.com and returns its HTTP 200 homepage.
- Refuses https:// targets -- at the initial URL and on redirect -- with a
clear message instead of speaking plaintext to port 443 and timing out.
- Split the URL parser out so both the initial request and redirect targets
go through the same path; added a case-insensitive header finder.
netcheck (CLI)
One command that walks the whole chain -- link, address, gateway ping, DNS,
and a real HTTP fetch -- and reports each PASS/FAIL, ending in ONLINE or
DEGRADED. The one-shot answer to "is the internet working?".
Network Monitor (GUI)
A live Internet: ONLINE/OFFLINE badge with the resolved IP, HTTP status and
latency. It auto-probes once on open and re-checks on the Check button, all
through the async HTTP worker so the window never blocks.
Verified against the real internet through QEMU's NAT: DNS resolved
example.com to a live Cloudflare address; `netcheck` passed all five layers
(Internet: ONLINE); the GUI badge showed "ONLINE 104.20.23.154 HTTP 200 240
ms"; redirect following and the HTTPS refusal both behaved. Builds clean on
x86_64, aarch64 and armv8i.
Known limit: no TLS, so HTTPS URLs are reported rather than fetched -- plain
HTTP only.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
This commit is contained in:
1 parent
dd4922767a
commit
f6e4511047
4 files changed
+308
-33
No files matched your search
+143
-28
@@ -37,41 +37,119 @@ static http_fetch_t* volatile mailbox = NULL; // job awaiting the worker
|
||||
// Anything it cannot make sense of leaves the job unusable and is reported as
|
||||
// an error rather than guessed at.
|
||||
// ===========================================================================
|
||||
static void parse_url(const char* url, http_fetch_t* f) {
|
||||
f->host[0] = '\0';
|
||||
strcpy(f->path, "/");
|
||||
f->port = 80;
|
||||
// Parse an absolute URL into its parts. Returns false only when there is no
|
||||
// host to be found. `is_https` reports the scheme so the caller can refuse a
|
||||
// TLS target cleanly rather than speaking plaintext to port 443.
|
||||
static bool split_url(const char* url, char* host, size_t host_cap,
|
||||
char* path, size_t path_cap, uint16_t* port, bool* is_https) {
|
||||
host[0] = '\0';
|
||||
strcpy(path, "/");
|
||||
*port = 80;
|
||||
*is_https = false;
|
||||
|
||||
if (!url) return;
|
||||
if (strncmp(url, "http://", 7) == 0) url += 7;
|
||||
else if (strncmp(url, "https://", 8) == 0) url += 8; // no TLS; try plain
|
||||
if (!url) return false;
|
||||
if (strncmp(url, "http://", 7) == 0) {
|
||||
url += 7;
|
||||
} else if (strncmp(url, "https://", 8) == 0) {
|
||||
url += 8;
|
||||
*is_https = true;
|
||||
*port = 443;
|
||||
}
|
||||
|
||||
// Host (and optional :port) up to the first '/'.
|
||||
int i = 0;
|
||||
char hostport[192];
|
||||
int i = 0;
|
||||
while (url[i] && url[i] != '/' && i < (int)sizeof(hostport) - 1) {
|
||||
hostport[i] = url[i];
|
||||
i++;
|
||||
}
|
||||
hostport[i] = '\0';
|
||||
|
||||
const char* rest = url + i; // "" or "/path..."
|
||||
|
||||
// Split host:port.
|
||||
char* colon = strchr(hostport, ':');
|
||||
if (colon) {
|
||||
*colon = '\0';
|
||||
int p = 0;
|
||||
for (const char* c = colon + 1; *c >= '0' && *c <= '9'; c++) p = p * 10 + (*c - '0');
|
||||
if (p > 0 && p < 65536) f->port = (uint16_t)p;
|
||||
if (p > 0 && p < 65536) *port = (uint16_t)p;
|
||||
}
|
||||
strncpy(f->host, hostport, sizeof(f->host) - 1);
|
||||
f->host[sizeof(f->host) - 1] = '\0';
|
||||
strncpy(host, hostport, host_cap - 1);
|
||||
host[host_cap - 1] = '\0';
|
||||
|
||||
if (rest[0] == '/') {
|
||||
strncpy(f->path, rest, sizeof(f->path) - 1);
|
||||
f->path[sizeof(f->path) - 1] = '\0';
|
||||
strncpy(path, rest, path_cap - 1);
|
||||
path[path_cap - 1] = '\0';
|
||||
}
|
||||
return host[0] != '\0';
|
||||
}
|
||||
|
||||
static void parse_url(const char* url, http_fetch_t* f) {
|
||||
split_url(url, f->host, sizeof(f->host), f->path, sizeof(f->path), &f->port, &f->tls);
|
||||
}
|
||||
|
||||
// Find a header's value in the response, case-insensitively. Copies the value
|
||||
// (trimmed, up to the CRLF) into out. Returns false if the header is absent.
|
||||
static bool find_header(const char* buf, int len, const char* name,
|
||||
char* out, size_t out_cap) {
|
||||
size_t nlen = strlen(name);
|
||||
// Scan line by line; the status line is skipped naturally (no match).
|
||||
for (int i = 0; i + (int)nlen < len; i++) {
|
||||
if (i != 0 && buf[i - 1] != '\n') continue; // only at a line start
|
||||
|
||||
// Case-insensitive compare of the header name.
|
||||
size_t k = 0;
|
||||
while (k < nlen) {
|
||||
char a = buf[i + k], b = name[k];
|
||||
if (a >= 'A' && a <= 'Z') a = (char)(a - 'A' + 'a');
|
||||
if (b >= 'A' && b <= 'Z') b = (char)(b - 'A' + 'a');
|
||||
if (a != b) break;
|
||||
k++;
|
||||
}
|
||||
if (k != nlen || buf[i + k] != ':') continue;
|
||||
|
||||
const char* v = buf + i + nlen + 1;
|
||||
const char* end = buf + len;
|
||||
while (v < end && (*v == ' ' || *v == '\t')) v++;
|
||||
size_t o = 0;
|
||||
while (v < end && *v != '\r' && *v != '\n' && o < out_cap - 1) out[o++] = *v++;
|
||||
out[o] = '\0';
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Resolve a Location value against the current request. Absolute URLs replace
|
||||
// host/port/path; a path-only Location keeps the host. Returns false and sets
|
||||
// f->err when the target is HTTPS, which this TLS-less client cannot follow.
|
||||
static bool apply_redirect(http_fetch_t* f, const char* location) {
|
||||
if (location[0] == '/' || location[0] == '\0') {
|
||||
// Same host, new path.
|
||||
strncpy(f->path, location[0] ? location : "/", sizeof(f->path) - 1);
|
||||
f->path[sizeof(f->path) - 1] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strncmp(location, "http://", 7) != 0 && strncmp(location, "https://", 8) != 0) {
|
||||
// A bare relative reference; treat it as a rooted path.
|
||||
snprintf(f->path, sizeof(f->path), "/%s", location);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool https;
|
||||
char host[128], path[256];
|
||||
uint16_t port;
|
||||
if (!split_url(location, host, sizeof(host), path, sizeof(path), &port, &https)) {
|
||||
snprintf(f->err, sizeof(f->err), "redirect to a URL we cannot parse");
|
||||
return false;
|
||||
}
|
||||
if (https) {
|
||||
snprintf(f->err, sizeof(f->err), "redirect to HTTPS (TLS unsupported): %s", host);
|
||||
return false;
|
||||
}
|
||||
strcpy(f->host, host);
|
||||
strcpy(f->path, path);
|
||||
f->port = port;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parse the numeric status out of "HTTP/1.x NNN ...", or 0 if absent.
|
||||
@@ -96,22 +174,23 @@ static int parse_status(const char* buf, int len) {
|
||||
// finalize would then read freed memory. Returning the state instead keeps the
|
||||
// job private to the worker until the atomic hand-off.
|
||||
// ===========================================================================
|
||||
static int do_fetch(http_fetch_t* f) {
|
||||
uint64_t t0 = pit_get_ticks();
|
||||
#define HTTP_MAX_REDIRECTS 5
|
||||
|
||||
// Resolve: a dotted quad is taken as-is, otherwise the DNS resolver runs
|
||||
// (itself bounded, so an unreachable server times out rather than hangs).
|
||||
// Perform one request/response against the current host/path/port. Returns the
|
||||
// HTTP status code, or a negative error code; the raw response lands in f->buf.
|
||||
static int do_one_request(http_fetch_t* f) {
|
||||
f->ip = ip_parse(f->host);
|
||||
if (f->ip == 0) f->ip = dns_resolve(f->host);
|
||||
if (f->ip == 0) {
|
||||
snprintf(f->err, sizeof(f->err), "cannot resolve %s", f->host);
|
||||
return HTTP_STATE_ERROR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
tcp_conn_t* c = tcp_connect(f->ip, f->port, HTTP_CONNECT_TIMEOUT_MS);
|
||||
if (!c) {
|
||||
snprintf(f->err, sizeof(f->err), "connect to :%u refused/timed out", f->port);
|
||||
return HTTP_STATE_ERROR;
|
||||
snprintf(f->err, sizeof(f->err), "connect to %s:%u refused/timed out",
|
||||
f->host, f->port);
|
||||
return -1;
|
||||
}
|
||||
|
||||
char req[512];
|
||||
@@ -121,10 +200,9 @@ static int do_fetch(http_fetch_t* f) {
|
||||
if (tcp_send(c, req, (uint16_t)rl) < 0) {
|
||||
snprintf(f->err, sizeof(f->err), "send failed");
|
||||
tcp_close(c);
|
||||
return HTTP_STATE_ERROR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Drain the response until the peer closes or the buffer fills.
|
||||
f->len = 0;
|
||||
while (f->len < f->cap - 1) {
|
||||
int n = tcp_recv(c, f->buf + f->len, (uint16_t)(f->cap - 1 - f->len),
|
||||
@@ -135,14 +213,51 @@ static int do_fetch(http_fetch_t* f) {
|
||||
f->buf[f->len] = '\0';
|
||||
tcp_close(c);
|
||||
|
||||
f->status_code = parse_status(f->buf, f->len);
|
||||
f->elapsed_ms = (uint32_t)((pit_get_ticks() - t0) * 10);
|
||||
|
||||
if (f->len == 0) {
|
||||
snprintf(f->err, sizeof(f->err), "no data received");
|
||||
return -1;
|
||||
}
|
||||
return parse_status(f->buf, f->len);
|
||||
}
|
||||
|
||||
// Fills in the result fields but deliberately does NOT touch f->state:
|
||||
// publishing the terminal state is finalize's job, done under interrupts-off.
|
||||
// Returning the state keeps the job private to the worker until that hand-off.
|
||||
//
|
||||
// A 3xx response with a Location header is followed, up to a bounded number of
|
||||
// hops, so a real-world URL that redirects (a bare host to a path, http to a
|
||||
// canonical host) resolves to actual content. An https target is reported
|
||||
// rather than followed, since this client has no TLS.
|
||||
static int do_fetch(http_fetch_t* f) {
|
||||
uint64_t t0 = pit_get_ticks();
|
||||
f->redirects = 0;
|
||||
|
||||
// The client has no TLS, so an https URL cannot be served. Say so plainly
|
||||
// rather than sending a plaintext request to port 443 and reporting a
|
||||
// confusing timeout.
|
||||
if (f->tls) {
|
||||
snprintf(f->err, sizeof(f->err), "%s is HTTPS; this client has no TLS", f->host);
|
||||
return HTTP_STATE_ERROR;
|
||||
}
|
||||
return HTTP_STATE_DONE;
|
||||
|
||||
for (;;) {
|
||||
int status = do_one_request(f);
|
||||
if (status < 0) return HTTP_STATE_ERROR; // f->err already set
|
||||
|
||||
bool is_redirect = (status == 301 || status == 302 || status == 303 ||
|
||||
status == 307 || status == 308);
|
||||
char location[512];
|
||||
if (is_redirect && f->redirects < HTTP_MAX_REDIRECTS &&
|
||||
find_header(f->buf, f->len, "Location", location, sizeof(location))) {
|
||||
if (!apply_redirect(f, location)) return HTTP_STATE_ERROR; // e.g. HTTPS
|
||||
f->redirects++;
|
||||
continue;
|
||||
}
|
||||
|
||||
f->status_code = status;
|
||||
f->elapsed_ms = (uint32_t)((pit_get_ticks() - t0) * 10);
|
||||
return HTTP_STATE_DONE;
|
||||
}
|
||||
}
|
||||
|
||||
static void free_job(http_fetch_t* f) {
|
||||
|
||||
Reference in new issue
Block a user