tAdd sacc.c - sacc - sacc (saccomys): simple gopher client.
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) LICENSE
       ---
 (DIR) commit e831853d549fee7311b1d02c320dbfa174e115b1
 (DIR) parent 78f764aea574b619509c5ebb247dab0de1606822
 (HTM) Author: Quentin Rameau <quinq@fifth.space>
       Date:   Mon, 19 Jun 2017 18:18:39 +0200
       
       Add sacc.c
       
       Basic implementation, parse url, connect to server, send and get data,
       display it.
       
       Diffstat:
         sacc.c                              |     384 +++++++++++++++++++++++++++++++
       
       1 file changed, 384 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/sacc.c b/sacc.c
       t@@ -0,0 +1,384 @@
       +/* See LICENSE file for copyright and license details. */
       +#include <errno.h>
       +#include <netdb.h>
       +#include <stdarg.h>
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +#include <unistd.h>
       +#include <sys/socket.h>
       +#include <sys/types.h>
       +
       +typedef struct item Item;
       +struct item {
       +        char type;
       +        char *username;
       +        char *selector;
       +        char *host;
       +        char *port;
       +        char *raw;
       +        Item *entry;
       +        void *target;
       +};
       +
       +typedef struct {
       +        int nitems;
       +        Item *items;
       +} Menu;
       +
       +static void die(const char *, ...);
       +
       +void
       +die(const char *fmt, ...)
       +{
       +        va_list arg;
       +
       +        va_start(arg, fmt);
       +        vfprintf(stderr, fmt, arg);
       +        va_end(arg);
       +        fputc('\n', stderr);
       +
       +        exit(1);
       +}
       +
       +void *
       +xrealloc(void *m, const size_t n)
       +{
       +        void *nm;
       +
       +        if (!(nm = realloc(m, n)))
       +                die("realloc: %s", strerror(errno));
       +
       +        return nm;
       +}
       +
       +void *
       +xmalloc(const size_t n)
       +{
       +        void *m = malloc(n);
       +
       +        if (!m)
       +                die("malloc: %s\n", strerror(errno));
       +
       +        return m;
       +}
       +
       +char *
       +xstrdup(const char *str)
       +{
       +        char *s;
       +
       +        if (!(s = strdup(str)))
       +                die("strdup: %s\n", strerror(errno));
       +
       +        return s;
       +}
       +
       +void
       +usage(void)
       +{
       +        die("usage: sacc URL");
       +}
       +
       +const char *
       +typedisplay(char t)
       +{
       +        switch (t) {
       +        case '0':
       +                return "Text";
       +        case '1':
       +                return "Dir";
       +        case '2':
       +                return "CSO";
       +        case '3':
       +                return "Err";
       +        case '4':
       +                return "Macf";
       +        case '5':
       +                return "DOSf";
       +        case '6':
       +                return "UUEf";
       +        case '7':
       +                return "Find";
       +        case '8':
       +                return "Tlnt";
       +        case '9':
       +                return "Binf";
       +        case '+':
       +                return "Mirr";
       +        case 'T':
       +                return "IBMt";
       +        case 'g':
       +                return "GIF";
       +        case 'I':
       +                return "Img";
       +        case 'h':
       +                return "HTML";
       +        case 'i':
       +                return "Info";
       +        case 's':
       +                return "Snd";
       +        default:
       +                return "WRNG";
       +        }
       +}
       +
       +int
       +display(Item *item)
       +{
       +        Item *itm;
       +        Item **items;
       +        int i = 0;
       +
       +        switch (item->type) {
       +        case '0':
       +                puts(item->target);
       +                break;
       +        case '1':
       +                items = (Item **)item->target;
       +                for (; items[i]; ++i)
       +                        printf("[%d]%.4s: %s\n", i+1, typedisplay(items[i]->type),
       +                               items[i]->username);
       +                break;
       +        }
       +
       +        return i;
       +}
       +
       +char *
       +pickfield(char **s)
       +{
       +        char *c, *f = *s;
       +
       +        /* loop until we reach the end of the string, or a tab, or CRLF */
       +        for (c = *s; *c; ++c) {
       +                switch (*c) {
       +                case '\t':
       +                        break;
       +                case '\r': /* FALLTHROUGH */
       +                        if (*(c+1) == '\n') {
       +                                *c++ = '\0';
       +                                break;
       +                        }
       +                default:
       +                        continue;
       +                }
       +                break;
       +        }
       +
       +        if (!*c)
       +                die("Can't parse directory item: %s", f);
       +        *c = '\0';
       +        *s = c+1;
       +
       +        return f;
       +}
       +
       +void *
       +parsediritem(char *raw)
       +{
       +        Item *item, **items = NULL;
       +        int nitems = 0;
       +        size_t n;
       +
       +        while (strncmp(raw, ".\r\n", 3)) {
       +                n = (++nitems+1) * sizeof(Item*);
       +                items = xrealloc(items, n);
       +
       +                item = xmalloc(sizeof(Item));
       +                item->type = *raw++;
       +                item->username = pickfield(&raw);
       +                item->selector = pickfield(&raw);
       +                item->host = pickfield(&raw);
       +                item->port = pickfield(&raw);
       +                item->target = NULL;
       +
       +                items[nitems-1] = item;
       +        }
       +        items[nitems] = NULL;
       +
       +        return items;
       +}
       +
       +char *
       +getrawitem(int sock)
       +{
       +        char *item, *buf;
       +        size_t is, bs, ns;
       +        ssize_t n;
       +
       +        is = bs = BUFSIZ;
       +        item = buf = xmalloc(BUFSIZ+1);
       +
       +        while ((n = read(sock, buf, bs)) > 0) {
       +                bs -= n;
       +                buf += n;
       +                if (bs <= 0) {
       +                        ns = is + BUFSIZ;
       +                        item = xrealloc(item, ns+1);
       +                        is = ns;
       +                        buf = item + is-BUFSIZ;
       +                        bs = BUFSIZ;
       +                }
       +        }
       +        if (n < 0)
       +                die("Can't read socket: %s", strerror(errno));
       +
       +        *buf = '\0';
       +
       +        return item;
       +}
       +
       +int
       +sendselector(int sock, const char *selector)
       +{
       +        if (write(sock, selector, strlen(selector)) < 0)
       +                die("Can't send message: %s", strerror(errno));
       +        if (write(sock, "\r\n", strlen("\r\n")) < 0)
       +                die("Can't send message: %s", strerror(errno));
       +
       +        return 1;
       +}
       +
       +int
       +connectto(const char *host, const char *port)
       +{
       +        static const struct addrinfo hints = {
       +            .ai_family = AF_UNSPEC,
       +            .ai_socktype = SOCK_STREAM,
       +            .ai_protocol = IPPROTO_TCP,
       +        };
       +        struct addrinfo *addrs, *addr;
       +        int sock, r;
       +
       +        if (r = getaddrinfo(host, port, &hints, &addrs))
       +                die("Can't resolve hostname ā€œ%sā€: %s", host, gai_strerror(r));
       +
       +        for (addr = addrs; addr; addr = addr->ai_next) {
       +                if ((sock = socket(addr->ai_family, addr->ai_socktype,
       +                                   addr->ai_protocol)) < 0)
       +                        continue;
       +                if ((r = connect(sock, addr->ai_addr, addr->ai_addrlen)) < 0) {
       +                        close(sock);
       +                        continue;
       +                }
       +                break;
       +        }
       +        if (sock < 0)
       +                die("Can't open socket: %s", strerror(errno));
       +        if (r < 0)
       +                die("Can't connect to: %s:%s: %s", host, port, strerror(errno));
       +
       +        freeaddrinfo(addrs);
       +
       +        return sock;
       +}
       +
       +void
       +dig(Item *item)
       +{
       +        int sock;
       +
       +        if (item->type > '1') /* not supported */
       +                return;
       +
       +        sock = connectto(item->host, item->port);
       +        sendselector(sock, item->selector);
       +        item->raw = getrawitem(sock);
       +
       +        if (item->type == '0')
       +                item->target = item->raw;
       +        else if (item->type == '1')
       +                item->target = parsediritem(item->raw);
       +}
       +
       +Item *
       +parseurl(const char *URL)
       +{
       +        Item *hole;
       +        char *p, *url, *host, *port = "gopher", *gopherpath = "1";
       +        int parsed, ipv6;
       +
       +        host = url = xstrdup(URL);
       +
       +        if (p = strstr(url, "://")) {
       +                if (strncmp(url, "gopher", p - url))
       +                        die("Protocol not supported: %.*s (%s)", p - url, url, URL);
       +                host = p + 3;
       +        }
       +
       +        if (*host == '[') {
       +                ipv6 = 1;
       +                ++host;
       +        } else {
       +                ipv6 = 0;
       +        }
       +
       +        for (parsed = 0, p = host; !parsed && *p; ++p) {
       +                switch (*p) {
       +                case ']':
       +                        if (ipv6) {
       +                                *p = '\0';
       +                                ipv6 = 0;
       +                        }
       +                        continue;
       +                case ':':
       +                        if (!ipv6) {
       +                                *p = '\0';
       +                                port = p+1;
       +                        }
       +                        continue;
       +                case '/':
       +                        *p = '\0';
       +                        gopherpath = p+1;
       +                        parsed = 1;
       +                        continue;
       +                }
       +        }
       +
       +        if (*host == '\0' || *port == '\0' || ipv6)
       +                die("Can't parse url: %s", URL);
       +
       +        if (gopherpath[0] > '1')
       +                die("Gopher type not supported: %s (%s)",
       +                    typedisplay(gopherpath[0]), URL);
       +
       +        hole = xmalloc(sizeof(Item));
       +        hole->raw = url;
       +        hole->type = gopherpath[0];
       +        hole->username = hole->selector = ++gopherpath;
       +        hole->host = host;
       +        hole->port = port;
       +        hole->entry = hole->target = NULL;
       +
       +        return hole;
       +}
       +
       +int
       +main(int argc, char *argv[])
       +{
       +        Item *hole;
       +        int n, itm;
       +
       +        if (argc != 2)
       +                usage();
       +
       +        hole = parseurl(argv[1]);
       +
       +        for (;;) {
       +                dig(hole);
       +                if (!(n = display(hole)))
       +                        break;
       +                do {
       +                        printf("%d items, visit (empty to quit): ", n);
       +                        if (scanf("%d", &itm) != 1)
       +                                goto quit;
       +                } while (itm < 1 && itm > n);
       +                hole = ((Item **)hole->target)[itm-1];
       +        }
       +
       +quit:
       +        free(hole); /* TODO free all tree recursively */
       +
       +        return 0;
       +}