Skip to content

Commit

Permalink
add Titan support
Browse files Browse the repository at this point in the history
  • Loading branch information
dimkr committed Apr 13, 2024
1 parent ce6e44a commit f29f59b
Show file tree
Hide file tree
Showing 10 changed files with 238 additions and 67 deletions.
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ CFLAGS += -D_GNU_SOURCE -DPREFIX=\"$(PREFIX)\" -DCONFDIR=\"$(CONFDIR)\" -DGPLAC
LDFLAGS ?=
LDFLAGS += $(shell pkg-config --libs libcurl libssl libcrypto)
MIMETYPES =
WITH_TITAN ?= 1
ifeq ($(WITH_TITAN),1)
CFLAGS += -DGPLACES_WITH_TITAN
MIMETYPES := $(MIMETYPES);x-scheme-handler/titan
endif
WITH_GOPHER ?= 1
ifeq ($(WITH_GOPHER),1)
CFLAGS += -DGPLACES_WITH_GOPHER
Expand Down Expand Up @@ -63,7 +68,7 @@ all: $(BIN) gplacesrc gplaces.desktop
$(BIN): $(OBJ)
$(CC) $(CFLAGS) -o $(BIN) $(OBJ) $(LDFLAGS)

gplaces.o: gplaces.c gopher.c gophers.c spartan.c finger.c guppy.c tcp.c socket.c
gplaces.o: gplaces.c titan.c gopher.c gophers.c spartan.c finger.c guppy.c tcp.c socket.c

