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
8 changed files with 250 additions and 48 deletions

View File

@@ -12,7 +12,7 @@ See-also:
- speed-limit - speed-limit
- speed-time - speed-time
Example: Example:
- --limit-rate 100K $URL - --limit-rate 123.45K $URL
- --limit-rate 1000 $URL - --limit-rate 1000 $URL
- --limit-rate 10M $URL - --limit-rate 10M $URL
- --limit-rate 200K --max-time 60 $URL - --limit-rate 200K --max-time 60 $URL
@@ -27,8 +27,8 @@ otherwise would be.
The given speed is measured in bytes/second, unless a suffix is appended. The given speed is measured in bytes/second, unless a suffix is appended.
Appending 'k' or 'K' counts the number as kilobytes, 'm' or 'M' makes it Appending 'k' or 'K' counts the number as kilobytes, 'm' or 'M' makes it
megabytes, while 'g' or 'G' makes it gigabytes. The suffixes (k, M, G, T, P) megabytes etc. The supported suffixes (k, M, G, T, P) are 1024-based. For
are 1024 based. For example 1k is 1024. Examples: 200K, 3m and 1G. example 1k is 1024. Examples: 200K, 3m and 1G.
The rate limiting logic works on averaging the transfer speed to no more than The rate limiting logic works on averaging the transfer speed to no more than
the set threshold over a period of multiple seconds. the set threshold over a period of multiple seconds.
@@ -36,3 +36,7 @@ the set threshold over a period of multiple seconds.
If you also use the --speed-limit option, that option takes precedence and If you also use the --speed-limit option, that option takes precedence and
might cripple the rate-limiting slightly, to help keep the speed-limit might cripple the rate-limiting slightly, to help keep the speed-limit
logic working. logic working.
Starting in curl 8.19.0, the rate can be specified using a fraction as in
`2.5M` for two and a half megabytes per second. It only works with a period
(`.`) delimiter, independent of what your locale might prefer.

View File

@@ -12,6 +12,7 @@ See-also:
- limit-rate - limit-rate
Example: Example:
- --max-filesize 100K $URL - --max-filesize 100K $URL
- --max-filesize 2.6M $URL
--- ---
# `--max-filesize` # `--max-filesize`
@@ -22,9 +23,9 @@ transfer does not start and curl returns with exit code 63.
Setting the maximum value to zero disables the limit. Setting the maximum value to zero disables the limit.
A size modifier may be used. For example, Appending 'k' or 'K' counts the A unit suffix letter can be used. Appending 'k' or 'K' counts the number as
number as kilobytes, 'm' or 'M' makes it megabytes, while 'g' or 'G' makes it kilobytes, 'm' or 'M' makes it megabytes etc. The supported suffixes (k, M, G,
gigabytes. Examples: 200K, 3m and 1G. (Added in 7.58.0) T, P) are 1024-based. Examples: 200K, 3m and 1G. (Added in 7.58.0)
**NOTE**: before curl 8.4.0, when the file size is not known prior to **NOTE**: before curl 8.4.0, when the file size is not known prior to
download, for such files this option has no effect even if the file transfer download, for such files this option has no effect even if the file transfer
@@ -32,3 +33,7 @@ ends up being larger than this given limit.
Starting with curl 8.4.0, this option aborts the transfer if it reaches the Starting with curl 8.4.0, this option aborts the transfer if it reaches the
threshold during transfer. threshold during transfer.
Starting in curl 8.19.0, the maximum size can be specified using a fraction as
in `2.5M` for two and a half megabytes. It only works with a period (`.`)
delimiter, independent of what your locale might prefer.

View File

