1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include "percent-util.h"
4 #include "string-util.h"
5 #include "parse-util.h"
6 
parse_parts_value_whole(const char * p,const char * symbol)7 static int parse_parts_value_whole(const char *p, const char *symbol) {
8         const char *pc, *n;
9         int r, v;
10 
11         pc = endswith(p, symbol);
12         if (!pc)
13                 return -EINVAL;
14 
15         n = strndupa_safe(p, pc - p);
16         r = safe_atoi(n, &v);
17         if (r < 0)
18                 return r;
19         if (v < 0)
20                 return -ERANGE;
21 
22         return v;
23 }
24 
parse_parts_value_with_tenths_place(const char * p,const char * symbol)25 static int parse_parts_value_with_tenths_place(const char *p, const char *symbol) {
26         const char *pc, *dot, *n;
27         int r, q, v;
28 
29         pc = endswith(p, symbol);
30         if (!pc)
31                 return -EINVAL;
32 
33         dot = memchr(p, '.', pc - p);
34         if (dot) {
35                 if (dot + 2 != pc)
36                         return -EINVAL;
37                 if (dot[1] < '0' || dot[1] > '9')
38                         return -EINVAL;
39                 q = dot[1] - '0';
40                 n = strndupa_safe(p, dot - p);
41         } else {
42                 q = 0;
43                 n = strndupa_safe(p, pc - p);
44         }
45         r = safe_atoi(n, &v);
46         if (r < 0)
47                 return r;
48         if (v < 0)
49                 return -ERANGE;
50         if (v > (INT_MAX - q) / 10)
51                 return -ERANGE;
52 
53         v = v * 10 + q;
54         return v;
55 }
56 
parse_parts_value_with_hundredths_place(const char * p,const char * symbol)57 static int parse_parts_value_with_hundredths_place(const char *p, const char *symbol) {
58         const char *pc, *dot, *n;
59         int r, q, v;
60 
61         pc = endswith(p, symbol);
62         if (!pc)
63                 return -EINVAL;
64 
65         dot = memchr(p, '.', pc - p);
66         if (dot) {
67                 if (dot + 3 == pc) {
68                         /* Support two places after the dot */
69 
70                         if (dot[1] < '0' || dot[1] > '9' || dot[2] < '0' || dot[2] > '9')
71                                 return -EINVAL;
72                         q = (dot[1] - '0') * 10 + (dot[2] - '0');
73 
74                 } else if (dot + 2 == pc) {
75                         /* Support one place after the dot */
76 
77                         if (dot[1] < '0' || dot[1] > '9')
78                                 return -EINVAL;
79                         q = (dot[1] - '0') * 10;
80                 } else
81                         /* We do not support zero or more than two places */
82                         return -EINVAL;
83 
84                 n = strndupa_safe(p, dot - p);
85         } else {
86                 q = 0;
87                 n = strndupa_safe(p, pc - p);
88         }
89         r = safe_atoi(n, &v);
90         if (r < 0)
91                 return r;
92         if (v < 0)
93                 return -ERANGE;
94         if (v > (INT_MAX - q) / 100)
95                 return -ERANGE;
96 
97         v = v * 100 + q;
98         return v;
99 }
100 
parse_percent_unbounded(const char * p)101 int parse_percent_unbounded(const char *p) {
102         return parse_parts_value_whole(p, "%");
103 }
104 
parse_percent(const char * p)105 int parse_percent(const char *p) {
106         int v;
107 
108         v = parse_percent_unbounded(p);
109         if (v > 100)
110                 return -ERANGE;
111 
112         return v;
113 }
114 
parse_permille_unbounded(const char * p)115 int parse_permille_unbounded(const char *p) {
116         const char *pm;
117 
118         pm = endswith(p, "‰");
119         if (pm)
120                 return parse_parts_value_whole(p, "‰");
121 
122         return parse_parts_value_with_tenths_place(p, "%");
123 }
124 
parse_permille(const char * p)125 int parse_permille(const char *p) {
126         int v;
127 
128         v = parse_permille_unbounded(p);
129         if (v > 1000)
130                 return -ERANGE;
131 
132         return v;
133 }
134 
parse_permyriad_unbounded(const char * p)135 int parse_permyriad_unbounded(const char *p) {
136         const char *pm;
137 
138         pm = endswith(p, "‱");
139         if (pm)
140                 return parse_parts_value_whole(p, "‱");
141 
142         pm = endswith(p, "‰");
143         if (pm)
144                 return parse_parts_value_with_tenths_place(p, "‰");
145 
146         return parse_parts_value_with_hundredths_place(p, "%");
147 }
148 
parse_permyriad(const char * p)149 int parse_permyriad(const char *p) {
150         int v;
151 
152         v = parse_permyriad_unbounded(p);
153         if (v > 10000)
154                 return -ERANGE;
155 
156         return v;
157 }
158