quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

pingpong.c (13580B)


      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 {
     54   timediff_t timeout_ms; /* in milliseconds */
     55   timediff_t response_time = (data->set.server_response_timeout > 0) ?
     56     data->set.server_response_timeout : pp->response_time;
     57   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   timeout_ms = response_time - curlx_timediff(now, pp->response);
     67 
     68   if((data->set.timeout > 0) && !disconnecting) {
     69     /* if timeout is requested, find out how much overall remains */
     70     timediff_t timeout2_ms = Curl_timeleft(data, &now, FALSE);
     71     /* pick the lowest number */
     72     timeout_ms = CURLMIN(timeout_ms, timeout2_ms);
     73   }
     74 
     75   if(disconnecting) {
     76     timediff_t total_left_ms = Curl_timeleft(data, NULL, FALSE);
     77     timeout_ms = CURLMIN(timeout_ms, CURLMAX(total_left_ms, 0));
     78   }
     79 
     80   return timeout_ms;
     81 }
     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 {
     90   struct connectdata *conn = data->conn;
     91   curl_socket_t sock = conn->sock[FIRSTSOCKET];
     92   int rc;
     93   timediff_t interval_ms;
     94   timediff_t timeout_ms = Curl_pp_state_timeout(data, pp, disconnecting);
     95   CURLcode result = CURLE_OK;
     96 
     97   if(timeout_ms <= 0) {
     98     failf(data, "server response timeout");
     99     return CURLE_OPERATION_TIMEDOUT; /* already too little time */
    100   }
    101 
    102   DEBUGF(infof(data, "pp_statematch, timeout=%" FMT_TIMEDIFF_T, timeout_ms));
    103   if(block) {
    104     interval_ms = 1000;  /* use 1 second timeout intervals */
    105     if(timeout_ms < interval_ms)
    106       interval_ms = timeout_ms;
    107   }
    108   else
    109     interval_ms = 0; /* immediate */
    110 
    111   if(Curl_conn_data_pending(data, FIRSTSOCKET))
    112     rc = 1;
    113   else if(pp->overflow)
    114     /* We are receiving and there is data in the cache so just read it */
    115     rc = 1;
    116   else if(!pp->sendleft && Curl_conn_data_pending(data, FIRSTSOCKET))
    117     /* We are receiving and there is data ready in the SSL library */
    118     rc = 1;
    119   else {
    120     DEBUGF(infof(data, "pp_statematch, select, timeout=%" FMT_TIMEDIFF_T
    121            ", sendleft=%zu",
    122            timeout_ms, pp->sendleft));
    123     rc = Curl_socket_check(pp->sendleft ? CURL_SOCKET_BAD : sock, /* reading */
    124                            CURL_SOCKET_BAD,
    125                            pp->sendleft ? sock : CURL_SOCKET_BAD, /* writing */
    126                            interval_ms);
    127   }
    128 
    129   if(block) {
    130     /* if we did not wait, we do not have to spend time on this now */
    131     if(Curl_pgrsUpdate(data))
    132       result = CURLE_ABORTED_BY_CALLBACK;
    133     else
    134       result = Curl_speedcheck(data, curlx_now());
    135 
    136     if(result)
    137       return result;
    138   }
    139 
    140   if(rc == -1) {
    141     failf(data, "select/poll error");
    142     result = CURLE_OUT_OF_MEMORY;
    143   }
    144   else if(rc)
    145     result = pp->statemachine(data, data->conn);
    146   else if(disconnecting)
    147     return CURLE_OPERATION_TIMEDOUT;
    148 
    149   return result;
    150 }
    151 
    152 /* initialize stuff to prepare for reading a fresh new response */
    153 void Curl_pp_init(struct pingpong *pp)
    154 {
    155   DEBUGASSERT(!pp->initialised);
    156   pp->nread_resp = 0;
    157   pp->response = curlx_now(); /* start response time-out now! */
    158   pp->pending_resp = TRUE;
    159   curlx_dyn_init(&pp->sendbuf, DYN_PINGPPONG_CMD);
    160   curlx_dyn_init(&pp->recvbuf, DYN_PINGPPONG_CMD);
    161   pp->initialised = TRUE;
    162 }
    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 {
    179   size_t bytes_written = 0;
    180   size_t write_len;
    181   char *s;
    182   CURLcode result;
    183   struct connectdata *conn = data->conn;
    184 
    185 #ifdef HAVE_GSSAPI
    186   enum protection_level data_sec;
    187 #endif
    188 
    189   DEBUGASSERT(pp->sendleft == 0);
    190   DEBUGASSERT(pp->sendsize == 0);
    191   DEBUGASSERT(pp->sendthis == NULL);
    192 
    193   if(!conn)
    194     /* cannot send without a connection! */
    195     return CURLE_SEND_ERROR;
    196 
    197   curlx_dyn_reset(&pp->sendbuf);
    198   result = curlx_dyn_vaddf(&pp->sendbuf, fmt, args);
    199   if(result)
    200     return result;
    201 
    202   /* append CRLF */
    203   result = curlx_dyn_addn(&pp->sendbuf, "\r\n", 2);
    204   if(result)
    205     return result;
    206 
    207   pp->pending_resp = TRUE;
    208   write_len = curlx_dyn_len(&pp->sendbuf);
    209   s = curlx_dyn_ptr(&pp->sendbuf);
    210 
    211 #ifdef HAVE_GSSAPI
    212   conn->data_prot = PROT_CMD;
    213 #endif
    214   result = Curl_conn_send(data, FIRSTSOCKET, s, write_len, FALSE,
    215                           &bytes_written);
    216   if(result == CURLE_AGAIN) {
    217     bytes_written = 0;
    218   }
    219   else if(result)
    220     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   Curl_debug(data, CURLINFO_HEADER_OUT, s, bytes_written);
    228 
    229   if(bytes_written != write_len) {
    230     /* the whole chunk was not sent, keep it around and adjust sizes */
    231     pp->sendthis = s;
    232     pp->sendsize = write_len;
    233     pp->sendleft = write_len - bytes_written;
    234   }
    235   else {
    236     pp->sendthis = NULL;
    237     pp->sendleft = pp->sendsize = 0;
    238     pp->response = curlx_now();
    239   }
    240 
    241   return CURLE_OK;
    242 }
    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 {
    258   CURLcode result;
    259   va_list ap;
    260   va_start(ap, fmt);
    261 
    262   result = Curl_pp_vsendf(data, pp, fmt, ap);
    263 
    264   va_end(ap);
    265 
    266   return result;
    267 }
    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 {
    275   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   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   return result;
    286 }
    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 {
    299   struct connectdata *conn = data->conn;
    300   CURLcode result = CURLE_OK;
    301   size_t gotbytes;
    302   char buffer[900];
    303 
    304   *code = 0; /* 0 for errors or not done */
    305   *size = 0;
    306 
    307   do {
    308     gotbytes = 0;
    309     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       size_t full = curlx_dyn_len(&pp->recvbuf);
    313 
    314       /* trim off the "final" leading part */
    315       curlx_dyn_tail(&pp->recvbuf, full -  pp->nfinal);
    316 
    317       pp->nfinal = 0; /* now gone */
    318     }
    319     if(!pp->overflow) {
    320       result = pingpong_read(data, sockindex, buffer, sizeof(buffer),
    321                              &gotbytes);
    322       if(result == CURLE_AGAIN)
    323         return CURLE_OK;
    324 
    325       if(result)
    326         return result;
    327 
    328       if(!gotbytes) {
    329         failf(data, "response reading failed (errno: %d)", SOCKERRNO);
    330         return CURLE_RECV_ERROR;
    331       }
    332 
    333       result = curlx_dyn_addn(&pp->recvbuf, buffer, gotbytes);
    334       if(result)
    335         return result;
    336 
    337       data->req.headerbytecount += (unsigned int)gotbytes;
    338 
    339       pp->nread_resp += gotbytes;
    340     }
    341 
    342     do {
    343       char *line = curlx_dyn_ptr(&pp->recvbuf);
    344       char *nl = memchr(line, '\n', curlx_dyn_len(&pp->recvbuf));
    345       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         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           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         result = Curl_client_write(data, CLIENTWRITE_INFO, line, length);
    361         if(result)
    362           return result;
    363 
    364         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           pp->nfinal = length;
    370           if(curlx_dyn_len(&pp->recvbuf) > length)
    371             pp->overflow = curlx_dyn_len(&pp->recvbuf) - length;
    372           else
    373             pp->overflow = 0;
    374           *size = pp->nread_resp; /* size of the response */
    375           pp->nread_resp = 0; /* restart */
    376           gotbytes = 0; /* force break out of outer loop */
    377           break;
    378         }
    379         if(curlx_dyn_len(&pp->recvbuf) > length)
    380           /* keep the remaining piece */
    381           curlx_dyn_tail((&pp->recvbuf), curlx_dyn_len(&pp->recvbuf) - length);
    382         else
    383           curlx_dyn_reset(&pp->recvbuf);
    384       }
    385       else {
    386         /* without a newline, there is no overflow */
    387         pp->overflow = 0;
    388         break;
    389       }
    390 
    391     } while(1); /* while there is buffer left to scan */
    392 
    393   } while(gotbytes == sizeof(buffer));
    394 
    395   pp->pending_resp = FALSE;
    396 
    397   return result;
    398 }
    399 
    400 int Curl_pp_getsock(struct Curl_easy *data,
    401                     struct pingpong *pp, curl_socket_t *socks)
    402 {
    403   struct connectdata *conn = data->conn;
    404   socks[0] = conn->sock[FIRSTSOCKET];
    405 
    406   if(pp->sendleft) {
    407     /* write mode */
    408     return GETSOCK_WRITESOCK(0);
    409   }
    410 
    411   /* read mode */
    412   return GETSOCK_READSOCK(0);
    413 }
    414 
    415 bool Curl_pp_needs_flush(struct Curl_easy *data,
    416                          struct pingpong *pp)
    417 {
    418   (void)data;
    419   return pp->sendleft > 0;
    420 }
    421 
    422 CURLcode Curl_pp_flushsend(struct Curl_easy *data,
    423                            struct pingpong *pp)
    424 {
    425   /* we have a piece of a command still left to send */
    426   size_t written;
    427   CURLcode result;
    428 
    429   if(!Curl_pp_needs_flush(data, pp))
    430     return CURLE_OK;
    431 
    432   result = Curl_conn_send(data, FIRSTSOCKET,
    433                           pp->sendthis + pp->sendsize - pp->sendleft,
    434                           pp->sendleft, FALSE, &written);
    435   if(result == CURLE_AGAIN) {
    436     result = CURLE_OK;
    437     written = 0;
    438   }
    439   if(result)
    440     return result;
    441 
    442   if(written != pp->sendleft) {
    443     /* only a fraction was sent */
    444     pp->sendleft -= written;
    445   }
    446   else {
    447     pp->sendthis = NULL;
    448     pp->sendleft = pp->sendsize = 0;
    449     pp->response = curlx_now();
    450   }
    451   return CURLE_OK;
    452 }
    453 
    454 CURLcode Curl_pp_disconnect(struct pingpong *pp)
    455 {
    456   if(pp->initialised) {
    457     curlx_dyn_free(&pp->sendbuf);
    458     curlx_dyn_free(&pp->recvbuf);
    459     memset(pp, 0, sizeof(*pp));
    460   }
    461   return CURLE_OK;
    462 }
    463 
    464 bool Curl_pp_moredata(struct pingpong *pp)
    465 {
    466   return !pp->sendleft && curlx_dyn_len(&pp->recvbuf) > pp->nfinal;
    467 }
    468 
    469 #endif