From 327e5b472b67e1eddbceae5afda3396c78873911 Mon Sep 17 00:00:00 2001 From: Witold Filipczyk Date: Fri, 2 Jul 2021 20:01:10 +0200 Subject: [PATCH] [gemini] gemini protocol support For now only status 20. --- src/protocol/Makefile | 2 +- src/protocol/gemini/Makefile | 6 + src/protocol/gemini/gemini.c | 321 ++++++++++++++++++++++++++++++++ src/protocol/gemini/gemini.h | 31 +++ src/protocol/gemini/meson.build | 1 + src/protocol/meson.build | 1 + src/protocol/protocol.c | 2 + src/protocol/protocol.h | 1 + 8 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 src/protocol/gemini/Makefile create mode 100644 src/protocol/gemini/gemini.c create mode 100644 src/protocol/gemini/gemini.h create mode 100644 src/protocol/gemini/meson.build diff --git a/src/protocol/Makefile b/src/protocol/Makefile index fe23b2bd..d4a2d7e8 100644 --- a/src/protocol/Makefile +++ b/src/protocol/Makefile @@ -10,7 +10,7 @@ SUBDIRS-$(CONFIG_NNTP) += nntp SUBDIRS-$(CONFIG_SMB) += smb SUBDIRS-$(CONFIG_URI_REWRITE) += rewrite -SUBDIRS = auth file http test +SUBDIRS = auth file gemini http test OBJS-$(CONFIG_DATA) += data.o diff --git a/src/protocol/gemini/Makefile b/src/protocol/gemini/Makefile new file mode 100644 index 00000000..9039144a --- /dev/null +++ b/src/protocol/gemini/Makefile @@ -0,0 +1,6 @@ +top_builddir=../../.. +include $(top_builddir)/Makefile.config + +OBJS = gemini.o + +include $(top_srcdir)/Makefile.lib diff --git a/src/protocol/gemini/gemini.c b/src/protocol/gemini/gemini.c new file mode 100644 index 00000000..6933cdc5 --- /dev/null +++ b/src/protocol/gemini/gemini.c @@ -0,0 +1,321 @@ +/* gemini protocol implementation */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include "elinks.h" + +#include "cache/cache.h" +#include "config/options.h" +#include "cookies/cookies.h" +#include "intl/charsets.h" +#include "intl/gettext/libintl.h" +#include "main/module.h" +#include "network/connection.h" +#include "network/progress.h" +#include "network/socket.h" +#include "osdep/ascii.h" +#include "osdep/osdep.h" +#include "osdep/sysname.h" +#include "protocol/date.h" +#include "protocol/gemini/gemini.h" +#include "protocol/header.h" +#include "protocol/uri.h" +#include "session/session.h" +#include "terminal/terminal.h" +#include "util/base64.h" +#include "util/conv.h" +#include "util/memory.h" +#include "util/string.h" + + +static void done_gemini(); + +struct module gemini_protocol_module = struct_module( + /* name: */ N_("Gemini"), + /* options: */ NULL, + /* hooks: */ NULL, + /* submodules: */ NULL, + /* data: */ NULL, + /* init: */ NULL, + /* done: */ done_gemini +); + + +static void +done_gemini(void) +{ +} + +static void +gemini_end_request(struct connection *conn, struct connection_state state, + int notrunc) +{ + shutdown_connection_stream(conn); + abort_connection(conn, state); +} + +static void gemini_send_header(struct socket *); + +void +gemini_protocol_handler(struct connection *conn) +{ + make_connection(conn->socket, conn->uri, gemini_send_header, 0); +} + +static void +done_gemini_connection(struct connection *conn) +{ + struct gemini_connection_info *gemini = conn->info; + + mem_free(gemini); + conn->info = NULL; + conn->done = NULL; +} + +struct gemini_connection_info * +init_gemini_connection_info(struct connection *conn) +{ + struct gemini_connection_info *gemini; + + gemini = mem_calloc(1, sizeof(*gemini)); + if (!gemini) { + gemini_end_request(conn, connection_state(S_OUT_OF_MEM), 0); + return NULL; + } + + mem_free_set(&conn->info, gemini); + conn->done = NULL;//done_gemini_connection; + + return gemini; +} + +static void +gemini_send_header(struct socket *socket) +{ + struct connection *conn = socket->conn; + struct gemini_connection_info *gemini; + struct string header; + struct uri *uri = conn->uri; + char *uristr; + + /* Sanity check for a host */ + if (!uri || !uri->host || !*uri->host || !uri->hostlen) { + gemini_end_request(conn, connection_state(S_BAD_URL), 0); + return; + } + + uristr = get_uri_string(uri, URI_BASE); + if (!uristr) return; + + gemini = init_gemini_connection_info(conn); + if (!gemini) return; + + if (!init_string(&header)) { + gemini_end_request(conn, connection_state(S_OUT_OF_MEM), 0); + return; + } + +// if (!conn->cached) conn->cached = find_in_cache(uri); + + add_to_string(&header, uristr); + mem_free(uristr); + add_crlf_to_string(&header); + + request_from_socket(socket, header.source, header.length, + connection_state(S_SENT), + SOCKET_END_ONCLOSE, gemini_got_header); + done_string(&header); +} + +static void read_gemini_data(struct socket *socket, struct read_buffer *rb); + +static void +read_more_gemini_data(struct connection *conn, struct read_buffer *rb, + int already_got_anything) +{ + struct connection_state state = already_got_anything + ? connection_state(S_TRANS) : conn->state; + + read_from_socket(conn->socket, rb, state, read_gemini_data); +} + +static void +read_gemini_data_done(struct connection *conn) +{ + struct gemini_connection_info *gemini = conn->info; + + /* There's no content but an error so just print + * that instead of nothing. */ +// if (!conn->from) { +// if (http->code >= 400) { +// http_error_document(conn, http->code); +// +// } else { +// /* This is not an error, thus fine. No need generate any +// * document, as this may be empty and it's not a problem. +// * In case of 3xx, we're probably just getting kicked to +// * another page anyway. And in case of 2xx, the document +// * may indeed be empty and thus the user should see it so. */ +// } +// } + + gemini_end_request(conn, connection_state(S_OK), 0); +} + +/* Returns 0 if more data, 1 if done. */ +static int +read_normal_gemini_data(struct connection *conn, struct read_buffer *rb) +{ + struct gemini_connection_info *gemini = conn->info; + int data_len; + int len = rb->length; + + conn->received += len; + + data_len = len; + add_fragment(conn->cached, conn->from, rb->data, data_len); + conn->from += data_len; + + kill_buffer_data(rb, len); + + if (conn->socket->state == SOCKET_CLOSED) { + return 2; + } + + return !!data_len; +} + +static void +read_gemini_data(struct socket *socket, struct read_buffer *rb) +{ + struct connection *conn = socket->conn; + struct gemini_connection_info *gemini = conn->info; + int ret; + + if (socket->state == SOCKET_CLOSED) { + read_gemini_data_done(conn); + return; + } + + ret = read_normal_gemini_data(conn, rb); + + switch (ret) { + case 0: + read_more_gemini_data(conn, rb, 0); + break; + case 1: + read_more_gemini_data(conn, rb, 1); + break; + case 2: + read_gemini_data_done(conn); + break; + default: + assertm(ret == -1, "Unexpected return value: %d", ret); + abort_connection(conn, connection_state(S_HTTP_ERROR)); + } +} + +/* Returns LF offset */ +static int +get_header(struct read_buffer *rb) +{ + int i; + + for (i = 0; i < rb->length; i++) { + unsigned char a0 = rb->data[i]; + unsigned char a1 = rb->data[i + 1]; + + if (a0 == ASCII_LF + && i < rb->length - 1) + return i; + if (a0 == ASCII_CR && i < rb->length - 1) { + if (a1 != ASCII_LF) return -1; + + return i + 1; + } + } + + return 0; +} + +static int +get_gemini_code(struct read_buffer *rb, int *code) +{ + char *head = rb->data; + + *code = 0; + if (rb->data[0] < '1' || rb->data[0] > '6') return -1; + if (rb->data[1] < '0' || rb->data[1] > '9') return -1; + + *code = (rb->data[0] - '0') * 10 + (rb->data[1] - '0'); + + return 0; +} + +void +gemini_got_header(struct socket *socket, struct read_buffer *rb) +{ + struct connection *conn = socket->conn; + struct gemini_connection_info *gemini = conn->info; + struct string head_string; + struct uri *uri = conn->uri; /* Set to the real uri */ + struct connection_state state = (!is_in_state(conn->state, S_PROC) + ? connection_state(S_GETH) + : connection_state(S_PROC)); + int a, h = 20; + int cf; + + if (socket->state == SOCKET_CLOSED) { + retry_connection(conn, connection_state(S_CANT_READ)); + return; + } + socket->state = SOCKET_END_ONCLOSE; + +again: + a = get_header(rb); + if (a == -1) { + abort_connection(conn, connection_state(S_HTTP_ERROR)); + return; + } + if (!a) { + read_from_socket(conn->socket, rb, state, gemini_got_header); + return; + } + + if ((a && get_gemini_code(rb, &h)) + || (h >= 30 && h < 70)) { + abort_connection(conn, connection_state(S_HTTP_ERROR)); + return; + } + + init_string(&head_string); + add_to_string(&head_string, "\nContent-Type:"); + add_bytes_to_string(&head_string, rb->data + 2, a); + + gemini->code = h; + + if (h >= 30 && h < 70) { + done_string(&head_string); + abort_connection(conn, connection_state(S_HTTP_ERROR)); + return; + } + + conn->cached = get_cache_entry(conn->uri); + if (!conn->cached) { + done_string(&head_string); + abort_connection(conn, connection_state(S_OUT_OF_MEM)); + return; + } + mem_free_set(&conn->cached->head, head_string.source); + + kill_buffer_data(rb, a); + read_gemini_data(socket, rb); +} diff --git a/src/protocol/gemini/gemini.h b/src/protocol/gemini/gemini.h new file mode 100644 index 00000000..266690f3 --- /dev/null +++ b/src/protocol/gemini/gemini.h @@ -0,0 +1,31 @@ + +#ifndef EL__PROTOCOL_GEMINI_GEMINI_H +#define EL__PROTOCOL_GEMINI_GEMINI_H + +#include "main/module.h" +#include "protocol/protocol.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct connection; +struct read_buffer; +struct socket; + +struct gemini_connection_info { + int code; +}; + +extern struct module gemini_protocol_module; + +extern protocol_handler_T gemini_protocol_handler; + +struct gemini_connection_info *init_gemini_connection_info(struct connection *conn); +void gemini_got_header(struct socket *, struct read_buffer *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/protocol/gemini/meson.build b/src/protocol/gemini/meson.build new file mode 100644 index 00000000..8ddabb5b --- /dev/null +++ b/src/protocol/gemini/meson.build @@ -0,0 +1 @@ +srcs += files('gemini.c') diff --git a/src/protocol/meson.build b/src/protocol/meson.build index b83c51d4..b0ceb8f1 100644 --- a/src/protocol/meson.build +++ b/src/protocol/meson.build @@ -24,6 +24,7 @@ if conf_data.get('CONFIG_URI_REWRITE') endif subdir('auth') subdir('file') +subdir('gemini') subdir('http') if conf_data.get('CONFIG_DATA') diff --git a/src/protocol/protocol.c b/src/protocol/protocol.c index 00b43058..e3d88685 100644 --- a/src/protocol/protocol.c +++ b/src/protocol/protocol.c @@ -36,6 +36,7 @@ #include "protocol/finger/finger.h" #include "protocol/fsp/fsp.h" #include "protocol/ftp/ftp.h" +#include "protocol/gemini/gemini.h" #include "protocol/gopher/gopher.h" #include "protocol/http/http.h" #include "protocol/nntp/connection.h" @@ -65,6 +66,7 @@ static const struct protocol_backend protocol_backends[] = { { "finger", 79, finger_protocol_handler, 1, 1, 0, 0, 1 }, { "fsp", 21, fsp_protocol_handler, 1, 1, 0, 0, 1 }, { "ftp", 21, ftp_protocol_handler, 1, 1, 0, 0, 0 }, + { "gemini", 1965, gemini_protocol_handler, 1, 1, 0, 1, 1 }, { "gopher", 70, gopher_protocol_handler, 1, 1, 0, 0, 1 }, { "http", 80, http_protocol_handler, 1, 1, 0, 0, 1 }, { "https", 443, https_protocol_handler, 1, 1, 0, 1, 1 }, diff --git a/src/protocol/protocol.h b/src/protocol/protocol.h index 947552b2..c0e89d3a 100644 --- a/src/protocol/protocol.h +++ b/src/protocol/protocol.h @@ -21,6 +21,7 @@ enum protocol { PROTOCOL_FINGER, PROTOCOL_FSP, PROTOCOL_FTP, + PROTOCOL_GEMINI, PROTOCOL_GOPHER, PROTOCOL_HTTP, PROTOCOL_HTTPS,