gplaces.desktop: gplaces.desktop.in
@sed "s~^MimeType=.*~&$(MIMETYPES)~" $< > $@
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ gplaces is originally a Gemini port of the delve Gopher client by Sebastian Stei
* sh-style history with $XDG_DATA_HOME/gplaces_history or ~/.gplaces_history
* UTF-8 word wrapping
* configurable external pager
* optional Titan support
* optional Gopher support
* optional gophers:// (Gopher+TLS+TOFU) support
* optional Spartan support
Expand Down
2 changes: 1 addition & 1 deletion com.github.dimkr.gplaces.appdata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<li>Slightly prettified Gemtext output</li>
<li>Configurable handlers for various file types</li>
<li>Quick navigation to recently viewed pages</li>
<li>Support for other "small internet" protocols: Spartan, Gopher, Finger and Guppy</li>
<li>Support for other "small internet" protocols: Titan, Spartan, Gopher, Finger and Guppy</li>
</ul>
<p>
gplaces should be fairly straightforward to use for anyone used to the command-line and man pages. Type "readme" for a short introduction.
Expand Down
7 changes: 4 additions & 3 deletions finger.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
================================================================================
gplaces - a simple terminal Gemini client
Copyright (C) 2022, 2023 Dima Krasner
Copyright (C) 2022 - 2024 Dima Krasner
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand All @@ -19,12 +19,13 @@
================================================================================
*/
static void *finger_download(const Selector *sel, URL *url, char **mime, Parser *parser, int ask) {
static void *finger_download(const Selector *sel, URL *url, char **mime, Parser *parser, unsigned int redirs, int ask) {
static char buffer[1024 + 3]; /* path\r\n\0 */
char *user = NULL;
int fd = -1, len = 0;

(void)sel;
(void)redirs;
(void)ask;

switch (curl_url_get(url->cu, CURLUPART_USER, &user, 0)) {
Expand All @@ -48,4 +49,4 @@ static void *finger_download(const Selector *sel, URL *url, char **mime, Parser
}


const Protocol finger = {"finger", "79", tcp_read, tcp_peek, socket_error, tcp_close, finger_download};
const Protocol finger = {"finger", "79", tcp_read, tcp_peek, socket_error, tcp_close, finger_download, set_fragment};
22 changes: 12 additions & 10 deletions gopher.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
================================================================================
gplaces - a simple terminal Gemini client
Copyright (C) 2022, 2023 Dima Krasner
Copyright (C) 2022 - 2024 Dima Krasner
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -58,26 +58,26 @@ static void parse_gophermap_line(char *line, int *pre, Selector **sel, SelectorL
/*============================================================================*/
static char *gopher_request(const Selector *sel, URL *url, int ask, int *len, size_t skip) {
static char buffer[1024 + 3]; /* path\r\n\0 */
char *input = NULL, *query = NULL;
char *input = NULL, *fragment = NULL;
const char *path, *end;

if (url->path[0] == '/' && url->path[1] == '7') {
switch (curl_url_get(url->cu, CURLUPART_QUERY, &query, CURLU_URLDECODE)) {
case CURLUE_OK: input = query; break;
case CURLUE_NO_QUERY: break;
switch (curl_url_get(url->cu, CURLUPART_FRAGMENT, &fragment, 0)) {
case CURLUE_OK: input = fragment; break;
case CURLUE_NO_FRAGMENT: break;
default: return NULL;
}
if (input == NULL) {
if (!ask || (input = bestline(color ? "\33[35mQuery>\33[0m " : "Query> ")) == NULL || !set_input(url, input)) return NULL;
if (!ask || (input = bestline(color ? "\33[35mQuery>\33[0m " : "Query> ")) == NULL || !set_fragment(url, input)) return NULL;
if (interactive) { bestlineHistoryAdd(input); bestlineHistoryAdd(url->url); }
*len = snprintf(buffer, sizeof(buffer), "%s\t%s\r\n", sel->rawurl + skip + strcspn(sel->rawurl + skip, "/") + 2, input);
} else {
path = sel->rawurl + skip + strcspn(sel->rawurl + skip, "/") + 2;
if ((end = strrchr(path, '?')) == NULL) *len = snprintf(buffer, sizeof(buffer), "%s\t%s\r\n", path, input);
else *len = snprintf(buffer, sizeof(buffer), "%.*s\t%s\r\n", (int)(end - path), path, input);
}
if (input != query) free(input);
curl_free(query);
if (input != fragment) free(input);
curl_free(fragment);
} else if (url->path[0] == '/' && url->path[1] != '\0') *len = snprintf(buffer, sizeof(buffer), "%s\r\n", sel->rawurl + skip + strcspn(sel->rawurl + skip, "/") + 2);
else *len = snprintf(buffer, sizeof(buffer), "%s\r\n", sel->rawurl + skip + strcspn(sel->rawurl + skip, "/"));

Expand Down Expand Up @@ -124,10 +124,12 @@ static void gopher_type(void *c, const URL *url, char **mime, Parser *parser) {
}


static void *gopher_download(const Selector *sel, URL *url, char **mime, Parser *parser, int ask) {
static void *gopher_download(const Selector *sel, URL *url, char **mime, Parser *parser, unsigned int redirs, int ask) {
char *buffer;
int fd = -1, len;

(void)redirs;

if ((buffer = gopher_request(sel, url, ask, &len, 9)) == NULL || (fd = socket_connect(url, SOCK_STREAM)) == -1) goto fail;
if (sendall(fd, buffer, len, MSG_NOSIGNAL) != len) {
if (errno == EAGAIN || errno == EWOULDBLOCK) error(0, "cannot send request to `%s`:`%s`: cancelled", url->host, url->port);
Expand All @@ -142,4 +144,4 @@ static void *gopher_download(const Selector *sel, URL *url, char **mime, Parser
}


const Protocol gopher = {"gopher", "70", tcp_read, tcp_peek, socket_error, tcp_close, gopher_download};
const Protocol gopher = {"gopher", "70", tcp_read, tcp_peek, socket_error, tcp_close, gopher_download, set_fragment};
8 changes: 5 additions & 3 deletions gophers.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
================================================================================
gplaces - a simple terminal Gemini client
Copyright (C) 2022, 2023 Dima Krasner
Copyright (C) 2022 - 2024 Dima Krasner
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand All @@ -19,12 +19,14 @@
================================================================================
*/
static void *gophers_download(const Selector *sel, URL *url, char **mime, Parser *parser, int ask) {
static void *gophers_download(const Selector *sel, URL *url, char **mime, Parser *parser, unsigned int redirs, int ask) {
char *buffer;
SSL_CTX *ctx = NULL;
SSL *ssl = NULL;
int len, err;

(void)redirs;

if ((ctx = SSL_CTX_new(TLS_client_method())) == NULL) return NULL;
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);

Expand All @@ -43,4 +45,4 @@ static void *gophers_download(const Selector *sel, URL *url, char **mime, Parser
}


const Protocol gophers = {"gophers", "70", ssl_read, ssl_peek, ssl_error, ssl_close, gophers_download};
const Protocol gophers = {"gophers", "70", ssl_read, ssl_peek, ssl_error, ssl_close, gophers_download, set_fragment};
110 changes: 73 additions & 37 deletions gplaces.c
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ typedef struct Protocol {
int (*peek)(void *, void *, int);
int (*error)(const URL *, void *, int);
void (*close)(void *);
void *(*download)(const Selector *, URL *, char **mime, Parser *, int ask);
void *(*download)(const Selector *, URL *, char **mime, Parser *, unsigned int redirs, int ask);
int (*set_input)(URL *url, const char *input);
} Protocol;

struct Selector {
Expand Down Expand Up @@ -128,6 +129,9 @@ typedef struct Help {

/*============================================================================*/
const Protocol gemini;
#ifdef GPLACES_WITH_TITAN
const Protocol titan;
#endif
#ifdef GPLACES_WITH_GOPHERS
const Protocol gophers;
#endif
Expand Down Expand Up @@ -288,7 +292,7 @@ static void free_selectors(SelectorList *list) {
}


static int set_input(URL *url, const char *input) {
static int set_query(URL *url, const char *input) {
char *query, *tmp;
if ((query = curl_easy_escape(NULL, input, 0)) == NULL) return 0;
if (curl_url_set(url->cu, CURLUPART_QUERY, query, CURLU_NON_SUPPORT_SCHEME) != CURLUE_OK || curl_url_get(url->cu, CURLUPART_URL, &tmp, 0) != CURLUE_OK) { curl_free(query); return 0; }
Expand All @@ -298,6 +302,16 @@ static int set_input(URL *url, const char *input) {
}


#if defined(GPLACES_WITH_TITAN) || defined(GPLACES_WITH_GOPHER) || defined(GPLACES_WITH_GOPHERS) || defined(GPLACSE_WITH_FINGER)
static int set_fragment(URL *url, const char *input) {
char *tmp;
if (curl_url_set(url->cu, CURLUPART_FRAGMENT, input, CURLU_NON_SUPPORT_SCHEME) != CURLUE_OK || curl_url_get(url->cu, CURLUPART_URL, &tmp, 0) != CURLUE_OK) return 0;
curl_free(url->url); url->url = tmp;
return 1;
}
#endif


static int parse_url(URL *url, const char *rawurl, const char *from, const char *input) {
static char buffer[1024];
#if defined(GPLACES_USE_LIBIDN2) || defined(GPLACES_USE_LIBIDN)
Expand Down Expand Up @@ -328,30 +342,14 @@ static int parse_url(URL *url, const char *rawurl, const char *from, const char
valid:
#endif

if (input != NULL && input[0] != '\0' && !set_input(url, input)) return 0;
else if ((input == NULL || input[0] == '\0') && curl_url_get(url->cu, CURLUPART_URL, &url->url, 0) != CURLUE_OK) return 0;

if (curl_url_get(url->cu, CURLUPART_SCHEME, &url->scheme, 0) != CURLUE_OK || (!(file = (strcmp(url->scheme, "file") == 0)) && curl_url_get(url->cu, CURLUPART_HOST, &url->host, 0) != CURLUE_OK)) return 0;

#if defined(GPLACES_USE_LIBIDN2) || defined(GPLACES_USE_LIBIDN)
#ifdef GPLACES_USE_LIBIDN2
if (!file && (idn2_to_ascii_8z(url->host, &host, IDN2_NONTRANSITIONAL) == IDN2_OK || idn2_to_ascii_8z(url->host, &host, IDN2_TRANSITIONAL) == IDN2_OK)) {
#elif defined(GPLACES_USE_LIBIDN)
if (!file && idna_to_ascii_8z(url->host, &host, 0) == IDNA_SUCCESS) {
#endif
if (curl_url_set(url->cu, CURLUPART_HOST, host, 0) != CURLUE_OK) { free(host); return 0; }
free(host);
curl_free(url->host); url->host = NULL;
if (curl_url_get(url->cu, CURLUPART_HOST, &url->host, 0) != CURLUE_OK) return 0;
}
#endif

if (curl_url_get(url->cu, CURLUPART_PATH, &url->path, 0) != CURLUE_OK) return 0;

if (file) return 1;
if (curl_url_get(url->cu, CURLUPART_SCHEME, &url->scheme, 0) != CURLUE_OK) return 0;

if (strcmp(url->scheme, "gemini") == 0) {
url->proto = &gemini;
#ifdef GPLACES_WITH_TITAN
} else if (strcmp(url->scheme, "titan") == 0) {
url->proto = &titan;
#endif
#ifdef GPLACES_WITH_GOPHER
} else if (strcmp(url->scheme, "gopher") == 0) {
url->proto = &gopher;
Expand All @@ -374,6 +372,28 @@ static int parse_url(URL *url, const char *rawurl, const char *from, const char
#endif
}

if (input != NULL && input[0] != '\0' && !url->proto->set_input(url, input)) return 0;
else if ((input == NULL || input[0] == '\0') && curl_url_get(url->cu, CURLUPART_URL, &url->url, 0) != CURLUE_OK) return 0;

if (!(file = (strcmp(url->scheme, "file")) == 0) && curl_url_get(url->cu, CURLUPART_HOST, &url->host, 0) != CURLUE_OK) return 0;

#if defined(GPLACES_USE_LIBIDN2) || defined(GPLACES_USE_LIBIDN)
#ifdef GPLACES_USE_LIBIDN2
if (!file && (idn2_to_ascii_8z(url->host, &host, IDN2_NONTRANSITIONAL) == IDN2_OK || idn2_to_ascii_8z(url->host, &host, IDN2_TRANSITIONAL) == IDN2_OK)) {
#elif defined(GPLACES_USE_LIBIDN)
if (!file && idna_to_ascii_8z(url->host, &host, 0) == IDNA_SUCCESS) {
#endif
if (curl_url_set(url->cu, CURLUPART_HOST, host, 0) != CURLUE_OK) { free(host); return 0; }
free(host);
curl_free(url->host); url->host = NULL;
if (curl_url_get(url->cu, CURLUPART_HOST, &url->host, 0) != CURLUE_OK) return 0;
}
#endif

if (curl_url_get(url->cu, CURLUPART_PATH, &url->path, 0) != CURLUE_OK) return 0;

if (file) return 1;

switch (curl_url_get(url->cu, CURLUPART_PORT, &url->port, 0)) {
case CURLUE_OK: break;
case CURLUE_NO_PORT:
Expand Down Expand Up @@ -960,7 +980,7 @@ static int save_body(const URL *url, void *c, FILE *fp) {
}


static int do_download(URL *url, SSL **body, char **mime, int ask) {
static int ssl_download(URL *url, SSL **body, char **mime, int request(const URL *, SSL *, void *), void *p, int ask) {
static char crtpath[1024], keypath[1024], suffix[1024], buffer[1024], data[2 + 1 + 1024 + 2 + 1]; /* 99 meta\r\n\0 */
struct stat stbuf;
const char *home;
Expand Down Expand Up @@ -1015,8 +1035,7 @@ static int do_download(URL *url, SSL **body, char **mime, int ask) {

if ((ssl = ssl_connect(url, ctx, ask)) == NULL) goto fail;

len = snprintf(buffer, sizeof(buffer), "%s\r\n", url->url);
if ((err = SSL_get_error(ssl, SSL_write(ssl, buffer, len >= (int)sizeof(buffer) ? (int)sizeof(buffer) - 1 : len))) != SSL_ERROR_NONE) {
if ((err = request(url, ssl, p)) != SSL_ERROR_NONE) {
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) error(0, "cannot send request to `%s`:`%s`: cancelled", url->host, url->port);
else error(0, "cannot send request to `%s`:`%s`: error %d", url->host, url->port, err);
goto fail;
Expand Down Expand Up @@ -1047,7 +1066,7 @@ static int do_download(URL *url, SSL **body, char **mime, int ask) {
if ((line = bestline(buffer)) == NULL) goto fail;
if (data[1] != '1' && interactive) bestlineHistoryAdd(line);
if (data[1] == '1') bestlineMaskModeDisable();
if (!set_input(url, line)) { free(line); goto fail; }
if (!set_query(url, line)) { free(line); goto fail; }
free(line);
if (data[1] != '1' && interactive) bestlineHistoryAdd(url->url);
break;
Expand Down Expand Up @@ -1087,21 +1106,30 @@ static int do_download(URL *url, SSL **body, char **mime, int ask) {
}


static void sigint(int sig) {
(void)sig;
/*============================================================================*/
static int gemini_request(const URL *url, SSL *ssl, void *p) {
static char buffer[1024];
int len;

(void)p;

len = snprintf(buffer, sizeof(buffer), "%s\r\n", url->url);
return SSL_get_error(ssl, SSL_write(ssl, buffer, len >= (int)sizeof(buffer) ? (int)sizeof(buffer) - 1 : len));
}


static void *gemini_download(const Selector *sel, URL *url, char **mime, Parser *parser, int ask) {
static void *gemini_download(const Selector *sel, URL *url, char **mime, Parser *parser, unsigned int redirs, int ask) {
SSL *ssl = NULL;
int status, redirs = 0;
int status = -1;

(void)sel;

do {
status = do_download(url, &ssl, mime, ask);
status = ssl_download(url, &ssl, mime, gemini_request, NULL, ask);
if (status >= 20 && status <= 29) break;
} while ((status >= 10 && status <= 19) || (status >= 60 && status <= 69) || (status >= 30 && status <= 39 && ++redirs < 5));
} while ((status >= 10 && status <= 19) || (status >= 60 && status <= 69) || (status >= 30 && status <= 39 && ++redirs < 5 && url->proto->download == gemini_download));

if (redirs < 5 && url->proto->download != gemini_download) return url->proto->download(sel, url, mime, parser, redirs, ask);

if (ssl != NULL && strncmp(*mime, "text/gemini", 11) == 0) *parser = parse_gemtext_line;
else if (ssl != NULL && (!interactive || strncmp(*mime, "text/plain", 10) == 0)) *parser = parse_plaintext_line;
Expand All @@ -1111,10 +1139,13 @@ static void *gemini_download(const Selector *sel, URL *url, char **mime, Parser
}


const Protocol gemini = {"gemini", "1965", ssl_read, ssl_peek, ssl_error, ssl_close, gemini_download};
const Protocol gemini = {"gemini", "1965", ssl_read, ssl_peek, ssl_error, ssl_close, gemini_download, set_query};


/*============================================================================*/
#ifdef GPLACES_WITH_TITAN
#include "titan.c"
#endif
#if defined(GPLACES_WITH_GOPHER) || defined(GPLACES_WITH_SPARTAN) || defined(GPLACES_WITH_FINGER) || defined(GPLACES_WITH_GUPPY)
#include "socket.c"
#endif
Expand Down Expand Up @@ -1174,7 +1205,7 @@ static void stream_to_handler(const Selector *sel, URL *url, const char *filenam
if (pipe(fds) == -1) return;
if (fcntl(fds[1], F_SETFD, FD_CLOEXEC) == 0 && (fp = fdopen(fds[1], "w")) != NULL) {
setbuf(fp, NULL);
if ((c = url->proto->download(sel, url, &mime, &parser, 1)) != NULL) {
if ((c = url->proto->download(sel, url, &mime, &parser, 0, 1)) != NULL) {
if ((handler = find_mime_handler(mime)) != NULL && (pid = start_handler(handler, filename, command, sizeof(command), sel, url, fds[0])) > 0) {
close(fds[0]); fds[0] = -1;
save_body(url, c, fp);
Expand Down Expand Up @@ -1219,7 +1250,7 @@ static void download_to_file(const Selector *sel, URL *url, const char *def) {
}
if ((fp = fopen(filename, "wb")) == NULL) error(0, "cannot create file `%s`: %s", filename, strerror(errno));
else {
if ((c = url->proto->download(sel, url, &mime, &parser, 1)) != NULL) {
if ((c = url->proto->download(sel, url, &mime, &parser, 0, 1)) != NULL) {
ret = save_body(url, c, fp);
url->proto->close(c);
}
Expand Down Expand Up @@ -1266,7 +1297,7 @@ static SelectorList download_text(const Selector *sel, URL *url, int ask, int ha
size_t parsed, length = 0, total = 0, prog = 0;
int received, pre = 0, width, ok = 0, links = 0;

if (url->proto == NULL || (c = url->proto->download(sel, url, &mime, &parser, ask)) == NULL) goto out;
if (url->proto == NULL || (c = url->proto->download(sel, url, &mime, &parser, 0, ask)) == NULL) goto out;
if (parser == NULL) {
if (handle) save_and_handle(sel, url, c, mime);
goto out;
Expand Down Expand Up @@ -1795,6 +1826,11 @@ static void quit_client() {
}


static void sigint(int sig) {
(void)sig;
}


int main(int argc, char **argv) {
struct sigaction sa = {.sa_handler = sigint};

Expand Down
Loading

0 comments on commit f29f59b

Please sign in to comment.