cgmnlm

Unnamed repository; edit this file 'description' to name the repository.
git clone git://code.clttr.info/cgmnlm.git
Log | Files | Refs | README | LICENSE

commit 01567e578c9632960903e1f56dd2086547806da3
parent 48d0feed6d097c54662a7f231c7bc4704837f023
Author: Drew DeVault <sir@cmpwn.com>
Date:   Sun, 20 Sep 2020 18:31:33 -0400

Initial work for line-mode browser

Diffstat:
MMakefile | 4++++
Mconfig.sh | 1+
Mconfigure | 10+++++++++-
Msrc/gmni.c | 2+-
Asrc/gmnlm.c | 215+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 230 insertions(+), 2 deletions(-)

diff --git a/Makefile b/Makefile @@ -8,6 +8,10 @@ gmni: $(gmni_objects) @printf 'CCLD\t$@\n' @$(CC) $(LDFLAGS) $(LIBS) -o $@ $(gmni_objects) +gmnlm: $(gmnlm_objects) + @printf 'CCLD\t$@\n' + @$(CC) $(LDFLAGS) $(LIBS) -o $@ $(gmnlm_objects) + doc/gmni.1: doc/gmni.scd .SUFFIXES: .c .o .scd .1 diff --git a/config.sh b/config.sh @@ -134,6 +134,7 @@ run_configure() { all: ${all} EOF gmni >>"$outdir"/config.mk + gmnlm >>"$outdir"/config.mk echo done touch $outdir/cppcache diff --git a/configure b/configure @@ -7,10 +7,18 @@ gmni() { src/client.c \ src/escape.c \ src/gmni.c \ + src/url.c +} + +gmnlm() { + genrules gmnlm \ + src/client.c \ + src/escape.c \ + src/gmnlm.c \ src/parser.c \ src/url.c } -all="gmni" +all="gmni gmnlm" run_configure diff --git a/src/gmni.c b/src/gmni.c @@ -15,7 +15,7 @@ #include "gmni.h" static void -usage(char *argv_0) +usage(const char *argv_0) { fprintf(stderr, "usage: %s [-46lLiIN] [-E cert] [-d input] [-D path] gemini://...\n", diff --git a/src/gmnlm.c b/src/gmnlm.c @@ -0,0 +1,215 @@ +#include <assert.h> +#include <ctype.h> +#include <getopt.h> +#include <openssl/bio.h> +#include <openssl/err.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include <sys/ioctl.h> +#include <termios.h> +#include <unistd.h> +#include "gmni.h" +#include "url.h" + +struct link { + char *url; + struct link *next; +}; + +static void +usage(const char *argv_0) +{ + fprintf(stderr, "usage: %s [gemini://...]\n", argv_0); +} + +static bool +set_url(struct Curl_URL *url, char *new_url) +{ + if (curl_url_set(url, CURLUPART_URL, new_url, 0) != CURLUE_OK) { + fprintf(stderr, "Error: invalid URL\n"); + return false; + } + return true; +} + +static char * +trim_ws(char *in) +{ + for (int i = strlen(in) - 1; in[i] && isspace(in[i]); --i) { + in[i] = 0; + } + for (; *in && isspace(*in); ++in); + return in; +} + +static void +display_gemini(FILE *tty, struct gemini_response *resp, + struct link **next, bool pagination) +{ + int nlinks = 0; + struct gemini_parser p; + gemini_parser_init(&p, resp->bio); + + struct winsize ws; + ioctl(fileno(tty), TIOCGWINSZ, &ws); + + int row = 0, col = 0; + struct gemini_token tok; + while (gemini_parser_next(&p, &tok) == 0) { + switch (tok.token) { + case GEMINI_TEXT: + // TODO: word wrap + col += fprintf(tty, " %s\n", trim_ws(tok.text)); + break; + case GEMINI_LINK: + (void)next; // TODO: Record links + col += fprintf(tty, "[%d] %s\n", nlinks++, trim_ws( + tok.link.text ? tok.link.text : tok.link.url)); + break; + case GEMINI_PREFORMATTED: + continue; // TODO + case GEMINI_HEADING: + for (int n = tok.heading.level; n; --n) { + col += fprintf(tty, "#"); + } + col += fprintf(tty, " %s\n", trim_ws(tok.heading.title)); + break; + case GEMINI_LIST_ITEM: + // TODO: Option to disable Unicode + col += fprintf(tty, " • %s\n", trim_ws(tok.list_item)); + break; + case GEMINI_QUOTE: + // TODO: Option to disable Unicode + col += fprintf(tty, " | %s\n", trim_ws(tok.quote_text)); + break; + } + + while (col >= ws.ws_col) { + col -= ws.ws_col; + ++row; + } + ++row; + col = 0; + + if (pagination && row >= ws.ws_row - 1) { + fprintf(tty, "[Enter for more, or q to stop] "); + + size_t n = 0; + char *l = NULL; + if (getline(&l, &n, tty) == -1) { + return; + } + if (strcmp(l, "q\n") == 0) { + return; + } + + free(l); + row = col = 0; + } + } + + gemini_parser_finish(&p); +} + +int +main(int argc, char *argv[]) +{ + bool pagination = true; + + bool have_url = false; + struct Curl_URL *url = curl_url(); + + FILE *tty = fopen("/dev/tty", "w+"); + + int c; + while ((c = getopt(argc, argv, "hP")) != -1) { + switch (c) { + case 'P': + pagination = false; + break; + case 'h': + usage(argv[0]); + return 0; + default: + fprintf(stderr, "fatal: unknown flag %c\n", c); + return 1; + } + } + + if (optind == argc - 1) { + set_url(url, argv[optind]); + have_url = true; + } else if (optind < argc - 1) { + usage(argv[0]); + return 1; + } + + SSL_load_error_strings(); + ERR_load_crypto_strings(); + struct gemini_options opts = { + .ssl_ctx = SSL_CTX_new(TLS_method()), + }; + + bool run = true; + struct gemini_response resp; + while (run) { + assert(have_url); // TODO + + struct link *links; + static char prompt[4096]; + + char *plain_url; + CURLUcode uc = curl_url_get(url, CURLUPART_URL, &plain_url, 0); + assert(uc == CURLUE_OK); // Invariant + + snprintf(prompt, sizeof(prompt), "\n\t%s\n" + "\tWhere to? [n]: follow Nth link; [o <url>]: open URL; [q]: quit\n" + "=> ", plain_url); + + enum gemini_result res = gemini_request(plain_url, &opts, &resp); + if (res != GEMINI_OK) { + fprintf(stderr, "Error: %s\n", gemini_strerr(res, &resp)); + assert(0); // TODO: Prompt + } + + switch (gemini_response_class(resp.status)) { + case GEMINI_STATUS_CLASS_INPUT: + assert(0); // TODO + case GEMINI_STATUS_CLASS_REDIRECT: + assert(0); // TODO + case GEMINI_STATUS_CLASS_CLIENT_CERTIFICATE_REQUIRED: + assert(0); // TODO + case GEMINI_STATUS_CLASS_TEMPORARY_FAILURE: + case GEMINI_STATUS_CLASS_PERMANENT_FAILURE: + fprintf(stderr, "Server returned %s %d %s\n", + resp.status / 10 == 4 ? + "TEMPORARY FAILURE" : "PERMANENT FALIURE", + resp.status, resp.meta); + break; + case GEMINI_STATUS_CLASS_SUCCESS: + display_gemini(tty, &resp, &links, pagination); + break; + } + + gemini_response_finish(&resp); + + fprintf(tty, "%s", prompt); + size_t l = 0; + char *in = NULL; + ssize_t n = getline(&in, &l, tty); + if (n == -1 && feof(tty)) { + break; + } + + if (strcmp(in, "q\n") == 0) { + run = false; + } + + free(in); + } + + SSL_CTX_free(opts.ssl_ctx); + curl_url_cleanup(url); + return 0; +}