Coverage Report

Created: 2025-08-29 06:10

/src/curl/lib/pingpong.c
Line
Count
Source (jump to first uncovered line)
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
28
#include "curl_setup.h"
29
30
#include "urldata.h"
31
#include "cfilters.h"
32
#include "connect.h"
33
#include "sendf.h"
34
#include "select.h"
35
#include "progress.h"
36
#include "speedcheck.h"
37
#include "pingpong.h"
38
#include "multiif.h"
39
#include "vtls/vtls.h"
40
#include "strdup.h"
41
42
/* The last 3 #include files should be in this order */
43
#include "curl_printf.h"
44
#include "curl_memory.h"
45
#include "memdebug.h"
46
47
#ifdef USE_PINGPONG
48
49
/* Returns timeout in ms. 0 or negative number means the timeout has already
50
   triggered */
51
timediff_t Curl_pp_state_timeout(struct Curl_easy *data,
52
                                 struct pingpong *pp, bool disconnecting)
53
7.36M
{
54
7.36M
  timediff_t timeout_ms; /* in milliseconds */
55
7.36M
  timediff_t response_time = (data->set.server_response_timeout > 0) ?
56
7.36M
    data->set.server_response_timeout : pp->response_time;
57
7.36M
  struct curltime now = curlx_now();
58
59
  /* if CURLOPT_SERVER_RESPONSE_TIMEOUT is set, use that to determine
60
     remaining time, or use pp->response because SERVER_RESPONSE_TIMEOUT is
61
     supposed to govern the response for any given server response, not for
62
     the time from connect to the given server response. */
63
64
  /* Without a requested timeout, we only wait 'response_time' seconds for the
65
     full response to arrive before we bail out */
66
7.36M
  timeout_ms = response_time - curlx_timediff(now, pp->response);
67
68
7.36M
  if((data->set.timeout > 0) && !disconnecting) {
69
    /* if timeout is requested, find out how much overall remains */
70
7.36M
    timediff_t timeout2_ms = Curl_timeleft(data, &now, FALSE);
71
    /* pick the lowest number */
72
7.36M
    timeout_ms = CURLMIN(timeout_ms, timeout2_ms);
73
7.36M
  }
74
75
7.36M
  if(disconnecting) {
76
3.86k
    timediff_t total_left_ms = Curl_timeleft(data, NULL, FALSE);
77
3.86k
    timeout_ms = CURLMIN(timeout_ms, CURLMAX(total_left_ms, 0));
78
3.86k
  }
79
80
7.36M
  return timeout_ms;
81
7.36M
}
82
83
/*
84
 * Curl_pp_statemach()
85
 */
86
CURLcode Curl_pp_statemach(struct Curl_easy *data,
87
                           struct pingpong *pp, bool block,
88
                           bool disconnecting)
