diff --git a/docs/cmdline-opts/limit-rate.md b/docs/cmdline-opts/limit-rate.md index f58fd1fcd8..88f62709b0 100644 --- a/docs/cmdline-opts/limit-rate.md +++ b/docs/cmdline-opts/limit-rate.md @@ -12,7 +12,7 @@ See-also: - speed-limit - speed-time Example: - - --limit-rate 100K $URL + - --limit-rate 123.45K $URL - --limit-rate 1000 $URL - --limit-rate 10M $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. 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) -are 1024 based. For example 1k is 1024. Examples: 200K, 3m and 1G. +megabytes etc. The supported suffixes (k, M, G, T, P) are 1024-based. For +example 1k is 1024. Examples: 200K, 3m and 1G. The rate limiting logic works on averaging the transfer speed to no more than 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 might cripple the rate-limiting slightly, to help keep the speed-limit 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. diff --git a/docs/cmdline-opts/max-filesize.md b/docs/cmdline-opts/max-filesize.md index cf2ac65376..e3fd900fe6 100644 --- a/docs/cmdline-opts/max-filesize.md +++ b/docs/cmdline-opts/max-filesize.md @@ -12,6 +12,7 @@ See-also: - limit-rate Example: - --max-filesize 100K $URL + - --max-filesize 2.6M $URL --- # `--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. -A size modifier may be used. For example, Appending 'k' or 'K' counts the -number as kilobytes, 'm' or 'M' makes it megabytes, while 'g' or 'G' makes it -gigabytes. Examples: 200K, 3m and 1G. (Added in 7.58.0) +A unit suffix letter can be used. Appending 'k' or 'K' counts the number as +kilobytes, 'm' or 'M' makes it megabytes etc. The supported suffixes (k, M, G, +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 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 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. diff --git a/src/tool_getparam.c b/src/tool_getparam.c index e51b6f440d..56f4d58f73 100644 --- a/src/tool_getparam.c +++ b/src/tool_getparam.c @@ -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; diff --git a/src/tool_getparam.h b/src/tool_getparam.h index 44eb361edc..0ff0a78182 100644 --- a/src/tool_getparam.h +++ b/src/tool_getparam.h @@ -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[]); diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 99184130af..7de9f5f595 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -214,7 +214,7 @@ test1590 test1591 test1592 test1593 test1594 test1595 test1596 test1597 \ test1598 test1599 test1600 test1601 test1602 test1603 test1604 test1605 \ test1606 test1607 test1608 test1609 test1610 test1611 test1612 test1613 \ test1614 test1615 test1616 test1617 \ -test1620 test1621 test1622 \ +test1620 test1621 test1622 test1623 \ \ test1630 test1631 test1632 test1633 test1634 test1635 test1636 \ \ diff --git a/tests/data/test1623 b/tests/data/test1623 new file mode 100644 index 0000000000..000c08a2ef --- /dev/null +++ b/tests/data/test1623 @@ -0,0 +1,25 @@ + + + + +unittest +tool-sizeparser + + + +# Client-side + + +unittest + + +the size parser for --limit-rate + + +tool%TESTNUMBER + + + + + + diff --git a/tests/tunit/Makefile.inc b/tests/tunit/Makefile.inc index 4196065686..1f3c8cb9b7 100644 --- a/tests/tunit/Makefile.inc +++ b/tests/tunit/Makefile.inc @@ -33,4 +33,5 @@ TESTS_C = \ tool1394.c \ tool1604.c \ tool1621.c \ - tool1622.c + tool1622.c \ + tool1623.c diff --git a/tests/tunit/tool1623.c b/tests/tunit/tool1623.c new file mode 100644 index 0000000000..9fcc0b49cf --- /dev/null +++ b/tests/tunit/tool1623.c @@ -0,0 +1,127 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 +}