Coverage Report

Created: 2026-09-14 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/pingpong.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
 *   'pingpong' is for generic back-and-forth support functions used by FTP,
24
 *   IMAP, POP3, SMTP and whatever more that likes them.
25
 *
26
 ***************************************************************************/
27
#include "curl_setup.h"
28
29
#include "urldata.h"
30
#include "pingpong.h"
31
32
#ifdef USE_PINGPONG
33
34
#include "cfilters.h"
35
#include "connect.h"
36
#include "multiif.h"
37
#include "sendf.h"
38
#include "curl_trc.h"
39
#include "select.h"
40
#include "progress.h"
41
42
timediff_t Curl_pp_state_timeleft_ms(struct Curl_easy *data,
43
                                     struct pingpong *pp)
44
8.07M
{
45
8.07M
  timediff_t xfer_remain_ms;
46
8.07M
  timediff_t remain_ms = data->set.server_response_timeout ?
47
7.96M
    data->set.server_response_timeout : PINGPONG_TIMEOUT_MS;
48
49
  /* If the overall transfer has less time remaining than pingpong
50
   * has otherwise for the state, return that. */
51
8.07M
  remain_ms -= curlx_ptimediff_ms(Curl_pgrs_now(data), &pp->response);
52
  /* transfer remaining time is 0, when it has no timeout. */
53
8.07M
  xfer_remain_ms = Curl_timeleft_ms(data);
54
8.07M
  if(xfer_remain_ms)
55
8.07M
    return CURLMIN(remain_ms, xfer_remain_ms);
56
743
  return remain_ms;
57
8.07M
}
58
59
/*
60
 * Curl_pp_statemach()
61
 */
62
CURLcode Curl_pp_statemach(struct Curl_easy *data,
63
                           struct pingpong *pp, bool block,
64
                           bool disconnecting)
65
8.07M
{
66
8.07M
  struct connectdata *conn = data->conn;
67
8.07M
  curl_socket_t sock = conn->sock[FIRSTSOCKET];
68
8.07M
  int rc;
69
8.07M
  timediff_t interval_ms;
70
8.07M
  timediff_t timeout_ms = Curl_pp_state_timeleft_ms(data, pp);
71
8.07M
  CURLcode result = CURLE_OK;
72
73
8.07M
  if(timeout_ms <= 0) {
74
351
    failf(data, "server response timeout");
75
351
    return CURLE_OPERATION_TIMEDOUT; /* already too little time */
76
351
  }
77
78
8.07M
  if(block) {
79
7.92M
    interval_ms = 1000;  /* use 1 second timeout intervals */
80
7.92M
    if(timeout_ms < interval_ms)
81
7.91M
      interval_ms = timeout_ms;
82
7.92M
  }
83
148k
  else
84
148k
    interval_ms = 0; /* immediate */
85
86
8.07M
  if(Curl_conn_data_pending(data, FIRSTSOCKET))
87
0
    rc = 1;
88
8.07M
  else if(pp->overflow)
89
    /* We are receiving and there is data in the cache so read it */
90
7.94M
    rc = 1;
91
130k
  else if(!pp->sendleft && Curl_conn_data_pending(data, FIRSTSOCKET))
92
    /* We are receiving and there is data ready in the SSL library */
93
0
    rc = 1;
94
130k
  else {
95
130k
    rc = Curl_socket_check(pp->sendleft ? CURL_SOCKET_BAD : sock, /* reading */
96
130k
                           CURL_SOCKET_BAD,
97
130k
                           pp->sendleft ? sock : CURL_SOCKET_BAD, /* writing */
98
130k
                           interval_ms);
99
130k
  }
100
101
8.07M
  if(block) {
102
    /* if we did not wait, we do not have to spend time on this now */
103
7.92M
    result = Curl_pgrsCheck(data);
104
7.92M
    if(result)
105
0
      return result;
106
7.92M
  }
107
108
8.07M
  if(rc == -1) {
109
0
    failf(data, "select/poll error");
110
0
    result = CURLE_OUT_OF_MEMORY;
111
0
  }
112
8.07M
  else if(rc)
113
8.00M
    result = pp->statemachine(data, data->conn);
114
69.6k
  else if(disconnecting)
115
290
    return CURLE_OPERATION_TIMEDOUT;
116
117
8.07M
  return result;
118
8.07M
}
119
120
/* initialize stuff to prepare for reading a fresh new response */
121
void Curl_pp_init(struct pingpong *pp, const struct curltime *pnow)
122
27.3k
{
123
27.3k
  DEBUGASSERT(!pp->initialized);
124
27.3k
  pp->nread_resp = 0;
125
27.3k
  pp->response = *pnow; /* start response time-out */
126
27.3k
  pp->pending_resp = TRUE;
127
27.3k
  curlx_dyn_init(&pp->sendbuf, DYN_PINGPPONG_CMD);
128
27.3k
  curlx_dyn_init(&pp->recvbuf, DYN_PINGPPONG_CMD);
129
27.3k
  pp->initialized = TRUE;
130
27.3k
}
131
132
/***********************************************************************
133
 *
134
 * Curl_pp_vsendf()
135
 *
136
 * Send the formatted string as a command to a pingpong server. Note that
137
 * the string should not have any CRLF appended, as this function will
138
 * append the necessary things itself.
139
 *
140
 * made to never block
141
 */
