Coverage Report

Created: 2026-08-13 07:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/gopher.c
Line
Count
Source
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
#include "curl_setup.h"
25
#include "urldata.h"
26
#include "gopher.h"
27
28
#ifndef CURL_DISABLE_GOPHER
29
30
#include "transfer.h"
31
#include "sendf.h"
32
#include "curl_trc.h"
33
#include "cfilters.h"
34
#include "connect.h"
35
#include "select.h"
36
#include "url.h"
37
#include "escape.h"
38
39
#ifdef USE_SSL
40
static CURLcode gopher_connect(struct Curl_easy *data, bool *done)
41
0
{
42
0
  (void)data;
43
0
  (void)done;
44
0
  return CURLE_OK;
45
0
}
46
47
static CURLcode gopher_connecting(struct Curl_easy *data, bool *done)
48
0
{
49
0
  struct connectdata *conn = data->conn;
50
0
  CURLcode result;
51
52
0
  result = Curl_conn_connect(data, FIRSTSOCKET, TRUE, done);
53
0
  if(result)
54
0
    connclose(conn);
55
0
  *done = TRUE;
56
0
  return result;
57
0
}
58
#endif
59
60
static CURLcode gopher_do(struct Curl_easy *data, bool *done)
61
915
{
62
915
  CURLcode result = CURLE_OK;
63
915
  struct connectdata *conn = data->conn;
64
915
  curl_socket_t sockfd = conn->sock[FIRSTSOCKET];
65
915
  char *gopherpath;
66
915
  const char *path = data->state.up.path;
67
915
  const char *query = data->state.up.query;
68
915
  const char *buf = NULL;
69
915
  char *buf_alloc = NULL;
70
915
  size_t nwritten, buf_len;
71
915
  timediff_t timeout_ms;
72
915
  int what;
73
74
915
  *done = TRUE; /* unconditionally */
75
76
  /* path is guaranteed non-NULL */
77
915
  DEBUGASSERT(path);
78
79
915
  if(query)
80
37
    gopherpath = curl_maprintf("%s?%s", path, query);
81
878
  else
82
878
    gopherpath = curlx_strdup(path);
83
84
915
  if(!gopherpath)
85
0
    return CURLE_OUT_OF_MEMORY;
86
87
  /* Create selector. Degenerate cases: / and /1 => convert to "" */
88
915
  if(strlen(gopherpath) <= 2) {
89
801
    buf = "";
90
801
    buf_len = 0;
91
801
    curlx_free(gopherpath);
92
801
  }
93
114
  else {
94
114
    const char *newp;
95
96
    /* Otherwise, drop / and the first character (i.e., item type) ... */
97
114
    newp = gopherpath;
98
114
    newp += 2;
99
100
    /* ... and finally unescape */
101
114
    result = Curl_urldecode(newp, 0, &buf_alloc, &buf_len, REJECT_ZERO);
102
114
    curlx_free(gopherpath);
103
114
    if(result)
104
1
      return result;
105
113
    buf = buf_alloc;
106
107
    /* A decoded CR or LF would terminate the single-line gopher request and
108
       let a crafted URL smuggle additional bytes onto the wire. REJECT_ZERO
109
       only blocks NUL; reject CR and LF here too. A TAB is left alone as it
110
       is the legitimate gopher type-7 selector/search separator. */
111
113
    if(memchr(buf, '\r', buf_len) || memchr(buf, '\n', buf_len)) {
112
4
      curlx_free(buf_alloc);
113
4
      failf(data, "Bad gopher selector, CR or LF not allowed");
114
4
      return CURLE_URL_MALFORMAT;
115
4
    }
116
113
  }
117
118
910
  for(; buf_len;) {
119
120
109
    result = Curl_xfer_send(data, buf, buf_len, FALSE, &nwritten);
121
109
    if(!result) { /* Which may not have written it all! */
122
109
      result = Curl_client_write(data, CLIENTWRITE_HEADER, buf, nwritten);
123
109
      if(result)
124
1
        break;
125
126
108
      if(nwritten > buf_len) {
127
0
        DEBUGASSERT(0);
128
0
        break;
129
0
      }
130
108
      buf_len -= nwritten;
131
108
      buf += nwritten;
132
108
      if(!buf_len)
133
67
        break; /* but it did write it all */
134
108
    }
135
0
    else
136
0
      break;
137
138
41
    timeout_ms = Curl_timeleft_ms(data);
139
41
    if(timeout_ms < 0) {
140
0
      result = CURLE_OPERATION_TIMEDOUT;
141
0
      break;
142
0
    }
143
41
    if(!timeout_ms)
144
0
      timeout_ms = TIMEDIFF_T_MAX;
145
146
    /* Do not busyloop. The entire loop thing is a workaround as it causes a
147
       BLOCKING behavior which is a NO-NO. This function should rather be
148
       split up in a do and a doing piece where the pieces that are not
149
       possible to send now will be sent in the doing function repeatedly
150
       until the entire request is sent. */
151
41
    what = SOCKET_WRITABLE(sockfd, timeout_ms);
152
41
    if(what < 0) {
153
0
      result = CURLE_SEND_ERROR;
154
0
      break;
155
0
    }
156
41
    else if(!what) {
157
41
      result = CURLE_OPERATION_TIMEDOUT;
158
41
      break;
159
41
    }
160
41
  }
161
162
910
  curlx_free(buf_alloc);
163
164
910
  if(!result)
165
868
    result = Curl_xfer_send(data, "\r\n", 2, FALSE, &nwritten);
166
910
  if(result) {
167
42
    failf(data, "Failed sending Gopher request");
168
42
    return result;
169
42
  }
170
868
  result = Curl_client_write(data, CLIENTWRITE_HEADER, "\r\n", 2);
171
868
  if(result)
172
2
    return result;
173
174
866
  Curl_xfer_setup_recv(data, FIRSTSOCKET, -1);
175
866
  return CURLE_OK;
176
868
}
177
178
/*
179
 * Gopher protocol handler.
180
 * This is also a nice simple template to build off for simple
181
 * connect-command-download protocols.
182
 */
