tool: support fractions for --limit-rate and --max-filesize

Allow 2.5k or 3.7M etc. Add mention in documentation.

Verify in test case 1623.

Closes #20266
This commit is contained in:
Daniel Stenberg 2026-01-17 23:11:07 +01:00
parent 6aaac9dd38
commit 6d6899c2f0
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
8 changed files with 250 additions and 48 deletions

View file

@ -534,54 +534,93 @@ static ParameterError GetFileAndPassword(const char *nextarg, char **file,
return err;
}
struct sizeunit {
char unit; /* single lowercase ASCII letter */
curl_off_t mul;
size_t mlen; /* number of digits in 'mul', when written in decimal */
};
static const struct sizeunit *getunit(char unit)
{
static const struct sizeunit list[] = {
{'p', (curl_off_t)1125899906842624, 16 }, /* Peta */
{'t', (curl_off_t)1099511627776, 13 }, /* Tera */
{'g', 1073741824, 10 }, /* Giga */
{'m', 1048576, 7 }, /* Mega */
{'k', 1024, 4 }, /* Kilo */
};
size_t i;
for(i = 0; i < CURL_ARRAYSIZE(list); i++)
if((unit | 0x20) == list[i].unit)
return &list[i];
return NULL;
}
/* Get a size parameter for '--limit-rate' or '--max-filesize'.
* We support a 'G', 'M' or 'K' suffix too.
We support P, T, G, M and K (case insensitive) suffixes.
Unit test 1623
*/
static ParameterError GetSizeParameter(const char *arg,
const char *which,
curl_off_t *value_out)
UNITTEST ParameterError GetSizeParameter(const char *arg, curl_off_t *out)
{
const char *unit = arg;
curl_off_t value;
curl_off_t prec = 0;
size_t plen = 0;
curl_off_t add = 0;
curl_off_t mul = 1;
int rc;
if(curlx_str_number(&unit, &value, CURL_OFF_T_MAX)) {
warnf("invalid number specified for %s", which);
return PARAM_BAD_USE;
rc = curlx_str_number(&unit, &value, CURL_OFF_T_MAX);
if(rc == STRE_OVERFLOW)
return PARAM_NUMBER_TOO_LARGE;
else if(rc)
return PARAM_BAD_NUMERIC;
if(!curlx_str_single(&unit, '.')) {
const char *s = unit;
if(curlx_str_number(&unit, &prec, CURL_OFF_T_MAX))
return PARAM_BAD_NUMERIC;
plen = unit - s;
}
if(!*unit)
unit = "b";
else if(strlen(unit) > 1)
unit = "w"; /* unsupported */
switch(*unit) {
case 'G':
case 'g':
if(value > (CURL_OFF_T_MAX / (1024 * 1024 * 1024)))
return PARAM_NUMBER_TOO_LARGE;
value *= 1024 * 1024 * 1024;
break;
case 'M':
case 'm':
if(value > (CURL_OFF_T_MAX / (1024 * 1024)))
return PARAM_NUMBER_TOO_LARGE;
value *= 1024 * 1024;
break;
case 'K':
case 'k':
if(value > (CURL_OFF_T_MAX / 1024))
return PARAM_NUMBER_TOO_LARGE;
value *= 1024;
break;
case 'b':
case 'B':
/* for plain bytes, leave as-is */
break;
default:
warnf("unsupported %s unit. Use G, M, K or B", which);
if(strlen(unit) > 1)
return PARAM_BAD_USE;
else if(!*unit || ((*unit | 0x20) == 'b')) {
if(plen)
/* cannot handle partial bytes */
return PARAM_BAD_USE;
}
*value_out = value;
else {
const struct sizeunit *su = getunit(*unit);
if(!su)
return PARAM_BAD_USE;
mul = su->mul;
if(prec) {
/* precision was provided */
curl_off_t frac = 1;
/* too many precision digits, trim them */
while(su->mlen <= plen) {
prec /= 10;
plen--;
}
while(plen--)
frac *= 10;
if((CURL_OFF_T_MAX / mul) > prec)
add = mul * prec / frac;
else
add = (mul / frac) * prec;
}
}
if(value > ((CURL_OFF_T_MAX - add) / mul))
return PARAM_NUMBER_TOO_LARGE;
*out = value * mul + add;
return PARAM_OK;
}
@ -2365,7 +2404,7 @@ static ParameterError opt_string(struct OperationConfig *config,
err = getstr(&config->dns_servers, nextarg, DENY_BLANK);
break;
case C_LIMIT_RATE: /* --limit-rate */
err = GetSizeParameter(nextarg, "rate", &value);
err = GetSizeParameter(nextarg, &value);
if(!err) {
config->recvpersecond = value;
config->sendpersecond = value;
@ -2401,7 +2440,7 @@ static ParameterError opt_string(struct OperationConfig *config,
err = getstr(&config->haproxy_clientip, nextarg, DENY_BLANK);
break;
case C_MAX_FILESIZE: /* --max-filesize */
err = GetSizeParameter(nextarg, "max-filesize", &value);
err = GetSizeParameter(nextarg, &value);
if(!err)
config->max_filesize = value;
break;

View file

@ -375,6 +375,7 @@ ParameterError getparameter(const char *flag, const char *nextarg,
ParameterError parse_cert_parameter(const char *cert_parameter,
char **certname,
char **passphrase);
UNITTEST ParameterError GetSizeParameter(const char *arg, curl_off_t *out);
#endif
ParameterError parse_args(int argc, argv_item_t argv[]);