Coverage Report

Created: 2025-12-04 07:04

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
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 "vtls/vtls.h"
39
#include "url.h"
40
#include "escape.h"
41
#include "curlx/warnless.h"
42
43
/*
44
 * Forward declarations.
45
 */
46
47
static CURLcode gopher_do(struct Curl_easy *data, bool *done);
48
#ifdef USE_SSL
49
static CURLcode gopher_connect(struct Curl_easy *data, bool *done);
50
static CURLcode gopher_connecting(struct Curl_easy *data, bool *done);
51
#endif
52
53
/*
54
 * Gopher protocol handler.
55
 * This is also a nice simple template to build off for simple
56
 * connect-command-download protocols.
57
 */
58
59
const struct Curl_handler Curl_handler_gopher = {
60
  "gopher",                             /* scheme */
61
  ZERO_NULL,                            /* setup_connection */
62
  gopher_do,                            /* do_it */
63
  ZERO_NULL,                            /* done */
64
  ZERO_NULL,                            /* do_more */
65
  ZERO_NULL,                            /* connect_it */
66
  ZERO_NULL,                            /* connecting */
67
  ZERO_NULL,                            /* doing */
68
  ZERO_NULL,                            /* proto_pollset */
69
  ZERO_NULL,                            /* doing_pollset */
70
  ZERO_NULL,                            /* domore_pollset */
71
  ZERO_NULL,                            /* perform_pollset */
72
  ZERO_NULL,                            /* disconnect */
73
  ZERO_NULL,                            /* write_resp */
74
  ZERO_NULL,                            /* write_resp_hd */
75
  ZERO_NULL,                            /* connection_check */
76
  ZERO_NULL,                            /* attach connection */
77
  ZERO_NULL,                            /* follow */
78
  PORT_GOPHER,                          /* defport */
79
  CURLPROTO_GOPHER,                     /* protocol */
80
  CURLPROTO_GOPHER,                     /* family */
81
  PROTOPT_NONE                          /* flags */
82
};
83
84
#ifdef USE_SSL
85
const struct Curl_handler Curl_handler_gophers = {
86
  "gophers",                            /* scheme */
87
  ZERO_NULL,                            /* setup_connection */
88
  gopher_do,                            /* do_it */
89
  ZERO_NULL,                            /* done */
90
  ZERO_NULL,                            /* do_more */
91
  gopher_connect,                       /* connect_it */
92
  gopher_connecting,                    /* connecting */
93
  ZERO_NULL,                            /* doing */
94
  ZERO_NULL,                            /* proto_pollset */
95
  ZERO_NULL,                            /* doing_pollset */
96
  ZERO_NULL,                            /* domore_pollset */
97
  ZERO_NULL,                            /* perform_pollset */
98
  ZERO_NULL,                            /* disconnect */
99
  ZERO_NULL,                            /* write_resp */
100
  ZERO_NULL,                            /* write_resp_hd */
101
  ZERO_NULL,                            /* connection_check */
102
  ZERO_NULL,                            /* attach connection */
103
  ZERO_NULL,                            /* follow */
104
  PORT_GOPHER,                          /* defport */
105
  CURLPROTO_GOPHERS,                    /* protocol */
106
  CURLPROTO_GOPHER,                     /* family */
107
  PROTOPT_SSL                           /* flags */
108
};
109
110
static CURLcode gopher_connect(struct Curl_easy *data, bool *done)
111
0
{
112
0
  (void)data;
113
0
  (void)done;
114
0
  return CURLE_OK;
115
0
}
116
117
static CURLcode gopher_connecting(struct Curl_easy *data, bool *done)
118
0
{
119
0
  struct connectdata *conn = data->conn;
120
0
  CURLcode result;
121
122
0
  result = Curl_conn_connect(data, FIRSTSOCKET, TRUE, done);
123
0
  if(result)
124
0
    connclose(conn, "Failed TLS connection");
125
0
  *done = TRUE;
126
0
  return result;
127
0
}
128
#endif
129
130
static CURLcode gopher_do(struct Curl_easy *data, bool *done)
131
822
{
132
822
  CURLcode result = CURLE_OK;
133
822
  struct connectdata *conn = data->conn;
134
822
  curl_socket_t sockfd = conn->sock[FIRSTSOCKET];
135
822
  char *gopherpath;
136
822
  char *path = data->state.up.path;
137
822
  char *query = data->state.up.query;
138
822
  const char *buf = NULL;
139
822
  char *buf_alloc = NULL;
140
822
  size_t nwritten, buf_len;
141
822
  timediff_t timeout_ms;
142
822
  int what;
143
144
822
  *done = TRUE; /* unconditionally */
145
146
  /* path is guaranteed non-NULL */
147
822
  DEBUGASSERT(path);
148
149
822
  if(query)
150
75
    gopherpath = curl_maprintf("%s?%s", path, query);
151
747
  else
152
747
    gopherpath = curlx_strdup(path);
153
154
822
  if(!gopherpath)
155
0
    return CURLE_OUT_OF_MEMORY;
156
157
  /* Create selector. Degenerate cases: / and /1 => convert to "" */
158
822
  if(strlen(gopherpath) <= 2) {
159
678
    buf = "";
160
678
    buf_len = 0;
161
678
    curlx_free(gopherpath);
162
678
  }
163
144
  else {
164
144
    char *newp;
165
166
    /* Otherwise, drop / and the first character (i.e., item type) ... */
167
144
    newp = gopherpath;
168
144
    newp += 2;
169
170
    /* ... and finally unescape */
171
144
    result = Curl_urldecode(newp, 0, &buf_alloc, &buf_len, REJECT_ZERO);
172
144
    curlx_free(gopherpath);
173
144
    if(result)
174
1
      return result;
175
143
    buf = buf_alloc;
176
143
  }
177
178
821
  for(; buf_len;) {
179
180
143
    result = Curl_xfer_send(data, buf, buf_len, FALSE, &nwritten);
181
143
    if(!result) { /* Which may not have written it all! */
182
143
      result = Curl_client_write(data, CLIENTWRITE_HEADER, buf, nwritten);
183
143
      if(result)
184
0
        break;
185
186
143
      if(nwritten > buf_len) {
187
0
        DEBUGASSERT(0);
188
0
        break;
189
0
      }
190
143
      buf_len -= nwritten;
191
143
      buf += nwritten;
192
143
      if(!buf_len)
193
121
        break; /* but it did write it all */
194
143
    }
195
0
    else
196
0
      break;
197
198
22
    timeout_ms = Curl_timeleft_ms(data, NULL, FALSE);
199
22
    if(timeout_ms < 0) {
200
0
      result = CURLE_OPERATION_TIMEDOUT;
201
0
      break;
202
0
    }
203
22
    if(!timeout_ms)
204
0
      timeout_ms = TIMEDIFF_T_MAX;
205
206
    /* Do not busyloop. The entire loop thing is a work-around as it causes a
207
       BLOCKING behavior which is a NO-NO. This function should rather be
208
       split up in a do and a doing piece where the pieces that are not
209
       possible to send now will be sent in the doing function repeatedly
210
       until the entire request is sent.
211
    */
212
22
    what = SOCKET_WRITABLE(sockfd, timeout_ms);
213
22
    if(what < 0) {
214
0
      result = CURLE_SEND_ERROR;
215
0
      break;
216
0
    }
217
22
    else if(!what) {
218
22
      result = CURLE_OPERATION_TIMEDOUT;
219
22
      break;
220
22
    }
221
22
  }
222
223
821
  curlx_free(buf_alloc);
224
225
821
  if(!result)
226
799
    result = Curl_xfer_send(data, "\r\n", 2, FALSE, &nwritten);
227
821
  if(result) {
228
22
    failf(data, "Failed sending Gopher request");
229
22
    return result;
230
22
  }
231
799
  result = Curl_client_write(data, CLIENTWRITE_HEADER, "\r\n", 2);
232
799
  if(result)
233
0
    return result;
234
235
799
  Curl_xfer_setup_recv(data, FIRSTSOCKET, -1);
236
799
  return CURLE_OK;
237
799
}
238
#endif /* CURL_DISABLE_GOPHER */