From d8c87b67316fa87fdb9bd5c8b6b1d3291d9ebc9f Mon Sep 17 00:00:00 2001 From: Ben Burwell Date: Thu, 21 May 2020 15:00:11 -0400 Subject: Add gemini protocol support The gemini protocol's "speculative specification" is documented at the following URLs: https://gemini.circumlunar.space/docs/spec-spec.txt gopher://gemini.circumlunar.space/1/docs/spec-spec.txt gemini://gemini.circumlunar.space/docs/spec-spec.txt This patch introduces preliminary support for version 0.11.0 of the specification, as of March 1st 2020. --- CMakeLists.txt | 4 ++ configure.ac | 19 ++++++ docs/CURL-DISABLE.md | 4 ++ include/curl/curl.h | 1 + lib/Makefile.inc | 2 + lib/curl_setup.h | 3 + lib/gemini.c | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++ lib/gemini.h | 29 +++++++++ lib/url.c | 11 +++- lib/urldata.h | 1 + lib/version.c | 3 + src/tool_libinfo.c | 1 + src/tool_paramhlp.c | 1 + src/tool_setopt.c | 1 + 14 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 lib/gemini.c create mode 100644 lib/gemini.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a13395db7..1b52f87f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -169,6 +169,8 @@ option(CURL_DISABLE_IMAP "to disable IMAP" OFF) mark_as_advanced(CURL_DISABLE_IMAP) option(CURL_DISABLE_SMTP "to disable SMTP" OFF) mark_as_advanced(CURL_DISABLE_SMTP) +option(CURL_DISABLE_GEMINI "to disable Gemini" OFF) +mark_as_advanced(CURL_DISABLE_GEMINI) option(CURL_DISABLE_GOPHER "to disable Gopher" OFF) mark_as_advanced(CURL_DISABLE_GOPHER) option(CURL_ENABLE_MQTT "to enable MQTT" OFF) @@ -188,6 +190,7 @@ if(HTTP_ONLY) set(CURL_DISABLE_SMB ON) set(CURL_DISABLE_SMTP ON) set(CURL_DISABLE_GOPHER ON) + set(CURL_DISABLE_GEMINI ON) endif() option(CURL_DISABLE_COOKIES "to disable cookies support" OFF) @@ -1363,6 +1366,7 @@ _add_if("LDAPS" NOT CURL_DISABLE_LDAPS AND (NOT USE_OPENLDAP AND HAVE_LDAP_SSL))) _add_if("DICT" NOT CURL_DISABLE_DICT) _add_if("TFTP" NOT CURL_DISABLE_TFTP) +_add_if("GEMINI" NOT CURL_DISABLE_GEMINI) _add_if("GOPHER" NOT CURL_DISABLE_GOPHER) _add_if("POP3" NOT CURL_DISABLE_POP3) _add_if("POP3S" NOT CURL_DISABLE_POP3 AND SSL_ENABLED) diff --git a/configure.ac b/configure.ac index f0fbcb0af..b4fcb2eae 100755 --- a/configure.ac +++ b/configure.ac @@ -621,6 +621,22 @@ AC_HELP_STRING([--disable-smtp],[Disable SMTP support]), AC_MSG_RESULT(yes) ) +AC_MSG_CHECKING([whether to support gemini]) +AC_ARG_ENABLE(gemini, +AC_HELP_STRING([--enable-gemini],[Enable Gemini support]) +AC_HELP_STRING([--disable-gemini],[Disable Gemini support]), +[ case "$enableval" in + no) + AC_MSG_RESULT(no) + AC_DEFINE(CURL_DISABLE_GEMINI, 1, [to disable Gemini]) + AC_SUBST(CURL_DISABLE_GEMINI, [1]) + ;; + *) AC_MSG_RESULT(yes) + ;; + esac ], + AC_MSG_RESULT(yes) +) + AC_MSG_CHECKING([whether to support gopher]) AC_ARG_ENABLE(gopher, AC_HELP_STRING([--enable-gopher],[Enable Gopher support]) @@ -4933,6 +4949,9 @@ fi if test "x$CURL_DISABLE_TFTP" != "x1"; then SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS TFTP" fi +if test "x$CURL_DISABLE_GEMINI" != "x1"; then + SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS GEMINI" +fi if test "x$CURL_DISABLE_GOPHER" != "x1"; then SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS GOPHER" fi diff --git a/docs/CURL-DISABLE.md b/docs/CURL-DISABLE.md index 83436b473..e42878c6b 100644 --- a/docs/CURL-DISABLE.md +++ b/docs/CURL-DISABLE.md @@ -24,6 +24,10 @@ Disable the FILE protocol Disable the FTP (and FTPS) protocol +## CURL_DISABLE_GEMINI + +Disable the Gemini protocol. + ## CURL_DISABLE_GOPHER Disable the GOPHER protocol. diff --git a/include/curl/curl.h b/include/curl/curl.h index d5f8817d5..8aef99fbb 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -942,6 +942,7 @@ typedef enum { #define CURLPROTO_SMB (1<<26) #define CURLPROTO_SMBS (1<<27) #define CURLPROTO_MQTT (1<<28) +#define CURLPROTO_GEMINI (1<<29) #define CURLPROTO_ALL (~0) /* enable everything */ /* long may be 32 or 64 bits, but we should never depend on anything else diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 723b826e9..d8afeb1db 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -51,6 +51,7 @@ LIB_CFILES = altsvc.c amigaos.c asyn-ares.c asyn-thread.c base64.c \ curl_ntlm_core.c curl_ntlm_wb.c curl_path.c curl_range.c curl_rtmp.c \ curl_sasl.c curl_sspi.c curl_threads.c dict.c dotdot.c easy.c escape.c \ file.c fileinfo.c formdata.c ftp.c url.c ftplistparser.c getenv.c getinfo.c \ + gemini.c \ gopher.c hash.c hmac.c hostasyn.c hostcheck.c hostip.c hostip4.c hostip6.c \ hostsyn.c http.c http2.c http_chunks.c http_digest.c http_negotiate.c \ http_ntlm.c http_proxy.c idn_win32.c if2ip.c imap.c inet_ntop.c inet_pton.c \ @@ -70,6 +71,7 @@ LIB_HFILES = altsvc.h amigaos.h arpa_telnet.h asyn.h conncache.h connect.h \ curl_printf.h curl_range.h curl_rtmp.h curl_sasl.h curl_sec.h curl_setup.h \ curl_setup_once.h curl_sha256.h curl_sspi.h curl_threads.h curlx.h dict.h \ dotdot.h easyif.h escape.h file.h fileinfo.h formdata.h ftp.h url.h \ + gemini.h \ ftplistparser.h getinfo.h gopher.h hash.h hostcheck.h hostip.h http.h \ http2.h http_chunks.h http_digest.h http_negotiate.h http_ntlm.h \ http_proxy.h if2ip.h imap.h inet_ntop.h inet_pton.h llist.h memdebug.h \ diff --git a/lib/curl_setup.h b/lib/curl_setup.h index 82b962b89..257805890 100644 --- a/lib/curl_setup.h +++ b/lib/curl_setup.h @@ -196,6 +196,9 @@ # ifndef CURL_DISABLE_GOPHER # define CURL_DISABLE_GOPHER # endif +# ifndef CURL_DISABLE_GEMINI +# define CURL_DISABLE_GEMINI +# endif # ifndef CURL_DISABLE_SMB # define CURL_DISABLE_SMB # endif diff --git a/lib/gemini.c b/lib/gemini.c new file mode 100644 index 000000000..bd1953d82 --- /dev/null +++ b/lib/gemini.c @@ -0,0 +1,179 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2020, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_GEMINI + +#include "urldata.h" +#include +#include "transfer.h" +#include "sendf.h" +#include "connect.h" +#include "progress.h" +#include "gemini.h" +#include "select.h" +#include "strdup.h" +#include "url.h" +#include "escape.h" +#include "warnless.h" +#include "curl_printf.h" +#include "curl_memory.h" +#include "vtls/vtls.h" +/* The last #include file should be: */ +#include "memdebug.h" + +/* + * Forward declarations. + */ + +static CURLcode gemini_do(struct connectdata *conn, bool *done); +static CURLcode gemini_connecting(struct connectdata *conn, bool *done); +CURLcode Curl_gemini_connect(struct connectdata *conn, bool *done); + +/* + * Gemini protocol handler. + */ + +const struct Curl_handler Curl_handler_gemini = { + "GEMINI", /* scheme */ + ZERO_NULL, /* setup_connection */ + gemini_do, /* do_it */ + ZERO_NULL, /* done */ + ZERO_NULL, /* do_more */ + Curl_gemini_connect, /* connect_it */ + gemini_connecting, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + ZERO_NULL, /* connection_check */ + PORT_GEMINI, /* defport */ + CURLPROTO_GEMINI, /* protocol */ + PROTOPT_NONE /* flags */ +}; + +static CURLcode gemini_do(struct connectdata *conn, bool *done) +{ + CURLcode result = CURLE_OK; + struct Curl_easy *data = conn->data; + curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; + timediff_t timeout_ms; + ssize_t amount, k; + size_t len; + int what; + char *req = NULL; + char *u = NULL; + + *done = TRUE; /* unconditionally */ + + /* format the request URL */ + if (curl_url_get(data->state.uh, CURLUPART_URL, &u, 0) != CURLUE_OK) + return CURLE_URL_MALFORMAT; + + /* build the request line */ + req = aprintf("%s\r\n", u); + if (!req) + return CURLE_OUT_OF_MEMORY; + + len = strlen(req); + + /* We use Curl_write instead of Curl_sendf to make sure the entire buffer is + sent, which could be sizeable with long selectors. */ + k = curlx_uztosz(len); + + for(;;) { + result = Curl_write(conn, sockfd, req, k, &amount); + if(!result) { /* Which may not have written it all! */ + result = Curl_client_write(conn, CLIENTWRITE_HEADER, req, amount); + if(result) + break; + + k -= amount; + req += amount; + if(k < 1) + break; /* but it did write it all */ + } + else + break; + + timeout_ms = Curl_timeleft(conn->data, NULL, FALSE); + if(timeout_ms < 0) { + result = CURLE_OPERATION_TIMEDOUT; + break; + } + if(!timeout_ms) + timeout_ms = TIMEDIFF_T_MAX; + + /* Don't busyloop. The entire loop thing is a work-around as it causes a + BLOCKING behavior which is a NO-NO. This function should rather be + split up in a do and a doing piece where the pieces that aren't + possible to send now will be sent in the doing function repeatedly + until the entire request is sent. + */ + what = SOCKET_WRITABLE(sockfd, timeout_ms); + if(what < 0) { + result = CURLE_SEND_ERROR; + break; + } + else if(!what) { + result = CURLE_OPERATION_TIMEDOUT; + break; + } + } + + Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); + return CURLE_OK; +} + +static CURLcode gemini_connecting(struct connectdata *conn, bool *done) +{ + CURLcode result; + DEBUGASSERT((conn) && (conn->handler->flags & PROTOPT_SSL)); + + /* perform SSL initialization for this socket */ + result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, done); + if(result) + connclose(conn, "Failed TLS connection"); + + return result; +} + +/* + * Curl_http_connect() performs HTTP stuff to do at connect-time, called from + * the generic Curl_connect(). + */ +CURLcode Curl_gemini_connect(struct connectdata *conn, bool *done) +{ + CURLcode result; + + /* perform SSL initialization */ + result = gemini_connecting(conn, done); + if(result) + return result; + + return CURLE_OK; +} +#endif /*CURL_DISABLE_GEMINI*/ diff --git a/lib/gemini.h b/lib/gemini.h new file mode 100644 index 000000000..54cc6afd0 --- /dev/null +++ b/lib/gemini.h @@ -0,0 +1,29 @@ +#ifndef HEADER_CURL_GEMINI_H +#define HEADER_CURL_GEMINI_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2019, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#ifndef CURL_DISABLE_GEMINI +extern const struct Curl_handler Curl_handler_gemini; +#endif + +#endif /* HEADER_CURL_GEMINI_H */ diff --git a/lib/url.c b/lib/url.c index 4021c5d78..cc5c282ad 100644 --- a/lib/url.c +++ b/lib/url.c @@ -113,6 +113,7 @@ bool curl_win32_idn_to_ascii(const char *in, char **out); #include "inet_ntop.h" #include "http_ntlm.h" #include "curl_rtmp.h" +#include "gemini.h" #include "gopher.h" #include "mqtt.h" #include "http_proxy.h" @@ -145,7 +146,7 @@ static unsigned int get_protocol_family(unsigned int protocol); * Protocol table. Schemes (roughly) in 2019 popularity order: * * HTTPS, HTTP, FTP, FTPS, SFTP, FILE, SCP, SMTP, LDAP, IMAPS, TELNET, IMAP, - * LDAPS, SMTPS, TFTP, SMB, POP3, GOPHER POP3S, RTSP, RTMP, SMBS, DICT + * LDAPS, SMTPS, TFTP, SMB, POP3, GOPHER POP3S, RTSP, RTMP, SMBS, DICT, GEMINI */ static const struct Curl_handler * const protocols[] = { @@ -249,6 +250,10 @@ static const struct Curl_handler * const protocols[] = { &Curl_handler_dict, #endif +#ifndef CURL_DISABLE_GEMINI + &Curl_handler_gemini, +#endif + (struct Curl_handler *) NULL }; @@ -4107,6 +4112,10 @@ static unsigned int get_protocol_family(unsigned int protocol) family = CURLPROTO_GOPHER; break; + case CURLPROTO_GEMINI: + family = CURLPROTO_GEMINI; + break; + case CURLPROTO_SMB: case CURLPROTO_SMBS: family = CURLPROTO_SMB; diff --git a/lib/urldata.h b/lib/urldata.h index 33ec160ac..e7efb497c 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -49,6 +49,7 @@ #define PORT_RTMPT PORT_HTTP #define PORT_RTMPS PORT_HTTPS #define PORT_GOPHER 70 +#define PORT_GEMINI 1965 #define PORT_MQTT 1883 #define DICT_MATCH "/MATCH:" diff --git a/lib/version.c b/lib/version.c index 14e509606..87c69a187 100644 --- a/lib/version.c +++ b/lib/version.c @@ -250,6 +250,9 @@ static const char * const protocols[] = { #if defined(USE_SSL) && !defined(CURL_DISABLE_FTP) "ftps", #endif +#ifndef CURL_DISABLE_GEMINI + "gemini", +#endif #ifndef CURL_DISABLE_GOPHER "gopher", #endif diff --git a/src/tool_libinfo.c b/src/tool_libinfo.c index de7ec4d18..4c26376b8 100644 --- a/src/tool_libinfo.c +++ b/src/tool_libinfo.c @@ -54,6 +54,7 @@ CURLcode get_libcurl_info(void) { "file", CURLPROTO_FILE }, { "ftp", CURLPROTO_FTP }, { "ftps", CURLPROTO_FTPS }, + { "gemini", CURLPROTO_GEMINI }, { "gopher", CURLPROTO_GOPHER }, { "http", CURLPROTO_HTTP }, { "https", CURLPROTO_HTTPS }, diff --git a/src/tool_paramhlp.c b/src/tool_paramhlp.c index c375bcc82..f6be25a52 100644 --- a/src/tool_paramhlp.c +++ b/src/tool_paramhlp.c @@ -335,6 +335,7 @@ long proto2num(struct OperationConfig *config, long *val, const char *str) { "smtp", CURLPROTO_SMTP }, { "smtps", CURLPROTO_SMTPS }, { "rtsp", CURLPROTO_RTSP }, + { "gemini", CURLPROTO_GEMINI }, { "gopher", CURLPROTO_GOPHER }, { "smb", CURLPROTO_SMB }, { "smbs", CURLPROTO_SMBS }, diff --git a/src/tool_setopt.c b/src/tool_setopt.c index 9858d49c9..383600ab7 100644 --- a/src/tool_setopt.c +++ b/src/tool_setopt.c @@ -145,6 +145,7 @@ const struct NameValue setopt_nv_CURLPROTO[] = { NV(CURLPROTO_FILE), NV(CURLPROTO_FTP), NV(CURLPROTO_FTPS), + NV(CURLPROTO_GEMINI), NV(CURLPROTO_GOPHER), NV(CURLPROTO_HTTP), NV(CURLPROTO_HTTPS), -- cgit v1.2.3