#include #include #include #include #include #include #include #include #include #include "net.h" #include "err.h" #include "doc.h" int net_addr(const char *url, struct addr *adr, enum protocol prot_default) { char *prot_mark; if ((prot_mark = strstr(url, "://"))) { static struct { const char *str; enum protocol prot; } prot_str_tbl[] = { { "gopher", PROT_GOPHER }, { "gemini", PROT_GEMINI }, { "file", PROT_FILE }, }; size_t n = prot_mark - url; adr->prot = PROT_UNKNOWN; for (size_t i = 0; i < sizeof prot_str_tbl / sizeof *prot_str_tbl; i++) { if (!strncmp(prot_str_tbl[i].str, url, n)) { adr->prot = prot_str_tbl[i].prot; break; } } url = prot_mark + 3; } else { adr->prot = prot_default; } if (adr->prot == PROT_FILE) { adr->host_len = 0; size_t n = strlen(url); if (n >= PATH_MAX) return -1; adr->path_len = n; memcpy(adr->path, url, n + 1); } else { adr->host_len = 0; while (*url && *url != '/' && adr->host_len < HOST_MAX) { adr->host[adr->host_len++] = *url++; } adr->host[adr->host_len] = 0; char *colon = strchr(adr->host, ':'); if (colon) { errno = 0; int port = strtoul(colon + 1, NULL, 10); if (errno) { return -1; } else { adr->port = port; } *colon = 0; adr->host_len = colon - adr->host; } else { switch (adr->prot) { case PROT_GOPHER: adr->port = 70; break; default: adr->port = -1; break; } } if (*url && *url != '/') { perr("hostname too long"); return -1; } if (*url) url++; adr->path_len = 0; while (*url && adr->path_len < HOST_MAX) { adr->path[adr->path_len++] = *url++; } adr->path[adr->path_len] = 0; if (*url) { perr("path too long"); return -1; } } return 0; } static long net_hosttoaddr(const char *hostname) { struct hostent *h; struct in_addr **al; h = gethostbyname(hostname); if (!h) { perr("host not found"); return -1; } al = (struct in_addr **)h->h_addr_list; if (!al || !(*al)) perr("empty host address"); return (*al)->s_addr; } static int net_conntohost(const char *hostname, int port) { struct sockaddr_in sai; int s; s = socket(AF_INET, SOCK_STREAM, 0); if (!s) return -1; sai.sin_family = AF_INET; sai.sin_port = htons(port); sai.sin_addr.s_addr = net_hosttoaddr(hostname); if (connect(s, (struct sockaddr *)&sai, sizeof(sai))) return -1; return s; } static int fetch_file(const struct addr *adr, struct buf *buf, enum doc_type *doct) { FILE *f = fopen(adr->path, "r/o"); if (!f) { perr("file not found"); return -1; } str_init(buf, 1024); char b[256]; while (fgets(b, sizeof b, f)) { str_cat(buf, strv(b)); } str_catc(buf, 0); fclose(f); *doct = DOC_PLAIN; return 0; } static int fetch_gopher(const struct addr *adr, struct buf *buf, enum doc_type *doct) { const char *sel = adr->path; size_t sel_len = adr->path_len; if (sel_len > 0 && sel[0] == '/') { sel_len--; sel++; } if (sel_len > 0) { switch (*sel) { case '0': *doct = DOC_PLAIN; break; case '1': *doct = DOC_GOPHERMAP; break; default: perr("invalid item type"); return -1; } sel++; sel_len--; } else { *doct = DOC_GOPHERMAP; } int s = net_conntohost(adr->host, adr->port); if (s < 0) { return -1; } if (write(s, sel, sel_len) < 0 || write(s, "\r\n", 2) < 0) { perr("write failure"); return -1; } str_init(buf, 64); char inbuf[256]; for (;;) { ssize_t n = read(s, inbuf, sizeof inbuf); if (n < 0) { perr("read error"); str_free(buf); close(s); return -1; } if (!n) break; str_cat(buf, (strv_t) { inbuf, n }); } close(s); return 0; } int net_fetch(const struct addr *adr, struct buf *buf, enum doc_type *doct) { switch (adr->prot) { case PROT_FILE: return fetch_file(adr, buf, doct); case PROT_GOPHER: return fetch_gopher(adr, buf, doct); default: perr("unsupported protocol"); return -1; } }