237 lines
5.6 KiB
C
237 lines
5.6 KiB
C
/**
|
|
* @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 <ip>[:<port>]/<path>
|
|
* wget <ip> - 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 <path> HTTP/1.0\r\nHost: <host>\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 <ip>[:<port>]/<path>\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;
|
|
}
|