diff options
Diffstat (limited to 'net.c')
-rw-r--r-- | net.c | 119 |
1 files changed, 117 insertions, 2 deletions
diff --git a/net.c b/net.c index 5d68714..6fbfa97 100644 --- a/net.c +++ b/net.c @@ -1,3 +1,11 @@ +#include <netinet/in.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <unistd.h> + +#include <stdlib.h> +#include <errno.h> #include <string.h> #include <stdio.h> @@ -41,6 +49,29 @@ int net_addr(const char *url, struct addr *adr, enum protocol prot_default) { 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, 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; @@ -60,7 +91,39 @@ int net_addr(const char *url, struct addr *adr, enum protocol prot_default) { return 0; } -static int file_fetch(const struct addr *adr, struct buf *buf, enum doc_type *doct) { +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"); @@ -77,10 +140,62 @@ static int file_fetch(const struct addr *adr, struct buf *buf, enum doc_type *do 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; + } + buf_init(buf, 64); + char inbuf[256]; + for (;;) { + ssize_t n = read(s, inbuf, sizeof inbuf); + if (n < 0) { + perr("read error"); + buf_free(buf); + close(s); + return -1; + } + if (!n) break; + buf_cat(buf, 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 file_fetch(adr, buf, doct); + return fetch_file(adr, buf, doct); + case PROT_GOPHER: + return fetch_gopher(adr, buf, doct); default: perr("unsupported protocol"); return -1; |