89
7.36M
{
90
7.36M
  struct connectdata *conn = data->conn;
91
7.36M
  curl_socket_t sock = conn->sock[FIRSTSOCKET];
92
7.36M
  int rc;
93
7.36M
  timediff_t interval_ms;
94
7.36M
  timediff_t timeout_ms = Curl_pp_state_timeout(data, pp, disconnecting);
95
7.36M
  CURLcode result = CURLE_OK;
96
97
7.36M
  if(timeout_ms <= 0) {
98
232
    failf(data, "server response timeout");
99
232
    return CURLE_OPERATION_TIMEDOUT; /* already too little time */
100
232
  }
101
102
7.36M
  DEBUGF(infof(data, "pp_statematch, timeout=%" FMT_TIMEDIFF_T, timeout_ms));
103
7.36M
  if(block) {
104
7.28M
    interval_ms = 1000;  /* use 1 second timeout intervals */
105
7.28M
    if(timeout_ms < interval_ms)
106
7.28M
      interval_ms = timeout_ms;
107
7.28M
  }
108
79.2k
  else
109
79.2k
    interval_ms = 0; /* immediate */
110
111
7.36M
  if(Curl_conn_data_pending(data, FIRSTSOCKET))
112
0
    rc = 1;
113
7.36M
  else if(pp->overflow)
114
    /* We are receiving and there is data in the cache so just read it */
115
7.33M
    rc = 1;
116
25.9k
  else if(!pp->sendleft && Curl_conn_data_pending(data, FIRSTSOCKET))
117
    /* We are receiving and there is data ready in the SSL library */
118
0
    rc = 1;
119
25.9k
  else {
120
25.9k
    DEBUGF(infof(data, "pp_statematch, select, timeout=%" FMT_TIMEDIFF_T
121
25.9k
           ", sendleft=%zu",
122
25.9k
           timeout_ms, pp->sendleft));
123
25.9k
    rc = Curl_socket_check(pp->sendleft ? CURL_SOCKET_BAD : sock, /* reading */
124
25.9k
                           CURL_SOCKET_BAD,
125
25.9k
                           pp->sendleft ? sock : CURL_SOCKET_BAD, /* writing */
126
25.9k
                           interval_ms);
127
25.9k
  }
128
129
7.36M
  if(block) {
130
    /* if we did not wait, we do not have to spend time on this now */
131
7.28M
    if(Curl_pgrsUpdate(data))
132
0
      result = CURLE_ABORTED_BY_CALLBACK;
133
7.28M
    else
134
7.28M
      result = Curl_speedcheck(data, curlx_now());
135
136
7.28M
    if(result)
137
0
      return result;
138
7.28M
  }
139
140
7.36M
  if(rc == -1) {
141
0
    failf(data, "select/poll error");
142
0
    result = CURLE_OUT_OF_MEMORY;
143
0
  }
144
7.36M
  else if(rc)
145
7.36M
    result = pp->statemachine(data, data->conn);
146
1.64k
  else if(disconnecting)
147
108
    return CURLE_OPERATION_TIMEDOUT;
148
149
7.36M
  return result;
150
7.36M
}
151
152
/* initialize stuff to prepare for reading a fresh new response */
153
void Curl_pp_init(struct pingpong *pp)
154
14.2k
{
155
14.2k
  DEBUGASSERT(!pp->initialised);
156
14.2k
  pp->nread_resp = 0;
157
14.2k
  pp->response = curlx_now(); /* start response time-out now! */
158
14.2k
  pp->pending_resp = TRUE;
159
14.2k
  curlx_dyn_init(&pp->sendbuf, DYN_PINGPPONG_CMD);
160
14.2k
  curlx_dyn_init(&pp->recvbuf, DYN_PINGPPONG_CMD);
161
14.2k
  pp->initialised = TRUE;
162
14.2k
}
163
164
/***********************************************************************
165
 *
166
 * Curl_pp_vsendf()
167
 *
168
 * Send the formatted string as a command to a pingpong server. Note that
169
 * the string should not have any CRLF appended, as this function will
170
 * append the necessary things itself.
171
 *
172
 * made to never block
173
 */
174
CURLcode Curl_pp_vsendf(struct Curl_easy *data,
175
                        struct pingpong *pp,
176
                        const char *fmt,
177
                        va_list args)
178
25.7k
{
179
25.7k
  size_t bytes_written = 0;
180
25.7k
  size_t write_len;
181
25.7k
  char *s;
182
25.7k
  CURLcode result;
183
25.7k
  struct connectdata *conn = data->conn;
184
185
#ifdef HAVE_GSSAPI
186
  enum protection_level data_sec;
187
#endif
188
189
25.7k
  DEBUGASSERT(pp->sendleft == 0);
190
25.7k
  DEBUGASSERT(pp->sendsize == 0);
191
25.7k
  DEBUGASSERT(pp->sendthis == NULL);
192
193
25.7k
  if(!conn)
194
    /* cannot send without a connection! */
195
0
    return CURLE_SEND_ERROR;
196
197
25.7k
  curlx_dyn_reset(&pp->sendbuf);
198
25.7k
  result = curlx_dyn_vaddf(&pp->sendbuf, fmt, args);
199
25.7k
  if(result)
200
1
    return result;
201
202
  /* append CRLF */
203
25.7k
  result = curlx_dyn_addn(&pp->sendbuf, "\r\n", 2);
204
25.7k
  if(result)
205
0
    return result;
206
207
25.7k
  pp->pending_resp = TRUE;
208
25.7k
  write_len = curlx_dyn_len(&pp->sendbuf);
209
25.7k
  s = curlx_dyn_ptr(&pp->sendbuf);
210
211
#ifdef HAVE_GSSAPI
212
  conn->data_prot = PROT_CMD;
213
#endif
214
25.7k
  result = Curl_conn_send(data, FIRSTSOCKET, s, write_len, FALSE,
215
25.7k
                          &bytes_written);
216
25.7k
  if(result == CURLE_AGAIN) {
217
60
    bytes_written = 0;
218
60
  }
219
25.6k
  else if(result)
220
0
    return result;
221
#ifdef HAVE_GSSAPI
222
  data_sec = conn->data_prot;
223
  DEBUGASSERT(data_sec > PROT_NONE && data_sec < PROT_LAST);
224
  conn->data_prot = (unsigned char)data_sec;
225
#endif
226
227
25.7k
  Curl_debug(data, CURLINFO_HEADER_OUT, s, bytes_written);
228
229
25.7k
  if(bytes_written != write_len) {
230
    /* the whole chunk was not sent, keep it around and adjust sizes */
231
60
    pp->sendthis = s;
232
60
    pp->sendsize = write_len;
233
60
    pp->sendleft = write_len - bytes_written;
234
60
  }
235
25.6k
  else {
236
25.6k
    pp->sendthis = NULL;
237
25.6k
    pp->sendleft = pp->sendsize = 0;
238
25.6k
    pp->response = curlx_now();
239
25.6k
  }
240
241
25.7k
  return CURLE_OK;
242
25.7k
}
243
244
245
/***********************************************************************
246
 *
247
 * Curl_pp_sendf()
248
 *
249
 * Send the formatted string as a command to a pingpong server. Note that
250
 * the string should not have any CRLF appended, as this function will
251
 * append the necessary things itself.
252
 *
253
 * made to never block
254
 */
