#include #include #include #include #include #include #include #define ARENA_IMPL #define STR_IMPL #include "dynarr.h" #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; } /* documents */ typedef enum { DOC_ERROR, DOC_TEXT, DOC_GOPHERMAP, DOC_UNKNOWN } DocType; typedef struct { Arena arena; DocType type; Str src; } Doc; int doc_parse(Doc *d, Str src, DocType t) { } /* fetching documents */ ssize_t write_str(int fd, Str s) { return write(fd, s.s, s.n); } Str read_all(int fd, Arena *a) { DYNARR(char) r = { 0 }; for (;;) { DA_AFIT(&r, a, r.n + 1024); ssize_t n = read(fd, r.v + r.n, r.c_ - r.n); if (n <= 0) break; r.n += n; } return (Str) { r.v, r.n }; } DocType fetch_gopher(Str *buf, Request req, Arena *perm, Arena *scratch) { DocType t = DOC_UNKNOWN; if (req.path.n == 0) { t = DOC_GOPHERMAP; } else { switch (req.path.s[0]) { case '0': t = DOC_TEXT; break; case '1': t = DOC_GOPHERMAP; break; default: /* don't bother fetching */ *buf = S(""); return DOC_UNKNOWN; } req.path = str_skip(req.path, 1); } int s = addr_conn(req.host, req.port, scratch); if (s == -1) return DOC_ERROR; write_str(s, req.path); write_str(s, S("\r\n")); *buf = read_all(s, perm); close(s); return t; } DocType fetch_file(Str *buf, Request req, Arena *perm, Arena *scratch) { int f = open(str_to_cstr(req.path, scratch), O_RDONLY); if (f == -1) return DOC_ERROR; *buf = read_all(f, perm); close(f); return DOC_TEXT; } DocType fetch(Str *buf, Request req, Arena *perm, Arena *scratch) { switch (req.proto) { case PROT_GOPHER: return fetch_gopher(buf, req, perm, scratch); case PROT_FILE: return fetch_file(buf, req, perm, scratch); default: *buf = S(""); fprintf(stderr, "unknown type to fetch file\n"); return DOC_ERROR; } } int main(void) { Arena scratch = { 0 }; Request req = { 0 }; addr_init(); printf("parse_request() -> %d\n", parse_request(S("file://main.c"), &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); Str buf = { 0 }; printf("fetch() -> %d\n", fetch(&buf, req, &scratch, &scratch)); printf("%.*s\n", (int)buf.n, buf.s); addr_fini(); return 0; }