/src/sudo/lib/util/strtonum.c
Line  | Count  | Source  | 
1  |  | /*  | 
2  |  |  * SPDX-License-Identifier: ISC  | 
3  |  |  *  | 
4  |  |  * Copyright (c) 2013-2015, 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws>  | 
5  |  |  *  | 
6  |  |  * Permission to use, copy, modify, and distribute this software for any  | 
7  |  |  * purpose with or without fee is hereby granted, provided that the above  | 
8  |  |  * copyright notice and this permission notice appear in all copies.  | 
9  |  |  *  | 
10  |  |  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES  | 
11  |  |  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF  | 
12  |  |  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR  | 
13  |  |  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES  | 
14  |  |  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN  | 
15  |  |  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF  | 
16  |  |  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.  | 
17  |  |  */  | 
18  |  |  | 
19  |  | #include <config.h>  | 
20  |  |  | 
21  |  | #include <ctype.h>  | 
22  |  | #include <errno.h>  | 
23  |  |  | 
24  |  | #include <sudo_compat.h>  | 
25  |  | #include <sudo_gettext.h>  | 
26  |  | #include <sudo_util.h>  | 
27  |  |  | 
28  |  | enum strtonum_err { | 
29  |  |     STN_INITIAL,  | 
30  |  |     STN_VALID,  | 
31  |  |     STN_INVALID,  | 
32  |  |     STN_TOOSMALL,  | 
33  |  |     STN_TOOBIG  | 
34  |  | };  | 
35  |  |  | 
36  |  | /*  | 
37  |  |  * Convert a string to a number in the range [minval, maxval]  | 
38  |  |  * Unlike strtonum(), this returns the first non-digit in endp (if not NULL).  | 
39  |  |  */  | 
40  |  | long long  | 
41  |  | sudo_strtonumx(const char *str, long long minval, long long maxval, char **endp,  | 
42  |  |     const char **errstrp)  | 
43  | 0  | { | 
44  | 0  |     enum strtonum_err errval = STN_INITIAL;  | 
45  | 0  |     long long lastval, result = 0;  | 
46  | 0  |     const char *cp = str;  | 
47  | 0  |     int remainder;  | 
48  | 0  |     char ch, sign;  | 
49  |  | 
  | 
50  | 0  |     if (minval > maxval) { | 
51  | 0  |   errval = STN_INVALID;  | 
52  | 0  |   goto done;  | 
53  | 0  |     }  | 
54  |  |  | 
55  |  |     /* Trim leading space and check sign, if any. */  | 
56  | 0  |     do { | 
57  | 0  |   ch = *cp++;  | 
58  | 0  |     } while (isspace((unsigned char)ch));  | 
59  | 0  |     switch (ch) { | 
60  | 0  |     case '-':  | 
61  | 0  |   sign = '-';  | 
62  | 0  |   ch = *cp++;  | 
63  | 0  |   break;  | 
64  | 0  |     case '+':  | 
65  | 0  |   ch = *cp++;  | 
66  | 0  |   FALLTHROUGH;  | 
67  | 0  |     default:  | 
68  | 0  |   sign = '+';  | 
69  | 0  |   break;  | 
70  | 0  |     }  | 
71  |  |  | 
72  |  |     /*  | 
73  |  |      * To prevent overflow we determine the highest (or lowest in  | 
74  |  |      * the case of negative numbers) value result can have *before*  | 
75  |  |      * if its multiplied (divided) by 10 as well as the remainder.  | 
76  |  |      * If result matches this value and the next digit is larger than  | 
77  |  |      * the remainder, we know the result is out of range.  | 
78  |  |      * The remainder is always positive since it is compared against  | 
79  |  |      * an unsigned digit.  | 
80  |  |      */  | 
81  | 0  |     if (sign == '-') { | 
82  | 0  |   lastval = minval / 10;  | 
83  | 0  |   remainder = -(int)(minval % 10);  | 
84  | 0  |   if (remainder < 0) { | 
85  | 0  |       lastval += 1;  | 
86  | 0  |       remainder += 10;  | 
87  | 0  |   }  | 
88  | 0  |   for (;; ch = *cp++) { | 
89  | 0  |       if (!isdigit((unsigned char)ch))  | 
90  | 0  |     break;  | 
91  | 0  |       ch -= '0';  | 
92  | 0  |       if (result < lastval || (result == lastval && ch > remainder)) { | 
93  |  |     /* Skip remaining digits. */  | 
94  | 0  |     do { | 
95  | 0  |         ch = *cp++;  | 
96  | 0  |     } while (isdigit((unsigned char)ch));  | 
97  | 0  |     errval = STN_TOOSMALL;  | 
98  | 0  |     break;  | 
99  | 0  |       } else { | 
100  | 0  |     result *= 10;  | 
101  | 0  |     result -= ch;  | 
102  | 0  |     errval = STN_VALID;  | 
103  | 0  |       }  | 
104  | 0  |   }  | 
105  | 0  |   if (result > maxval)  | 
106  | 0  |       errval = STN_TOOBIG;  | 
107  | 0  |     } else { | 
108  | 0  |   lastval = maxval / 10;  | 
109  | 0  |   remainder = (int)(maxval % 10);  | 
110  | 0  |   for (;; ch = *cp++) { | 
111  | 0  |       if (!isdigit((unsigned char)ch))  | 
112  | 0  |     break;  | 
113  | 0  |       ch -= '0';  | 
114  | 0  |       if (result > lastval || (result == lastval && ch > remainder)) { | 
115  |  |     /* Skip remaining digits. */  | 
116  | 0  |     do { | 
117  | 0  |         ch = *cp++;  | 
118  | 0  |     } while (isdigit((unsigned char)ch));  | 
119  | 0  |     errval = STN_TOOBIG;  | 
120  | 0  |     break;  | 
121  | 0  |       } else { | 
122  | 0  |     result *= 10;  | 
123  | 0  |     result += ch;  | 
124  | 0  |     errval = STN_VALID;  | 
125  | 0  |       }  | 
126  | 0  |   }  | 
127  | 0  |   if (result < minval)  | 
128  | 0  |       errval = STN_TOOSMALL;  | 
129  | 0  |     }  | 
130  |  | 
  | 
131  | 0  | done:  | 
132  | 0  |     switch (errval) { | 
133  | 0  |     case STN_INITIAL:  | 
134  | 0  |     case STN_VALID:  | 
135  | 0  |   if (errstrp != NULL)  | 
136  | 0  |       *errstrp = NULL;  | 
137  | 0  |   break;  | 
138  | 0  |     case STN_INVALID:  | 
139  | 0  |   result = 0;  | 
140  | 0  |   errno = EINVAL;  | 
141  | 0  |   if (errstrp != NULL)  | 
142  | 0  |       *errstrp = N_("invalid value"); | 
143  | 0  |   break;  | 
144  | 0  |     case STN_TOOSMALL:  | 
145  | 0  |   result = 0;  | 
146  | 0  |   errno = ERANGE;  | 
147  | 0  |   if (errstrp != NULL)  | 
148  | 0  |       *errstrp = N_("value too small"); | 
149  | 0  |   break;  | 
150  | 0  |     case STN_TOOBIG:  | 
151  | 0  |   result = 0;  | 
152  | 0  |   errno = ERANGE;  | 
153  | 0  |   if (errstrp != NULL)  | 
154  | 0  |       *errstrp = N_("value too large"); | 
155  | 0  |   break;  | 
156  | 0  |     }  | 
157  | 0  |     if (endp != NULL) { | 
158  | 0  |   if (errval == STN_INITIAL || errval == STN_INVALID)  | 
159  | 0  |       *endp = (char *)str;  | 
160  | 0  |   else  | 
161  | 0  |       *endp = (char *)(cp - 1);  | 
162  | 0  |     }  | 
163  | 0  |     return result;  | 
164  | 0  | }  | 
165  |  |  | 
166  |  | /*  | 
167  |  |  * Convert a string to a number in the range [minval, maxval]  | 
168  |  |  */  | 
169  |  | long long  | 
170  |  | sudo_strtonum(const char *str, long long minval, long long maxval,  | 
171  |  |     const char **errstrp)  | 
172  | 0  | { | 
173  | 0  |     const char *errstr;  | 
174  | 0  |     char *ep;  | 
175  | 0  |     long long ret;  | 
176  |  | 
  | 
177  | 0  |     ret = sudo_strtonumx(str, minval, maxval, &ep, &errstr);  | 
178  |  |     /* Check for empty string and terminating NUL. */  | 
179  | 0  |     if (str == ep || *ep != '\0') { | 
180  | 0  |   errno = EINVAL;  | 
181  | 0  |   errstr = N_("invalid value"); | 
182  | 0  |   ret = 0;  | 
183  | 0  |     }  | 
184  | 0  |     if (errstrp != NULL)  | 
185  | 0  |   *errstrp = errstr;  | 
186  | 0  |     return ret;  | 
187  | 0  | }  |