142
CURLcode Curl_pp_vsendf(struct Curl_easy *data,
143
                        struct pingpong *pp,
144
                        const char *fmt,
145
                        va_list args)
146
75.4k
{
147
75.4k
  size_t bytes_written = 0;
148
75.4k
  size_t write_len;
149
75.4k
  char *s;
150
75.4k
  CURLcode result;
151
75.4k
  struct connectdata *conn = data->conn;
152
153
75.4k
  DEBUGASSERT(pp->sendleft == 0);
154
75.4k
  DEBUGASSERT(pp->sendsize == 0);
155
75.4k
  DEBUGASSERT(!pp->sendthis);
156
157
75.4k
  if(!conn)
158
    /* cannot send without a connection! */
159
0
    return CURLE_SEND_ERROR;
160
161
75.4k
  curlx_dyn_reset(&pp->sendbuf);
162
75.4k
  result = curlx_dyn_vaddf(&pp->sendbuf, fmt, args);
163
75.4k
  if(result)
164
25
    return result;
165
166
  /* append CRLF */
167
75.4k
  result = curlx_dyn_addn(&pp->sendbuf, "\r\n", 2);
168
75.4k
  if(result)
169
0
    return result;
170
171
75.4k
  pp->pending_resp = TRUE;
172
75.4k
  write_len = curlx_dyn_len(&pp->sendbuf);
173
75.4k
  s = curlx_dyn_ptr(&pp->sendbuf);
174
175
75.4k
  result = Curl_conn_send(data, FIRSTSOCKET, s, write_len, FALSE,
176
75.4k
                          &bytes_written);
177
75.4k
  if(result == CURLE_AGAIN) {
178
96
    bytes_written = 0;
179
96
  }
180
75.3k
  else if(result)
181
0
    return result;
182
183
75.4k
  Curl_debug(data, CURLINFO_HEADER_OUT, s, bytes_written);
184
185
75.4k
  if(bytes_written != write_len) {
186
    /* the whole chunk was not sent, keep it around and adjust sizes */
187
107
    pp->sendthis = s;
188
107
    pp->sendsize = write_len;
189
107
    pp->sendleft = write_len - bytes_written;
190
107
  }
191
75.3k
  else {
192
75.3k
    pp->sendthis = NULL;
193
75.3k
    pp->sendleft = pp->sendsize = 0;
194
75.3k
    pp->response = *Curl_pgrs_now(data);
195
75.3k
  }
196
197
75.4k
  return CURLE_OK;
198
75.4k
}
199
200
/***********************************************************************
201
 *
202
 * Curl_pp_sendf()
203
 *
204
 * Send the formatted string as a command to a pingpong server. Note that
205
 * the string should not have any CRLF appended, as this function will
206
 * append the necessary things itself.
207
 *
208
 * made to never block
209
 */
210
CURLcode Curl_pp_sendf(struct Curl_easy *data, struct pingpong *pp,
211
                       const char *fmt, ...)
