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. --- 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 + 7 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 lib/gemini.c create mode 100644 lib/gemini.h (limited to 'lib') 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 -- cgit v1.2.3