gawk

[old] Sed-like interface to the Gopher protocol
Log | Files | Refs | LICENSE

commit b009d1b81e1ed073de76b11d6b47ed8fcdc4ca0d
parent 8cae8e4f31cd38b441bc583c8038d212be7b5db8
Author: Jacob R. Edwards <jacobouno@protonmail.com>
Date:   Sun, 20 Dec 2020 22:46:44 -0800

Add file downloading command `f`

Diffstat:
Mmain.c | 122++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
1 file changed, 106 insertions(+), 16 deletions(-)

diff --git a/main.c b/main.c @@ -22,6 +22,7 @@ #include <ctype.h> #include <err.h> #include <errno.h> +#include <libgen.h> #include <limits.h> #include <netdb.h> #include <poll.h> @@ -32,10 +33,13 @@ #define LEN(X) (sizeof(X) / sizeof(*X)) +#define FETCH_MESSAGE #define BUFSIZE 4096 #define ARGV_MAX 16 #define ARG_SEP ":\t " +enum gphitem { GI_INFO, GI_PATH, GI_HOST, GI_PORT, GI_NULL }; + static int timeout = 5 * 1000; static char *gphfmt[] = { "gophmt", NULL }; @@ -255,7 +259,7 @@ gphsend(int sock, const char *request) } int -gphcache(const char *cache, const char *host, const char *path, const char *port) +selwrite(const char *host, const char *port, const char *selector, const char *file) { int sock; @@ -263,8 +267,8 @@ gphcache(const char *cache, const char *host, const char *path, const char *port if (sock == -1) return 1; - if (gphsend(sock, path) != 0 || - fetch(sock, cache) != 0) { + if (gphsend(sock, selector) != 0 || + fetch(sock, file) != 0) { close(sock); return 1; } @@ -283,7 +287,7 @@ pipedup(int old, int new, int fildes[2]) } int -efmt(int (*putter)(const char *, int, const char **), const char *path, int argc, const char **argv) +exfmt(int (*putter)(const char *, int, const char **), const char *path, int argc, const char **argv) { int fds[2]; int i; @@ -354,25 +358,37 @@ cgoto(int argc, const char **argv, int depth, const char *tmpdir, const char *ho } int -cback(int argc, const char **argv) +strtorange(unsigned int min, int max, const char *s) { + unsigned long n; char *ep; - unsigned long back; + + n = strtoul(s, &ep, 0); + if (*s == '\0' || *ep != '\0') { + warnc(EINVAL, "not a number '%s'", s); + return -1; + } else if ((errno == ERANGE && n == ULONG_MAX) || + n < min || n > max) { + warnc(ERANGE, "'%s'", s); + return -1; + } + + return n; +} + +int +cback(int argc, const char **argv) +{ + int back; wunused(1, argc, argv); if (argc == 0) return 1; /* back `1`, not an error */ - back = strtoul(argv[0], &ep, 0); - if (*ep != '\0' || (back == 0 && errno == EINVAL)) { - warnc(EINVAL, "'%s'", argv[0]); + back = strtorange(1, INT_MAX, argv[0]); + if (back < 0) return 0; - } else if (back > INT_MAX) { - warnc(ERANGE, "'%s'", argv[0]); - return 0; - } - return back; } @@ -411,6 +427,77 @@ cmatch(const char *path, int argc, const char **argv) return 0; } +/* argv[0] is the line index (starting from 0) of the file to + * download. If present argv[1] is the location to write the file + * to, otherwise it is written to the basename(3) of it's selector + * string. +*/ +int +cfetch(int argc, const char **argv, const char *cache, const char *host, const char *port) +{ + FILE *fp; + char *fields[GI_NULL + 1]; + char *line; + const char *output; + int error; + int i, index; + size_t size; + + if (argc == 0) { + warn("No index given"); + return 1; + } + wunused(2, argc, argv); + + index = strtorange(0, INT_MAX, argv[0]); + if (index < 0) + return 0; + if (argv[1]) + output = argv[1]; + else output = NULL; + + fp = wfopen(cache, "r"); + if (fp == NULL) + return 1; + + error = 0; + line = NULL; + size = 0; + for (i = 0; getline(&line, &size, fp) != -1; ++i) { + if (i != index) + continue; + if (argsplit(fields, LEN(fields), line, "\t\r\n") != LEN(fields) - 1) { + warnx("Not a valid gopher item."); + ++error; + goto cleanup; + } + if (output == NULL) + if ((output = basename(fields[GI_PATH])) == NULL) { + warn("unable to get basename '%s'", fields[GI_PATH]); + ++error; + goto cleanup; + } + if (selwrite(fields[GI_HOST], fields[GI_PORT], fields[GI_PATH], output) != 0) + ++error; + goto cleanup; + } + +cleanup: +#ifdef FETCH_MESSAGE + if (!error) + warnx("'%s/%s' written to '%s'", fields[GI_HOST], fields[GI_PATH], output); +#endif + free(line); + if (wfclose(fp) || error) + return 1; + if (i < index) { + warnx("%d: Out of range.", i); + return 1; + } + + return 0; +} + int execute(int command, int argc, const char **argv, int depth, const char *cache, const char *tmpdir, const char *host, const char *path, const char *port) @@ -420,7 +507,10 @@ execute(int command, int argc, const char **argv, int depth, const char *cache, wunused(0, argc, argv); return depth; case 'p': - efmt(cmatch, cache, argc, argv); + exfmt(cmatch, cache, argc, argv); + return 0; + case 'f': + cfetch(argc, argv, cache, host, port); return 0; case 'b': return cback(argc, argv); @@ -451,7 +541,7 @@ gawk(int depth, const char *tmpdir, const char *host, const char *path, const ch unlinkit = 0; else { unlinkit = 1; - if (gphcache(cache, host, path, port) == 1) + if (selwrite(host, port, path, cache) == 1) return 0; /* let the user handle it */ }