@@ -534,54 +534,93 @@ static ParameterError GetFileAndPassword(const char *nextarg, char **file,
return err; 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'. /* 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, UNITTEST ParameterError GetSizeParameter(const char *arg, curl_off_t *out)
const char *which,
curl_off_t *value_out)
{ {
const char *unit = arg; const char *unit = arg;
curl_off_t value; 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)) { rc = curlx_str_number(&unit, &value, CURL_OFF_T_MAX);
warnf("invalid number specified for %s", which); if(rc == STRE_OVERFLOW)
return PARAM_BAD_USE; 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) if(strlen(unit) > 1)
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);
return PARAM_BAD_USE; 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; return PARAM_OK;
} }
@@ -2365,7 +2404,7 @@ static ParameterError opt_string(struct OperationConfig *config,
err = getstr(&config->dns_servers, nextarg, DENY_BLANK); err = getstr(&config->dns_servers, nextarg, DENY_BLANK);
break; break;
case C_LIMIT_RATE: /* --limit-rate */ case C_LIMIT_RATE: /* --limit-rate */
err = GetSizeParameter(nextarg, "rate", &value); err = GetSizeParameter(nextarg, &value);
if(!err) { if(!err) {
config->recvpersecond = value; config->recvpersecond = value;
config->sendpersecond = value; config->sendpersecond = value;
@@ -2401,7 +2440,7 @@ static ParameterError opt_string(struct OperationConfig *config,
err = getstr(&config->haproxy_clientip, nextarg, DENY_BLANK); err = getstr(&config->haproxy_clientip, nextarg, DENY_BLANK);
break; break;
case C_MAX_FILESIZE: /* --max-filesize */ case C_MAX_FILESIZE: /* --max-filesize */
err = GetSizeParameter(nextarg, "max-filesize", &value); err = GetSizeParameter(nextarg, &value);
if(!err) if(!err)
config->max_filesize = value; config->max_filesize = value;
break; break;

View File

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

View File

@@ -214,7 +214,7 @@ test1590 test1591 test1592 test1593 test1594 test1595 test1596 test1597 \
test1598 test1599 test1600 test1601 test1602 test1603 test1604 test1605 \ test1598 test1599 test1600 test1601 test1602 test1603 test1604 test1605 \
test1606 test1607 test1608 test1609 test1610 test1611 test1612 test1613 \ test1606 test1607 test1608 test1609 test1610 test1611 test1612 test1613 \
test1614 test1615 test1616 test1617 \ test1614 test1615 test1616 test1617 \
test1620 test1621 test1622 \ test1620 test1621 test1622 test1623 \
\ \
test1630 test1631 test1632 test1633 test1634 test1635 test1636 \ test1630 test1631 test1632 test1633 test1634 test1635 test1636 \
\ \

25
tests/data/test1623 Normal file
View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="US-ASCII"?>
<testcase>
<info>
<keywords>
unittest
tool-sizeparser
</keywords>
</info>
# Client-side
<client>
<features>
unittest
</features>
<name>
the size parser for --limit-rate
</name>
<tool>
tool%TESTNUMBER
</tool>
</client>
<verify>
</verify>
</testcase>

View File

@@ -33,4 +33,5 @@ TESTS_C = \
tool1394.c \ tool1394.c \
tool1604.c \ tool1604.c \
tool1621.c \ tool1621.c \
tool1622.c tool1622.c \
tool1623.c

127
tests/tunit/tool1623.c Normal file
View File

@@ -0,0 +1,127 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* SPDX-License-Identifier: curl
*
***************************************************************************/
#include "unitcheck.h"
#include "tool_getparam.h"
struct check1623 {
const char *input;
curl_off_t amount;
ParameterError err;
};
static CURLcode test_tool1623(const char *arg)
{
UNITTEST_BEGIN_SIMPLE
{
int i;
static const struct check1623 check[] = {
{ "0", 0, PARAM_OK},
{ "00", 0, PARAM_OK},
{ "000", 0, PARAM_OK},
{ "1", 1, PARAM_OK},
{ "1b", 1, PARAM_OK},
{ "99B", 99, PARAM_OK},
{ "2", 2, PARAM_OK},
{ "3", 3, PARAM_OK},
{ "4", 4, PARAM_OK},
{ "5", 5, PARAM_OK},
{ "6", 6, PARAM_OK},
{ "7", 7, PARAM_OK},
{ "77", 77, PARAM_OK},
{ "8", 8, PARAM_OK},
{ "9", 9, PARAM_OK},
{ "10", 10, PARAM_OK},
{ "010", 10, PARAM_OK},
{ "000000000000000000000000000000000010", 10, PARAM_OK},
{ "1k", 1024, PARAM_OK},
{ "2K", 2048, PARAM_OK},
{ "3k", 3072, PARAM_OK},
{ "4K", 4096, PARAM_OK},
{ "5k", 5120, PARAM_OK},
{ "6K", 6144, PARAM_OK},
{ "7k", 7168, PARAM_OK},
{ "8K", 8192, PARAM_OK},
{ "9k", 9216, PARAM_OK},
{ "10K", 10240, PARAM_OK},
{ "20M", 20971520, PARAM_OK},
{ "30G", 32212254720, PARAM_OK},
{ "40T", 43980465111040, PARAM_OK},
{ "50P", 56294995342131200, PARAM_OK},
{ "1.1k", 1126, PARAM_OK},
{ "1.01k", 1034, PARAM_OK},
{ "1.001k", 1025, PARAM_OK},
{ "1.0001k", 1024, PARAM_OK},
{ "22.1m", 23173529, PARAM_OK},
{ "22.01m", 23079157, PARAM_OK},
{ "22.001m", 23069720, PARAM_OK},
{ "22.0001m", 23068776, PARAM_OK},
{ "22.00001m", 23068682, PARAM_OK},
{ "22.000001m", 23068673, PARAM_OK},
{ "22.0000001m", 23068672, PARAM_OK},
{ "22.000000001m", 23068672, PARAM_OK},
{ "3.4", 0, PARAM_BAD_USE},
{ "3.14b", 0, PARAM_BAD_USE},
{ "5000.9P", 5630512844129278361, PARAM_OK},
{ "5000.99P", 5630614175120894197, PARAM_OK},
{ "5000.999P", 5630624308220055781, PARAM_OK},
{ "5000.9999P", 5630625321529969316, PARAM_OK},
{ "8191P", 9222246136947933184, PARAM_OK},
{ "8191.9999999P", 9223372036735343194, PARAM_OK},
{ "8192P", 0, PARAM_NUMBER_TOO_LARGE},
{ "9223372036854775807", 9223372036854775807, PARAM_OK},
{ "9223372036854775808", 0, PARAM_NUMBER_TOO_LARGE},
{ "a", 0, PARAM_BAD_NUMERIC},
{ "-2", 0, PARAM_BAD_NUMERIC},
{ "+2", 0, PARAM_BAD_NUMERIC},
{ "2,2k", 0, PARAM_BAD_USE},
{ NULL, 0, PARAM_OK } /* end of list */
};
for(i = 0; check[i].input; i++) {
bool ok = FALSE;
curl_off_t output = 0;
ParameterError err =
GetSizeParameter(check[i].input, &output);
if(err != check[i].err)
curl_mprintf("'%s' unexpectedly returned %d \n",
check[i].input, err);
else if(check[i].amount != output)
curl_mprintf("'%s' unexpectedly gave %" FMT_OFF_T "\n",
check[i].input, output);
else {
#if 0 /* enable for debugging */
if(err)
curl_mprintf("'%s' returned %d\n", check[i].input, err);
else
curl_mprintf("'%s' == %" FMT_OFF_T "\n", check[i].input, output);
#endif
ok = TRUE;
}
if(!ok)
unitfail++;
}
}
UNITTEST_END_SIMPLE
}