curl: timeout in the read callback

The read callback can timeout if there's nothing to read within the
given maximum period. Example use case is when doing "curl -m 3
telnet://example.com" or anything else that expects input on stdin or
similar that otherwise would "hang" until something happens and then not
respect the timeout.

This fixes KNOWN_BUG 8.1, first filed in July 2009.

Bug: https://sourceforge.net/p/curl/bugs/846/

Closes #9815
This commit is contained in:
Daniel Stenberg 2022-10-27 13:40:06 +02:00
Родитель b830f9ba9e
Коммит a55256cfb2
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 5CC908FDB71E12C2
9 изменённых файлов: 58 добавлений и 29 удалений

Просмотреть файл

@ -95,7 +95,6 @@ problems may have been fixed or changed somewhat since this was written.
7.12 FTPS directory listing hangs on Windows with Schannel
8. TELNET
8.1 TELNET and time limitations do not work
8.2 Microsoft telnet server
9. SFTP and SCP
@ -781,11 +780,6 @@ problems may have been fixed or changed somewhat since this was written.
8. TELNET
8.1 TELNET and time limitations do not work
When using telnet, the time limitation options do not work.
https://curl.se/bug/view.cgi?id=846
8.2 Microsoft telnet server
There seems to be a problem when connecting to the Microsoft telnet server.

Просмотреть файл

@ -23,6 +23,10 @@
***************************************************************************/
#include "tool_setup.h"
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#define ENABLE_CURLX_PRINTF
/* use our own printf() functions */
#include "curlx.h"
@ -30,6 +34,7 @@
#include "tool_cfgable.h"
#include "tool_cb_rea.h"
#include "tool_operate.h"
#include "tool_util.h"
#include "memdebug.h" /* keep this as LAST include */
@ -39,8 +44,36 @@
size_t tool_read_cb(char *buffer, size_t sz, size_t nmemb, void *userdata)
{
ssize_t rc;
ssize_t rc = 0;
struct InStruct *in = userdata;
struct OperationConfig *config = in->config;
if(config->timeout_ms) {
struct timeval now = tvnow();
long msdelta = tvdiff(now, in->per->start);
if(msdelta > config->timeout_ms)
/* timeout */
return 0;
#ifndef WIN32
/* this logic waits on read activity on a file descriptor that is not a
socket which makes it not work with select() on Windows */
else {
fd_set bits;
struct timeval timeout;
long wait = config->timeout_ms - msdelta;
/* wait this long at the most */
timeout.tv_sec = wait/1000;
timeout.tv_usec = (wait%1000)*1000;
FD_ZERO(&bits);
FD_SET(in->fd, &bits);
if(!select(in->fd + 1, &bits, NULL, NULL, &timeout))
return 0; /* timeout */
}
#endif
}
rc = read(in->fd, buffer, sz*nmemb);
if(rc < 0) {
@ -53,6 +86,8 @@ size_t tool_read_cb(char *buffer, size_t sz, size_t nmemb, void *userdata)
rc = 0;
}
in->config->readbusy = FALSE;
/* when select() rerturned zero here, it timed out */
return (size_t)rc;
}

Просмотреть файл