255
CURLcode Curl_pp_sendf(struct Curl_easy *data, struct pingpong *pp,
256
                       const char *fmt, ...)
257
20.0k
{
258
20.0k
  CURLcode result;
259
20.0k
  va_list ap;
260
20.0k
  va_start(ap, fmt);
261
262
20.0k
  result = Curl_pp_vsendf(data, pp, fmt, ap);
263
264
20.0k
  va_end(ap);
265
266
20.0k
  return result;
267
20.0k
}
268
269
static CURLcode pingpong_read(struct Curl_easy *data,
270
                              int sockindex,
271
                              char *buffer,
272
                              size_t buflen,
273
                              size_t *nread)
274
29.5k
{
275
29.5k
  CURLcode result;
276
#ifdef HAVE_GSSAPI
277
  enum protection_level prot = data->conn->data_prot;
278
  data->conn->data_prot = PROT_CLEAR;
279
#endif
280
29.5k
  result = Curl_conn_recv(data, sockindex, buffer, buflen, nread);
281
#ifdef HAVE_GSSAPI
282
  DEBUGASSERT(prot  > PROT_NONE && prot < PROT_LAST);
283
  data->conn->data_prot = (unsigned char)prot;
284
#endif
285
29.5k
  return result;
286
29.5k
}
287
288
/*
289
 * Curl_pp_readresp()
290
 *
291
 * Reads a piece of a server response.
292
 */
293
CURLcode Curl_pp_readresp(struct Curl_easy *data,
294
                          int sockindex,
295
                          struct pingpong *pp,
296
                          int *code, /* return the server code if done */
297
                          size_t *size) /* size of the response */
298
823k
{
299
823k
  struct connectdata *conn = data->conn;
300
823k
  CURLcode result = CURLE_OK;
301
823k
  size_t gotbytes;
302
823k
  char buffer[900];
303
304
823k
  *code = 0; /* 0 for errors or not done */
305
823k
  *size = 0;
306
307
830k
  do {
308
830k
    gotbytes = 0;
309
830k
    if(pp->nfinal) {
310
      /* a previous call left this many bytes in the beginning of the buffer as
311
         that was the final line; now ditch that */
312
803k
      size_t full = curlx_dyn_len(&pp->recvbuf);
313
314
      /* trim off the "final" leading part */
315
803k
      curlx_dyn_tail(&pp->recvbuf, full -  pp->nfinal);
316
317
803k
      pp->nfinal = 0; /* now gone */
318
803k
    }
319
830k
    if(!pp->overflow) {
320
29.5k
      result = pingpong_read(data, sockindex, buffer, sizeof(buffer),
321
29.5k
                             &gotbytes);
322
29.5k
      if(result == CURLE_AGAIN)
323
5
        return CURLE_OK;
324
325
29.5k
      if(result)
326
0
        return result;
327
328
29.5k
      if(!gotbytes) {
329
7.03k
        failf(data, "response reading failed (errno: %d)", SOCKERRNO);
330
7.03k
        return CURLE_RECV_ERROR;
331
7.03k
      }
332
333
22.4k
      result = curlx_dyn_addn(&pp->recvbuf, buffer, gotbytes);
334
22.4k
      if(result)
335
5
        return result;
336
337
22.4k
      data->req.headerbytecount += (unsigned int)gotbytes;
338
339
22.4k
      pp->nread_resp += gotbytes;
340
22.4k
    }
341
342
2.01M
    do {
343
2.01M
      char *line = curlx_dyn_ptr(&pp->recvbuf);
344
2.01M
      char *nl = memchr(line, '\n', curlx_dyn_len(&pp->recvbuf));
345
2.01M
      if(nl) {
346
        /* a newline is CRLF in pp-talk, so the CR is ignored as
347
           the line is not really terminated until the LF comes */
348
2.00M
        size_t length = nl - line + 1;
349
350
        /* output debug output if that is requested */
351
#ifdef HAVE_GSSAPI
352
        if(!conn->sec_complete)
353
#endif
354
2.00M
          Curl_debug(data, CURLINFO_HEADER_IN, line, length);
355
356
        /*
357
         * Pass all response-lines to the callback function registered for
358
         * "headers". The response lines can be seen as a kind of headers.
359
         */
360
2.00M
        result = Curl_client_write(data, CLIENTWRITE_INFO, line, length);
361
2.00M
        if(result)
362
0
          return result;
363
364
2.00M
        if(pp->endofresp(data, conn, line, length, code)) {
365
          /* When at "end of response", keep the endofresp line first in the
366
             buffer since it will be accessed outside (by pingpong
367
             parsers). Store the overflow counter to inform about additional
368
             data in this buffer after the endofresp line. */
369
805k
          pp->nfinal = length;
370
805k
          if(curlx_dyn_len(&pp->recvbuf) > length)
371
802k
            pp->overflow = curlx_dyn_len(&pp->recvbuf) - length;
372
3.23k
          else
373
3.23k
            pp->overflow = 0;
374
805k
          *size = pp->nread_resp; /* size of the response */
375
805k
          pp->nread_resp = 0; /* restart */
376
805k
          gotbytes = 0; /* force break out of outer loop */
377
805k
          break;
378
805k
        }
379
1.19M
        if(curlx_dyn_len(&pp->recvbuf) > length)
380
          /* keep the remaining piece */
381
1.19M
          curlx_dyn_tail((&pp->recvbuf), curlx_dyn_len(&pp->recvbuf) - length);
382
1.83k
        else
383
1.83k
          curlx_dyn_reset(&pp->recvbuf);
384
1.19M
      }
385
17.1k
      else {
386
        /* without a newline, there is no overflow */
387
17.1k
        pp->overflow = 0;
388
17.1k
        break;
389
17.1k
      }
390
391
2.01M
    } while(1); /* while there is buffer left to scan */
392
393
823k
  } while(gotbytes == sizeof(buffer));
394
395
816k
  pp->pending_resp = FALSE;
396
397
816k
  return result;
398
823k
}
399
400
CURLcode Curl_pp_pollset(struct Curl_easy *data,
401
                         struct pingpong *pp,
402
                         struct easy_pollset *ps)
