| /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
| |
| #include "percent-util.h" |
| #include "string-util.h" |
| #include "parse-util.h" |
| |
| static int parse_parts_value_whole(const char *p, const char *symbol) { |
| const char *pc, *n; |
| int r, v; |
| |
| pc = endswith(p, symbol); |
| if (!pc) |
| return -EINVAL; |
| |
| n = strndupa_safe(p, pc - p); |
| r = safe_atoi(n, &v); |
| if (r < 0) |
| return r; |
| if (v < 0) |
| return -ERANGE; |
| |
| return v; |
| } |
| |
| static int parse_parts_value_with_tenths_place(const char *p, const char *symbol) { |
| const char *pc, *dot, *n; |
| int r, q, v; |
| |
| pc = endswith(p, symbol); |
| if (!pc) |
| return -EINVAL; |
| |
| dot = memchr(p, '.', pc - p); |
| if (dot) { |
| if (dot + 2 != pc) |
| return -EINVAL; |
| if (dot[1] < '0' || dot[1] > '9') |
| return -EINVAL; |
| q = dot[1] - '0'; |
| n = strndupa_safe(p, dot - p); |
| } else { |
| q = 0; |
| n = strndupa_safe(p, pc - p); |
| } |
| r = safe_atoi(n, &v); |
| if (r < 0) |
| return r; |
| if (v < 0) |
| return -ERANGE; |
| if (v > (INT_MAX - q) / 10) |
| return -ERANGE; |
| |
| v = v * 10 + q; |
| return v; |
| } |
| |
| static int parse_parts_value_with_hundredths_place(const char *p, const char *symbol) { |
| const char *pc, *dot, *n; |
| int r, q, v; |
| |
| pc = endswith(p, symbol); |
| if (!pc) |
| return -EINVAL; |
| |
| dot = memchr(p, '.', pc - p); |
| if (dot) { |
| if (dot + 3 == pc) { |
| /* Support two places after the dot */ |
| |
| if (dot[1] < '0' || dot[1] > '9' || dot[2] < '0' || dot[2] > '9') |
| return -EINVAL; |
| q = (dot[1] - '0') * 10 + (dot[2] - '0'); |
| |
| } else if (dot + 2 == pc) { |
| /* Support one place after the dot */ |
| |
| if (dot[1] < '0' || dot[1] > '9') |
| return -EINVAL; |
| q = (dot[1] - '0') * 10; |
| } else |
| /* We do not support zero or more than two places */ |
| return -EINVAL; |
| |
| n = strndupa_safe(p, dot - p); |
| } else { |
| q = 0; |
| n = strndupa_safe(p, pc - p); |
| } |
| r = safe_atoi(n, &v); |
| if (r < 0) |
| return r; |
| if (v < 0) |
| return -ERANGE; |
| if (v > (INT_MAX - q) / 100) |
| return -ERANGE; |
| |
| v = v * 100 + q; |
| return v; |
| } |
| |
| int parse_percent_unbounded(const char *p) { |
| return parse_parts_value_whole(p, "%"); |
| } |
| |
| int parse_percent(const char *p) { |
| int v; |
| |
| v = parse_percent_unbounded(p); |
| if (v > 100) |
| return -ERANGE; |
| |
| return v; |
| } |
| |
| int parse_permille_unbounded(const char *p) { |
| const char *pm; |
| |
| pm = endswith(p, "‰"); |
| if (pm) |
| return parse_parts_value_whole(p, "‰"); |
| |
| return parse_parts_value_with_tenths_place(p, "%"); |
| } |
| |
| int parse_permille(const char *p) { |
| int v; |
| |
| v = parse_permille_unbounded(p); |
| if (v > 1000) |
| return -ERANGE; |
| |
| return v; |
| } |
| |
| int parse_permyriad_unbounded(const char *p) { |
| const char *pm; |
| |
| pm = endswith(p, "‱"); |
| if (pm) |
| return parse_parts_value_whole(p, "‱"); |
| |
| pm = endswith(p, "‰"); |
| if (pm) |
| return parse_parts_value_with_tenths_place(p, "‰"); |
| |
| return parse_parts_value_with_hundredths_place(p, "%"); |
| } |
| |
| int parse_permyriad(const char *p) { |
| int v; |
| |
| v = parse_permyriad_unbounded(p); |
| if (v > 10000) |
| return -ERANGE; |
| |
| return v; |
| } |