/** * @file wget.c * @brief Simple HTTP client for ClaudeOS. * * Downloads a resource from an HTTP server using a TCP connection. * Only supports HTTP/1.0 GET with IP addresses (no DNS). * * Usage: * wget [:]/ * wget - fetches / * * Examples: * wget 10.0.2.2/index.html * wget 10.0.2.2:8080/api/data * wget 192.168.1.1 */ #include "syscalls.h" typedef unsigned char uint8_t; /** * Parse a decimal number from a string. * Advances *s past the digits. */ static uint32_t parse_uint(const char **s) { uint32_t val = 0; while (**s >= '0' && **s <= '9') { val = val * 10 + (uint32_t)(**s - '0'); (*s)++; } return val; } /** * Parse an IPv4 dotted-decimal address into a 32-bit host-order integer. * Returns 0 on failure. */ static uint32_t parse_ip(const char **s) { uint32_t ip = 0; for (int i = 0; i < 4; i++) { uint32_t octet = parse_uint(s); if (octet > 255) return 0; ip = (ip << 8) | octet; if (i < 3) { if (**s != '.') return 0; (*s)++; } } return ip; } /** * Print a decimal number. */ static void print_dec(uint32_t val) { char buf[12]; int i = 0; if (val == 0) { putchar('0'); return; } while (val > 0) { buf[i++] = '0' + (char)(val % 10); val /= 10; } while (i > 0) putchar(buf[--i]); } /** * Build the HTTP GET request. * Returns length of the request string. */ static uint32_t build_request(char *buf, uint32_t bufsize, const char *host, const char *path) { uint32_t pos = 0; /* "GET HTTP/1.0\r\nHost: \r\nConnection: close\r\n\r\n" */ const char *parts[] = { "GET ", path, " HTTP/1.0\r\nHost: ", host, "\r\nConnection: close\r\n\r\n", (const char *)0 }; for (int i = 0; parts[i]; i++) { const char *s = parts[i]; while (*s && pos < bufsize - 1) { buf[pos++] = *s++; } } buf[pos] = '\0'; return pos; } /** * Format an IP address as a dotted-decimal string. */ static void ip_to_str(uint32_t ip, char *buf) { int pos = 0; for (int i = 3; i >= 0; i--) { uint32_t octet = (ip >> (i * 8)) & 0xFF; char tmp[4]; int ti = 0; if (octet == 0) { tmp[ti++] = '0'; } else { while (octet > 0) { tmp[ti++] = '0' + (char)(octet % 10); octet /= 10; } } while (ti > 0) buf[pos++] = tmp[--ti]; if (i > 0) buf[pos++] = '.'; } buf[pos] = '\0'; } int main(void) { char url[256]; /* Get URL from ARG1 */ if (getenv("ARG1", url, sizeof(url)) < 0 || url[0] == '\0') { puts("Usage: wget [:]/\n"); puts(" e.g. wget 10.0.2.2/index.html\n"); return 1; } /* Parse: skip optional "http://" prefix */ const char *p = url; if (strncmp(p, "http://", 7) == 0) { p += 7; } /* Parse IP address */ uint32_t ip = parse_ip(&p); if (ip == 0) { puts("wget: invalid IP address\n"); return 1; } /* Parse optional port */ uint32_t port = 80; if (*p == ':') { p++; port = parse_uint(&p); if (port == 0 || port > 65535) { puts("wget: invalid port\n"); return 1; } } /* Parse path (default to /) */ const char *path = "/"; if (*p == '/') { path = p; } else if (*p != '\0') { puts("wget: invalid URL format\n"); return 1; } /* Build host string for Host header */ char host_str[64]; ip_to_str(ip, host_str); /* Print what we're doing */ puts("Connecting to "); puts(host_str); putchar(':'); print_dec(port); puts(path); puts("...\n"); /* Create TCP socket */ int32_t sockfd = socket(SOCK_TCP); if (sockfd < 0) { puts("wget: failed to create socket\n"); return 1; } /* Connect */ if (connect(sockfd, ip, port) < 0) { puts("wget: connect failed\n"); return 1; } /* Wait for connection to establish (poll with yield) */ int timeout = 500; /* ~5 seconds at ~100 yields/sec */ while (timeout > 0) { int32_t state = sockstate(sockfd); if (state == TCP_STATE_ESTABLISHED) break; if (state == TCP_STATE_CLOSED) { puts("wget: connection refused\n"); return 1; } yield(); timeout--; } if (timeout <= 0) { puts("wget: connection timed out\n"); return 1; } puts("Connected.\n"); /* Build and send HTTP request */ char req[512]; uint32_t req_len = build_request(req, sizeof(req), host_str, path); int32_t sent = net_send(sockfd, req, req_len); if (sent < 0) { puts("wget: send failed\n"); return 1; } /* Receive response */ puts("--- Response ---\n"); char buf[512]; int done = 0; int recv_timeout = 1000; while (!done && recv_timeout > 0) { int32_t n = net_recv(sockfd, buf, sizeof(buf) - 1); if (n > 0) { buf[n] = '\0'; write(1, buf, (uint32_t)n); recv_timeout = 200; /* Reset timeout on data */ } else if (n < 0) { /* Connection closed or error */ done = 1; } else { /* No data yet */ yield(); recv_timeout--; } } puts("\n--- End ---\n"); /* Close socket - we use close() which goes through SYS_CLOSE. * For sockets, we should ideally have a socket-specific close, * but for simplicity, the socket will be cleaned up on process exit. */ return 0; }