@ -70,8 +70,8 @@ struct OperationConfig {
char *postfields;
curl_off_t postfieldsize;
char *referer;
double timeout;
double connecttimeout;
long timeout_ms;
long connecttimeout_ms;
long maxredirs;
curl_off_t max_filesize;
char *output_dir;
@ -272,7 +272,7 @@ struct OperationConfig {
bool abstract_unix_socket; /* path to an abstract Unix domain socket */
bool falsestart;
bool path_as_is;
double expect100timeout;
long expect100timeout_ms;
bool suppress_connect_headers; /* suppress proxy CONNECT response headers
from user callbacks */
bool synthetic_error; /* if TRUE, this is tool-internal error */

Просмотреть файл

@ -807,8 +807,7 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
config->authtype |= CURLAUTH_BEARER;
break;
case 'c': /* connect-timeout */
err = str2udouble(&config->connecttimeout, nextarg,
(double)LONG_MAX/1000);
err = secs2ms(&config->connecttimeout_ms, nextarg);
if(err)
return err;
break;
@ -1374,8 +1373,7 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
return err;
break;
case 'R': /* --expect100-timeout */
err = str2udouble(&config->expect100timeout, nextarg,
(double)LONG_MAX/1000);
err = secs2ms(&config->expect100timeout_ms, nextarg);
if(err)
return err;
break;
@ -2068,7 +2066,7 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
break;
case 'm':
/* specified max time */
err = str2udouble(&config->timeout, nextarg, (double)LONG_MAX/1000);
err = secs2ms(&config->timeout_ms, nextarg);
if(err)
return err;
break;

Просмотреть файл

@ -328,6 +328,7 @@ static CURLcode pre_transfer(struct GlobalConfig *global,
}
per->input.fd = per->infd;
}
per->start = tvnow();
return result;
}
@ -1243,6 +1244,7 @@ static CURLcode single_transfer(struct GlobalConfig *global,
/* for uploads */
input->config = config;
input->per = per;
/* Note that if CURLOPT_READFUNCTION is fread (the default), then
* lib/telnet.c will Curl_poll() on the input file descriptor
* rather than calling the READFUNCTION at regular intervals.
@ -1344,7 +1346,7 @@ static CURLcode single_transfer(struct GlobalConfig *global,
per->errorbuffer = global_errorbuffer;
my_setopt(curl, CURLOPT_ERRORBUFFER, global_errorbuffer);
}
my_setopt(curl, CURLOPT_TIMEOUT_MS, (long)(config->timeout * 1000));
my_setopt(curl, CURLOPT_TIMEOUT_MS, config->timeout_ms);
switch(config->httpreq) {
case HTTPREQ_SIMPLEPOST:
@ -1832,8 +1834,7 @@ static CURLcode single_transfer(struct GlobalConfig *global,
my_setopt_slist(curl, CURLOPT_TELNETOPTIONS, config->telnet_options);
/* new in libcurl 7.7: */
my_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS,
(long)(config->connecttimeout * 1000));
my_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, config->connecttimeout_ms);
if(config->doh_url)
my_setopt_str(curl, CURLOPT_DOH_URL, config->doh_url);
@ -2079,9 +2080,9 @@ static CURLcode single_transfer(struct GlobalConfig *global,
my_setopt_str(curl, CURLOPT_DEFAULT_PROTOCOL, config->proto_default);
/* new in 7.47.0 */
if(config->expect100timeout > 0)
if(config->expect100timeout_ms > 0)
my_setopt_str(curl, CURLOPT_EXPECT_100_TIMEOUT_MS,
(long)(config->expect100timeout*1000));
config->expect100timeout_ms);
/* new in 7.48.0 */
if(config->tftp_no_options && proto_tftp)
@ -2386,7 +2387,6 @@ static CURLcode serial_transfers(struct GlobalConfig *global,
bool retry;
long delay_ms;
bool bailout = FALSE;
struct timeval start;
result = pre_transfer(global, per);
if(result)
break;
@ -2397,7 +2397,6 @@ static CURLcode serial_transfers(struct GlobalConfig *global,
break;
}
start = tvnow();
#ifdef CURLDEBUG
if(global->test_event_based)
result = curl_easy_perform_ev(per->curl);
@ -2429,7 +2428,7 @@ static CURLcode serial_transfers(struct GlobalConfig *global,
if(per && global->ms_per_transfer) {
/* how long time did the most recent transfer take in number of
milliseconds */
long milli = tvdiff(tvnow(), start);
long milli = tvdiff(tvnow(), per->start);
if(milli < global->ms_per_transfer) {
notef(global, "Transfer took %ld ms, waits %ldms as set by --rate\n",
milli, global->ms_per_transfer - milli);

Просмотреть файл

@ -37,6 +37,7 @@ struct per_transfer {
long retry_numretries;
long retry_sleep_default;
long retry_sleep;
struct timeval start; /* start of this transfer */
struct timeval retrystart;
char *this_url;
unsigned int urlnum; /* the index of the given URL */

Просмотреть файл

@ -240,8 +240,9 @@ static ParameterError str2double(double *val, const char *str, double max)
}
/*
* Parse the string and write the double in the given address. Return PARAM_OK
* on success, otherwise a parameter error enum. ONLY ACCEPTS POSITIVE NUMBERS!
* Parse the string as seconds with decimals, and write the number of
* milliseconds that corresponds in the given address. Return PARAM_OK on
* success, otherwise a parameter error enum. ONLY ACCEPTS POSITIVE NUMBERS!
*
* The 'max' argument is the maximum value allowed, as the numbers are often
* multiplied when later used.
@ -251,16 +252,16 @@ static ParameterError str2double(double *val, const char *str, double max)
* data.
*/
ParameterError str2udouble(double *valp, const char *str, double max)
ParameterError secs2ms(long *valp, const char *str)
{
double value;
ParameterError result = str2double(&value, str, max);
ParameterError result = str2double(&value, str, (double)LONG_MAX/1000);
if(result != PARAM_OK)
return result;
if(value < 0)
return PARAM_NEGATIVE_NUMERIC;
*valp = value;
*valp = (long)(value*1000);
return PARAM_OK;
}

Просмотреть файл

@ -36,7 +36,7 @@ ParameterError str2num(long *val, const char *str);
ParameterError str2unum(long *val, const char *str);
ParameterError oct2nummax(long *val, const char *str, long max);
ParameterError str2unummax(long *val, const char *str, long max);
ParameterError str2udouble(double *val, const char *str, double max);
ParameterError secs2ms(long *val, const char *str);
ParameterError proto2num(struct OperationConfig *config,
const char * const *val, char **obuf,

Просмотреть файл

@ -84,6 +84,7 @@ struct OutStruct {
struct InStruct {
int fd;
struct OperationConfig *config;
struct per_transfer *per;
};