summaryrefslogtreecommitdiffstats
path: root/lib/util/strtonum.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/util/strtonum.c193
1 files changed, 193 insertions, 0 deletions
diff --git a/lib/util/strtonum.c b/lib/util/strtonum.c
new file mode 100644
index 0000000..12edbbe
--- /dev/null
+++ b/lib/util/strtonum.c
@@ -0,0 +1,193 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2013-2015, 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <errno.h>
+
+#include "sudo_compat.h"
+#include "sudo_gettext.h"
+#include "sudo_util.h"
+
+enum strtonum_err {
+ STN_INITIAL,
+ STN_VALID,
+ STN_INVALID,
+ STN_TOOSMALL,
+ STN_TOOBIG
+};
+
+/*
+ * Convert a string to a number in the range [minval, maxval]
+ * Unlike strtonum(), this returns the first non-digit in endp (if not NULL).
+ */
+long long
+sudo_strtonumx(const char *str, long long minval, long long maxval, char **endp,
+ const char **errstrp)
+{
+ enum strtonum_err errval = STN_INITIAL;
+ long long lastval, result = 0;
+ const char *cp = str;
+ unsigned char ch;
+ int remainder;
+ char sign;
+
+ if (minval > maxval) {
+ errval = STN_INVALID;
+ goto done;
+ }
+
+ /* Trim leading space and check sign, if any. */
+ do {
+ ch = *cp++;
+ } while (isspace(ch));
+ switch (ch) {
+ case '-':
+ sign = '-';
+ ch = *cp++;
+ break;
+ case '+':
+ ch = *cp++;
+ FALLTHROUGH;
+ default:
+ sign = '+';
+ break;
+ }
+
+ /*
+ * To prevent overflow we determine the highest (or lowest in
+ * the case of negative numbers) value result can have *before*
+ * if its multiplied (divided) by 10 as well as the remainder.
+ * If result matches this value and the next digit is larger than
+ * the remainder, we know the result is out of range.
+ * The remainder is always positive since it is compared against
+ * an unsigned digit.
+ */
+ if (sign == '-') {
+ lastval = minval / 10;
+ remainder = -(minval % 10);
+ if (remainder < 0) {
+ lastval += 1;
+ remainder += 10;
+ }
+ for (;; ch = *cp++) {
+ if (!isdigit(ch))
+ break;
+ ch -= '0';
+ if (result < lastval || (result == lastval && ch > remainder)) {
+ /* Skip remaining digits. */
+ do {
+ ch = *cp++;
+ } while (isdigit(ch));
+ errval = STN_TOOSMALL;
+ break;
+ } else {
+ result *= 10;
+ result -= ch;
+ errval = STN_VALID;
+ }
+ }
+ if (result > maxval)
+ errval = STN_TOOBIG;
+ } else {
+ lastval = maxval / 10;
+ remainder = maxval % 10;
+ for (;; ch = *cp++) {
+ if (!isdigit(ch))
+ break;
+ ch -= '0';
+ if (result > lastval || (result == lastval && ch > remainder)) {
+ /* Skip remaining digits. */
+ do {
+ ch = *cp++;
+ } while (isdigit(ch));
+ errval = STN_TOOBIG;
+ break;
+ } else {
+ result *= 10;
+ result += ch;
+ errval = STN_VALID;
+ }
+ }
+ if (result < minval)
+ errval = STN_TOOSMALL;
+ }
+
+done:
+ switch (errval) {
+ case STN_INITIAL:
+ case STN_VALID:
+ if (errstrp != NULL)
+ *errstrp = NULL;
+ break;
+ case STN_INVALID:
+ result = 0;
+ errno = EINVAL;
+ if (errstrp != NULL)
+ *errstrp = N_("invalid value");
+ break;
+ case STN_TOOSMALL:
+ result = 0;
+ errno = ERANGE;
+ if (errstrp != NULL)
+ *errstrp = N_("value too small");
+ break;
+ case STN_TOOBIG:
+ result = 0;
+ errno = ERANGE;
+ if (errstrp != NULL)
+ *errstrp = N_("value too large");
+ break;
+ }
+ if (endp != NULL) {
+ if (errval == STN_INITIAL || errval == STN_INVALID)
+ *endp = (char *)str;
+ else
+ *endp = (char *)(cp - 1);
+ }
+ return result;
+}
+
+/*
+ * Convert a string to a number in the range [minval, maxval]
+ */
+long long
+sudo_strtonum(const char *str, long long minval, long long maxval,
+ const char **errstrp)
+{
+ const char *errstr;
+ char *ep;
+ long long ret;
+
+ ret = sudo_strtonumx(str, minval, maxval, &ep, &errstr);
+ /* Check for empty string and terminating NUL. */
+ if (str == ep || *ep != '\0') {
+ errno = EINVAL;
+ errstr = N_("invalid value");
+ ret = 0;
+ }
+ if (errstrp != NULL)
+ *errstrp = errstr;
+ return ret;
+}