diff options
Diffstat (limited to 'main.c')
| -rw-r--r-- | main.c | 237 |
1 files changed, 237 insertions, 0 deletions
@@ -0,0 +1,237 @@ +#include <stdio.h> +#include <unistd.h> +#include <limits.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#define ARENA_IMPL +#define STR_IMPL + +#include "arena.h" +#include "str.h" + +/* address cache */ + +#define ADDR_MAP_MAX 128 +#define ADDR_HOST_MAX 128 + +typedef struct { + char host[ADDR_HOST_MAX]; + int hostn, port; + struct addrinfo *ai; +} AddrMap; + +static AddrMap addr_mapv[ADDR_MAP_MAX]; +static int addr_mapc = 0; + +Str +uint_to_str(unsigned src, Arena *a) +{ + Str r = { 0 }; + do { + str_catc(&r, (src % 10) + '0', a); + src /= 10; + } while(src); + for (int i = 0, j = r.n - 1; i < j; i++, j--) { + char t = r.s[i]; + r.s[i] = r.s[j]; + r.s[j] = t; + } + return r; +} + +struct addrinfo * +addr_get(Str host, int port, Arena *scratch) +{ + if (host.n > ADDR_HOST_MAX) { + fprintf(stderr, "hostname too long\n"); + return NULL; + } + + /* TODO: maybe move fetched addr to front of table? */ + + for (int i = 0; i < addr_mapc; i++) { + AddrMap *m = &addr_mapv[i]; + if (m->port != port) continue; + if (str_eql((Str) { m->host, m->hostn }, host)) + return m->ai; + } + + puts("lookup"); + struct addrinfo *ai; + int e = getaddrinfo(str_to_cstr(host, scratch), + str_to_cstr(uint_to_str(port, scratch), scratch), + &(struct addrinfo) { + .ai_family = AF_INET, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP, + .ai_flags = AI_NUMERICSERV + }, &ai); + if (e) { + fprintf(stderr, "bad address: %s\n", gai_strerror(e)); + return NULL; + } + + if (addr_mapc == ADDR_MAP_MAX) { + freeaddrinfo(addr_mapv[0].ai); + for (int i = 1; i < ADDR_MAP_MAX; i++) + addr_mapv[i-1] = addr_mapv[i]; + addr_mapc--; + } + AddrMap *m = &addr_mapv[addr_mapc++]; + memcpy(m->host, host.s, host.n); + m->hostn = host.n; + m->port = port; + m->ai = ai; + return ai; +} + +void +addr_init(void) +{ + /* do nothing */ +} + +void +addr_fini(void) +{ + for (int i = 0; i < addr_mapc; i++) + freeaddrinfo(addr_mapv[i].ai); +} + +int +addr_conn(Str host, int port, Arena *scratch) +{ + struct addrinfo *ai = addr_get(host, port, scratch); + if (!ai) + return -1; + + int s = socket(AF_INET, SOCK_STREAM, 0); + if (s == -1) { + fprintf(stderr, "couldn't create socket\n"); + return -1; + } + + if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0) { + fprintf(stderr, "couldn't connect to host\n"); + close(s); + return -1; + } + + return s; +} + +/* url parsing */ + +#define PROT_DEFAULT PROT_GOPHER + +typedef enum { + PROT_UNKNOWN, + PROT_GOPHER, + PROT_FILE, +} Proto; + +typedef enum { + GI_TEXT, + GI_MENU, + GI_SEARCH, + GI_UNSUPPORTED, +} GopherItemType; + +typedef struct { + Str host, path; + Proto proto; + int port; +} Request; + +int +parse_port(Str s) { + int x = 0; + for (int i = 0; i < s.n; i++) { + if (s.s[i] < '0' || s.s[i] > '9') return -1; + if (x >= INT_MAX / 10 - 1) return -1; + x = (x * 10) + s.s[i] - '0'; + } + return x; +} + +int +parse_request(Str url, Request *req) +{ + int i; + static struct { Str s; Proto p; int port; } prot_tbl[] = { + { Ss("gopher"), PROT_GOPHER, 70 }, + { Ss("file"), PROT_FILE, 0 }, + }; + + /* protocol */ + + req->proto = PROT_UNKNOWN; + Cut cprot = str_cuts(url, S("://")); + if (cprot.tail.n > 0) { + for (i = 0; i < sizeof prot_tbl / sizeof *prot_tbl; i++) { + if (str_eql(cprot.head, prot_tbl[i].s)) { + req->proto = prot_tbl[i].p; + req->port = prot_tbl[i].port; + } + } + url = cprot.tail; + switch (req->proto) { + case PROT_UNKNOWN: + fprintf(stderr, "bad protocol\n"); + return -1; + case PROT_FILE: + req->path = url; + return 0; + default: + break; + } + } else { + req->proto = PROT_DEFAULT; + } + + /* host & port */ + + Cut chost = str_cut(url, '/'); + Cut cport = str_cut(chost.head, ':'); + req->path = chost.tail; + req->host = cport.head; + if (cport.tail.n > 0) { + req->port = parse_port(cport.tail); + if (req->port == -1) { + fprintf(stderr, "bad port\n"); + return -1; + } + } + + return 0; +} + +int +main(void) +{ + Arena scratch = { 0 }; + Request req = { 0 }; + addr_init(); + + printf("%d\n", parse_request(S("gopher://sdf.org/users/solderpunk"), &req)); + printf("host = %.*s\n", (int)req.host.n, req.host.s); + printf("path = %.*s\n", (int)req.path.n, req.path.s); + printf("prot = %d\n", req.proto); + printf("port = %d\n", req.port); + + int s = addr_conn(req.host, req.port, &scratch); + write(s, req.path.s, req.path.n); + write(s, "\r\n", 2); + for (;;) { + char buf[256]; + int n = read(s, buf, 256); + if (n <= 0) break; + fwrite(buf, 1, n, stdout); + } + close(s); + + addr_fini(); + return 0; +} |
