gopher.c (7790B)
1 /*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. 9 * 10 * This software is licensed as described in the file COPYING, which 11 * you should have received as part of this distribution. The terms 12 * are also available at https://curl.se/docs/copyright.html. 13 * 14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell 15 * copies of the Software, and permit persons to whom the Software is 16 * furnished to do so, under the terms of the COPYING file. 17 * 18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 19 * KIND, either express or implied. 20 * 21 * SPDX-License-Identifier: curl 22 * 23 ***************************************************************************/ 24 25 #include "curl_setup.h" 26 27 #ifndef CURL_DISABLE_GOPHER 28 29 #include "urldata.h" 30 #include <curl/curl.h> 31 #include "transfer.h" 32 #include "sendf.h" 33 #include "cfilters.h" 34 #include "connect.h" 35 #include "progress.h" 36 #include "gopher.h" 37 #include "select.h" 38 #include "strdup.h" 39 #include "vtls/vtls.h" 40 #include "url.h" 41 #include "escape.h" 42 #include "curlx/warnless.h" 43 #include "curl_printf.h" 44 #include "curl_memory.h" 45 /* The last #include file should be: */ 46 #include "memdebug.h" 47 48 /* 49 * Forward declarations. 50 */ 51 52 static CURLcode gopher_do(struct Curl_easy *data, bool *done); 53 #ifdef USE_SSL 54 static CURLcode gopher_connect(struct Curl_easy *data, bool *done); 55 static CURLcode gopher_connecting(struct Curl_easy *data, bool *done); 56 #endif 57 58 /* 59 * Gopher protocol handler. 60 * This is also a nice simple template to build off for simple 61 * connect-command-download protocols. 62 */ 63 64 const struct Curl_handler Curl_handler_gopher = { 65 "gopher", /* scheme */ 66 ZERO_NULL, /* setup_connection */ 67 gopher_do, /* do_it */ 68 ZERO_NULL, /* done */ 69 ZERO_NULL, /* do_more */ 70 ZERO_NULL, /* connect_it */ 71 ZERO_NULL, /* connecting */ 72 ZERO_NULL, /* doing */ 73 ZERO_NULL, /* proto_getsock */ 74 ZERO_NULL, /* doing_getsock */ 75 ZERO_NULL, /* domore_getsock */ 76 ZERO_NULL, /* perform_getsock */ 77 ZERO_NULL, /* disconnect */ 78 ZERO_NULL, /* write_resp */ 79 ZERO_NULL, /* write_resp_hd */ 80 ZERO_NULL, /* connection_check */ 81 ZERO_NULL, /* attach connection */ 82 ZERO_NULL, /* follow */ 83 PORT_GOPHER, /* defport */ 84 CURLPROTO_GOPHER, /* protocol */ 85 CURLPROTO_GOPHER, /* family */ 86 PROTOPT_NONE /* flags */ 87 }; 88 89 #ifdef USE_SSL 90 const struct Curl_handler Curl_handler_gophers = { 91 "gophers", /* scheme */ 92 ZERO_NULL, /* setup_connection */ 93 gopher_do, /* do_it */ 94 ZERO_NULL, /* done */ 95 ZERO_NULL, /* do_more */ 96 gopher_connect, /* connect_it */ 97 gopher_connecting, /* connecting */ 98 ZERO_NULL, /* doing */ 99 ZERO_NULL, /* proto_getsock */ 100 ZERO_NULL, /* doing_getsock */ 101 ZERO_NULL, /* domore_getsock */ 102 ZERO_NULL, /* perform_getsock */ 103 ZERO_NULL, /* disconnect */ 104 ZERO_NULL, /* write_resp */ 105 ZERO_NULL, /* write_resp_hd */ 106 ZERO_NULL, /* connection_check */ 107 ZERO_NULL, /* attach connection */ 108 ZERO_NULL, /* follow */ 109 PORT_GOPHER, /* defport */ 110 CURLPROTO_GOPHERS, /* protocol */ 111 CURLPROTO_GOPHER, /* family */ 112 PROTOPT_SSL /* flags */ 113 }; 114 115 static CURLcode gopher_connect(struct Curl_easy *data, bool *done) 116 { 117 (void)data; 118 (void)done; 119 return CURLE_OK; 120 } 121 122 static CURLcode gopher_connecting(struct Curl_easy *data, bool *done) 123 { 124 struct connectdata *conn = data->conn; 125 CURLcode result; 126 127 result = Curl_conn_connect(data, FIRSTSOCKET, TRUE, done); 128 if(result) 129 connclose(conn, "Failed TLS connection"); 130 *done = TRUE; 131 return result; 132 } 133 #endif 134 135 static CURLcode gopher_do(struct Curl_easy *data, bool *done) 136 { 137 CURLcode result = CURLE_OK; 138 struct connectdata *conn = data->conn; 139 curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; 140 char *gopherpath; 141 char *path = data->state.up.path; 142 char *query = data->state.up.query; 143 char *sel = NULL; 144 char *sel_org = NULL; 145 timediff_t timeout_ms; 146 ssize_t k; 147 size_t amount, len; 148 int what; 149 150 *done = TRUE; /* unconditionally */ 151 152 /* path is guaranteed non-NULL */ 153 DEBUGASSERT(path); 154 155 if(query) 156 gopherpath = aprintf("%s?%s", path, query); 157 else 158 gopherpath = strdup(path); 159 160 if(!gopherpath) 161 return CURLE_OUT_OF_MEMORY; 162 163 /* Create selector. Degenerate cases: / and /1 => convert to "" */ 164 if(strlen(gopherpath) <= 2) { 165 sel = (char *)CURL_UNCONST(""); 166 len = strlen(sel); 167 free(gopherpath); 168 } 169 else { 170 char *newp; 171 172 /* Otherwise, drop / and the first character (i.e., item type) ... */ 173 newp = gopherpath; 174 newp += 2; 175 176 /* ... and finally unescape */ 177 result = Curl_urldecode(newp, 0, &sel, &len, REJECT_ZERO); 178 free(gopherpath); 179 if(result) 180 return result; 181 sel_org = sel; 182 } 183 184 k = curlx_uztosz(len); 185 186 for(;;) { 187 /* Break out of the loop if the selector is empty because OpenSSL and/or 188 LibreSSL fail with errno 0 if this is the case. */ 189 if(strlen(sel) < 1) 190 break; 191 192 result = Curl_xfer_send(data, sel, k, FALSE, &amount); 193 if(!result) { /* Which may not have written it all! */ 194 result = Curl_client_write(data, CLIENTWRITE_HEADER, sel, amount); 195 if(result) 196 break; 197 198 k -= amount; 199 sel += amount; 200 if(k < 1) 201 break; /* but it did write it all */ 202 } 203 else 204 break; 205 206 timeout_ms = Curl_timeleft(data, NULL, FALSE); 207 if(timeout_ms < 0) { 208 result = CURLE_OPERATION_TIMEDOUT; 209 break; 210 } 211 if(!timeout_ms) 212 timeout_ms = TIMEDIFF_T_MAX; 213 214 /* Do not busyloop. The entire loop thing is a work-around as it causes a 215 BLOCKING behavior which is a NO-NO. This function should rather be 216 split up in a do and a doing piece where the pieces that are not 217 possible to send now will be sent in the doing function repeatedly 218 until the entire request is sent. 219 */ 220 what = SOCKET_WRITABLE(sockfd, timeout_ms); 221 if(what < 0) { 222 result = CURLE_SEND_ERROR; 223 break; 224 } 225 else if(!what) { 226 result = CURLE_OPERATION_TIMEDOUT; 227 break; 228 } 229 } 230 231 free(sel_org); 232 233 if(!result) 234 result = Curl_xfer_send(data, "\r\n", 2, FALSE, &amount); 235 if(result) { 236 failf(data, "Failed sending Gopher request"); 237 return result; 238 } 239 result = Curl_client_write(data, CLIENTWRITE_HEADER, "\r\n", 2); 240 if(result) 241 return result; 242 243 Curl_xfer_setup1(data, CURL_XFER_RECV, -1, FALSE); 244 return CURLE_OK; 245 } 246 #endif /* CURL_DISABLE_GOPHER */