403
56.6k
{
404
56.6k
  int flags = pp->sendleft ? CURL_POLL_OUT : CURL_POLL_IN;
405
56.6k
  return Curl_pollset_change(data, ps, data->conn->sock[FIRSTSOCKET],
406
56.6k
                             flags, 0);
407
56.6k
}
408
409
bool Curl_pp_needs_flush(struct Curl_easy *data,
410
                         struct pingpong *pp)
411
7.28M
{
412
7.28M
  (void)data;
413
7.28M
  return pp->sendleft > 0;
414
7.28M
}
415
416
CURLcode Curl_pp_flushsend(struct Curl_easy *data,
417
                           struct pingpong *pp)
418
7.27M
{
419
  /* we have a piece of a command still left to send */
420
7.27M
  size_t written;
421
7.27M
  CURLcode result;
422
423
7.27M
  if(!Curl_pp_needs_flush(data, pp))
424
0
    return CURLE_OK;
425
426
7.27M
  result = Curl_conn_send(data, FIRSTSOCKET,
427
7.27M
                          pp->sendthis + pp->sendsize - pp->sendleft,
428
7.27M
                          pp->sendleft, FALSE, &written);
429
7.27M
  if(result == CURLE_AGAIN) {
430
7.27M
    result = CURLE_OK;
431
7.27M
    written = 0;
432
7.27M
  }
433
7.27M
  if(result)
434
0
    return result;
435
436
7.27M
  if(written != pp->sendleft) {
437
    /* only a fraction was sent */
438
7.27M
    pp->sendleft -= written;
439
7.27M
  }
440
0
  else {
441
0
    pp->sendthis = NULL;
442
0
    pp->sendleft = pp->sendsize = 0;
443
0
    pp->response = curlx_now();
444
0
  }
445
7.27M
  return CURLE_OK;
446
7.27M
}
447
448
CURLcode Curl_pp_disconnect(struct pingpong *pp)
449
32.8k
{
450
32.8k
  if(pp->initialised) {
451
14.2k
    curlx_dyn_free(&pp->sendbuf);
452
14.2k
    curlx_dyn_free(&pp->recvbuf);
453
14.2k
    memset(pp, 0, sizeof(*pp));
454
14.2k
  }
455
32.8k
  return CURLE_OK;
456
32.8k
}
457
458
bool Curl_pp_moredata(struct pingpong *pp)
459
738k
{
460
738k
  return !pp->sendleft && curlx_dyn_len(&pp->recvbuf) > pp->nfinal;
461
738k
}
462
463
#endif