1 | /*************************************************************************** |
2 | * _ _ ____ _ |
3 | * Project ___| | | | _ \| | |
4 | * / __| | | | |_) | | |
5 | * | (__| |_| | _ <| |___ |
6 | * \___|\___/|_| \_\_____| |
7 | * |
8 | * Copyright (C) 1998 - 2019, 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.haxx.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 | ***************************************************************************/ |
22 | |
23 | #include "curl_setup.h" |
24 | |
25 | #ifndef CURL_DISABLE_GOPHER |
26 | |
27 | #include "urldata.h" |
28 | #include <curl/curl.h> |
29 | #include "transfer.h" |
30 | #include "sendf.h" |
31 | #include "progress.h" |
32 | #include "gopher.h" |
33 | #include "select.h" |
34 | #include "strdup.h" |
35 | #include "url.h" |
36 | #include "escape.h" |
37 | #include "warnless.h" |
38 | #include "curl_printf.h" |
39 | #include "curl_memory.h" |
40 | /* The last #include file should be: */ |
41 | #include "memdebug.h" |
42 | |
43 | /* |
44 | * Forward declarations. |
45 | */ |
46 | |
47 | static CURLcode gopher_do(struct connectdata *conn, bool *done); |
48 | |
49 | /* |
50 | * Gopher protocol handler. |
51 | * This is also a nice simple template to build off for simple |
52 | * connect-command-download protocols. |
53 | */ |
54 | |
55 | const struct Curl_handler Curl_handler_gopher = { |
56 | "GOPHER" , /* scheme */ |
57 | ZERO_NULL, /* setup_connection */ |
58 | gopher_do, /* do_it */ |
59 | ZERO_NULL, /* done */ |
60 | ZERO_NULL, /* do_more */ |
61 | ZERO_NULL, /* connect_it */ |
62 | ZERO_NULL, /* connecting */ |
63 | ZERO_NULL, /* doing */ |
64 | ZERO_NULL, /* proto_getsock */ |
65 | ZERO_NULL, /* doing_getsock */ |
66 | ZERO_NULL, /* domore_getsock */ |
67 | ZERO_NULL, /* perform_getsock */ |
68 | ZERO_NULL, /* disconnect */ |
69 | ZERO_NULL, /* readwrite */ |
70 | ZERO_NULL, /* connection_check */ |
71 | PORT_GOPHER, /* defport */ |
72 | CURLPROTO_GOPHER, /* protocol */ |
73 | PROTOPT_NONE /* flags */ |
74 | }; |
75 | |
76 | static CURLcode gopher_do(struct connectdata *conn, bool *done) |
77 | { |
78 | CURLcode result = CURLE_OK; |
79 | struct Curl_easy *data = conn->data; |
80 | curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; |
81 | char *gopherpath; |
82 | char *path = data->state.up.path; |
83 | char *query = data->state.up.query; |
84 | char *sel = NULL; |
85 | char *sel_org = NULL; |
86 | ssize_t amount, k; |
87 | size_t len; |
88 | |
89 | *done = TRUE; /* unconditionally */ |
90 | |
91 | /* path is guaranteed non-NULL */ |
92 | DEBUGASSERT(path); |
93 | |
94 | if(query) |
95 | gopherpath = aprintf("%s?%s" , path, query); |
96 | else |
97 | gopherpath = strdup(path); |
98 | |
99 | if(!gopherpath) |
100 | return CURLE_OUT_OF_MEMORY; |
101 | |
102 | /* Create selector. Degenerate cases: / and /1 => convert to "" */ |
103 | if(strlen(gopherpath) <= 2) { |
104 | sel = (char *)"" ; |
105 | len = strlen(sel); |
106 | free(gopherpath); |
107 | } |
108 | else { |
109 | char *newp; |
110 | |
111 | /* Otherwise, drop / and the first character (i.e., item type) ... */ |
112 | newp = gopherpath; |
113 | newp += 2; |
114 | |
115 | /* ... and finally unescape */ |
116 | result = Curl_urldecode(data, newp, 0, &sel, &len, FALSE); |
117 | free(gopherpath); |
118 | if(result) |
119 | return result; |
120 | sel_org = sel; |
121 | } |
122 | |
123 | /* We use Curl_write instead of Curl_sendf to make sure the entire buffer is |
124 | sent, which could be sizeable with long selectors. */ |
125 | k = curlx_uztosz(len); |
126 | |
127 | for(;;) { |
128 | result = Curl_write(conn, sockfd, sel, k, &amount); |
129 | if(!result) { /* Which may not have written it all! */ |
130 | result = Curl_client_write(conn, CLIENTWRITE_HEADER, sel, amount); |
131 | if(result) |
132 | break; |
133 | |
134 | k -= amount; |
135 | sel += amount; |
136 | if(k < 1) |
137 | break; /* but it did write it all */ |
138 | } |
139 | else |
140 | break; |
141 | |
142 | /* Don't busyloop. The entire loop thing is a work-around as it causes a |
143 | BLOCKING behavior which is a NO-NO. This function should rather be |
144 | split up in a do and a doing piece where the pieces that aren't |
145 | possible to send now will be sent in the doing function repeatedly |
146 | until the entire request is sent. |
147 | |
148 | Wait a while for the socket to be writable. Note that this doesn't |
149 | acknowledge the timeout. |
150 | */ |
151 | if(SOCKET_WRITABLE(sockfd, 100) < 0) { |
152 | result = CURLE_SEND_ERROR; |
153 | break; |
154 | } |
155 | } |
156 | |
157 | free(sel_org); |
158 | |
159 | if(!result) |
160 | /* We can use Curl_sendf to send the terminal \r\n relatively safely and |
161 | save allocing another string/doing another _write loop. */ |
162 | result = Curl_sendf(sockfd, conn, "\r\n" ); |
163 | if(result) { |
164 | failf(data, "Failed sending Gopher request" ); |
165 | return result; |
166 | } |
167 | result = Curl_client_write(conn, CLIENTWRITE_HEADER, (char *)"\r\n" , 2); |
168 | if(result) |
169 | return result; |
170 | |
171 | Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); |
172 | return CURLE_OK; |
173 | } |
174 | #endif /*CURL_DISABLE_GOPHER*/ |
175 | |