212
67.2k
{
213
67.2k
  CURLcode result;
214
67.2k
  va_list ap;
215
67.2k
  va_start(ap, fmt);
216
217
67.2k
  result = Curl_pp_vsendf(data, pp, fmt, ap);
218
219
67.2k
  va_end(ap);
220
221
67.2k
  return result;
222
67.2k
}
223
224
static CURLcode pingpong_read(struct Curl_easy *data,
225
                              int8_t sockindex,
226
                              char *buffer,
227
                              size_t buflen,
228
                              size_t *nread)
229
58.6k
{
230
58.6k
  return Curl_conn_recv(data, sockindex, buffer, buflen, nread);
231
58.6k
}
232
233
/*
234
 * Curl_pp_readresp()
235
 *
236
 * Reads a piece of a server response.
237
 */
238
CURLcode Curl_pp_readresp(struct Curl_easy *data,
239
                          int8_t sockindex,
240
                          struct pingpong *pp,
241
                          int *code, /* return the server code if done */
242
                          size_t *size) /* size of the response */
243
127k
{
244
127k
  struct connectdata *conn = data->conn;
245
127k
  CURLcode result = CURLE_OK;
246
127k
  size_t gotbytes;
247
127k
  char buffer[900];
248
249
127k
  *code = 0; /* 0 for errors or not done */
250
127k
  *size = 0;
251
252
129k
  do {
253
129k
    gotbytes = 0;
254
129k
    if(pp->nfinal) {
255
      /* a previous call left this many bytes in the beginning of the buffer as
256
         that was the final line; now ditch that */
257
101k
      size_t full = curlx_dyn_len(&pp->recvbuf);
258
259
      /* trim off the "final" leading part */
260
101k
      curlx_dyn_tail(&pp->recvbuf, full - pp->nfinal);
261
262
101k
      pp->nfinal = 0; /* now gone */
263
101k
    }
264
129k
    if(!pp->overflow) {
265
58.6k
      result = pingpong_read(data, sockindex, buffer, sizeof(buffer),
266
58.6k
                             &gotbytes);
267
58.6k
      if(result == CURLE_AGAIN)
268
238
        return CURLE_OK;
269
270
58.3k
      if(result)
271
0
        return result;
272
273
58.3k
      if(!gotbytes) {
274
8.11k
        failf(data, "response reading failed (errno: %d)", SOCKERRNO);
275
8.11k
        return CURLE_RECV_ERROR;
276
8.11k
      }
277
278
50.2k
      result = curlx_dyn_addn(&pp->recvbuf, buffer, gotbytes);
279
50.2k
      if(result)
280
10
        return result;
281
282
50.2k
      data->req.headerbytecount += (unsigned int)gotbytes;
283
284
50.2k
      pp->nread_resp += gotbytes;
285
50.2k
    }
286
287
552k
    do {
288
552k
      const char *line = curlx_dyn_ptr(&pp->recvbuf);
289
552k
      const char *nl = memchr(line, '\n', curlx_dyn_len(&pp->recvbuf));
290
552k
      if(nl) {
291
        /* a newline is CRLF in pp-talk, so the CR is ignored as
292
           the line is not really terminated until the LF comes */
293
542k
        size_t length = nl - line + 1;
294
295
542k
        if(memchr(line, 0, length)) {
296
          /* The response line is passed on as a "header" below, so reject an
297
             embedded nul the same way verify_header() does for HTTP. */
298
412
          failf(data, "Nul byte in server response line");
299
412
          return CURLE_WEIRD_SERVER_REPLY;
300
412
        }
301
302
        /* output debug output if that is requested */
303
542k
        Curl_debug(data, CURLINFO_HEADER_IN, line, length);
304
305
        /*
306
         * Pass all response-lines to the callback function registered for
307
         * "headers". The response lines can be seen as a kind of headers.
308
         */
309
542k
        result = Curl_client_write(data, CLIENTWRITE_INFO, line, length);
310
542k
        if(result)
311
0
          return result;
312
313
542k
        if(pp->endofresp(data, conn, line, length, code)) {
314
          /* When at "end of response", keep the endofresp line first in the
315
             buffer since it will be accessed outside (by pingpong
316
             parsers). Store the overflow counter to inform about additional
317
             data in this buffer after the endofresp line. */
318
111k
          pp->nfinal = length;
319
111k
          if(curlx_dyn_len(&pp->recvbuf) > length)
320
74.3k
            pp->overflow = curlx_dyn_len(&pp->recvbuf) - length;
321
36.9k
          else
322
36.9k
            pp->overflow = 0;
323
111k
          *size = pp->nread_resp; /* size of the response */
324
111k
          pp->nread_resp = 0; /* restart */
325
111k
          gotbytes = 0; /* force break out of outer loop */
326
111k
          break;
327
111k
        }
328
430k
        if(curlx_dyn_len(&pp->recvbuf) > length)
329
          /* keep the remaining piece */
330
428k
          curlx_dyn_tail((&pp->recvbuf), curlx_dyn_len(&pp->recvbuf) - length);
331
2.34k
        else
332
2.34k
          curlx_dyn_reset(&pp->recvbuf);
333
430k
      }
334
9.69k
      else {
335
        /* without a newline, there is no overflow */
336
9.69k
        pp->overflow = 0;
337
9.69k
        break;
338
9.69k
      }
339
340
552k
    } while(1); /* while there is buffer left to scan */
341
342
121k
  } while(gotbytes == sizeof(buffer));
343
344
118k
  pp->pending_resp = FALSE;
345
346
118k
  return result;
347
127k
}
348
349
CURLcode Curl_pp_pollset(struct Curl_easy *data,
350
                         struct pingpong *pp,
351
                         struct easy_pollset *ps)
