summaryrefslogtreecommitdiff
path: root/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'main.c')
-rw-r--r--main.c237
1 files changed, 237 insertions, 0 deletions
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..80ab2e3
--- /dev/null
+++ b/main.c
@@ -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;
+}