183
184
const struct Curl_protocol Curl_protocol_gopher = {
185
  ZERO_NULL,                            /* setup_connection */
186
  gopher_do,                            /* do_it */
187
  ZERO_NULL,                            /* done */
188
  ZERO_NULL,                            /* do_more */
189
  ZERO_NULL,                            /* connect_it */
190
  ZERO_NULL,                            /* connecting */
191
  ZERO_NULL,                            /* doing */
192
  ZERO_NULL,                            /* proto_pollset */
193
  ZERO_NULL,                            /* doing_pollset */
194
  ZERO_NULL,                            /* domore_pollset */
195
  ZERO_NULL,                            /* perform_pollset */
196
  ZERO_NULL,                            /* disconnect */
197
  ZERO_NULL,                            /* write_resp */
198
  ZERO_NULL,                            /* write_resp_hd */
199
  ZERO_NULL,                            /* connection_is_dead */
200
  ZERO_NULL,                            /* attach connection */
201
  ZERO_NULL,                            /* follow */
202
};
203
204
#ifdef USE_SSL
205
const struct Curl_protocol Curl_protocol_gophers = {
206
  ZERO_NULL,                            /* setup_connection */
207
  gopher_do,                            /* do_it */
208
  ZERO_NULL,                            /* done */
209
  ZERO_NULL,                            /* do_more */
210
  gopher_connect,                       /* connect_it */
211
  gopher_connecting,                    /* connecting */
212
  ZERO_NULL,                            /* doing */
213
  ZERO_NULL,                            /* proto_pollset */
214
  ZERO_NULL,                            /* doing_pollset */
215
  ZERO_NULL,                            /* domore_pollset */
216
  ZERO_NULL,                            /* perform_pollset */
217
  ZERO_NULL,                            /* disconnect */
218
  ZERO_NULL,                            /* write_resp */
219
  ZERO_NULL,                            /* write_resp_hd */
220
  ZERO_NULL,                            /* connection_is_dead */
221
  ZERO_NULL,                            /* attach connection */
222
  ZERO_NULL,                            /* follow */
223
};
224
#endif
225
226
#endif /* CURL_DISABLE_GOPHER */