352
10.5k
{
353
10.5k
  int flags = pp->sendleft ? CURL_POLL_OUT : CURL_POLL_IN;
354
10.5k
  return Curl_pollset_change(data, ps, data->conn->sock[FIRSTSOCKET],
355
10.5k
                             flags, 0);
356
10.5k
}
357
358
bool Curl_pp_needs_flush(struct Curl_easy *data,
359
                         struct pingpong *pp)
360
7.92M
{
361
7.92M
  (void)data;
362
7.92M
  return pp->sendleft > 0;
363
7.92M
}
364
365
CURLcode Curl_pp_flushsend(struct Curl_easy *data,
366
                           struct pingpong *pp)
367
7.90M
{
368
  /* we have a piece of a command still left to send */
369
7.90M
  size_t written;
370
7.90M
  CURLcode result;
371
372
7.90M
  if(!Curl_pp_needs_flush(data, pp))
373
0
    return CURLE_OK;
374
375
7.90M
  result = Curl_conn_send(data, FIRSTSOCKET,
376
7.90M
                          pp->sendthis + pp->sendsize - pp->sendleft,
377
7.90M
                          pp->sendleft, FALSE, &written);
378
7.90M
  if(result == CURLE_AGAIN) {
379
7.90M
    result = CURLE_OK;
380
7.90M
    written = 0;
381
7.90M
  }
382
7.90M
  if(result)
383
0
    return result;
384
385
7.90M
  if(written != pp->sendleft) {
386
    /* only a fraction was sent */
387
7.90M
    pp->sendleft -= written;
388
7.90M
  }
389
7
  else {
390
7
    pp->sendthis = NULL;
391
7
    pp->sendleft = pp->sendsize = 0;
392
7
    pp->response = *Curl_pgrs_now(data);
393
7
  }
394
7.90M
  return CURLE_OK;
395
7.90M
}
396
397
CURLcode Curl_pp_disconnect(struct pingpong *pp)
398
51.5k
{
399
51.5k
  if(pp->initialized) {
400
27.3k
    curlx_dyn_free(&pp->sendbuf);
401
27.3k
    curlx_dyn_free(&pp->recvbuf);
402
27.3k
    memset(pp, 0, sizeof(*pp));
403
27.3k
  }
404
51.5k
  return CURLE_OK;
405
51.5k
}
406
407
bool Curl_pp_moredata(struct pingpong *pp)
408
26.3k
{
409
26.3k
  return !pp->sendleft && curlx_dyn_len(&pp->recvbuf) > pp->nfinal;
410
26.3k
}
411
412
#endif