1 /** @file
2     Option parsing functions to complement getopt.
3 
4     Copyright (C) 2017 Christian Zuckschwerdt
5 
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10 */
11 
12 #include "optparse.h"
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <limits.h>
16 #include <string.h>
17 
tls_param(tls_opts_t * tls_opts,char const * key,char const * val)18 int tls_param(tls_opts_t *tls_opts, char const *key, char const *val)
19 {
20     if (!tls_opts || !key || !*key)
21         return 1;
22     else if (!strcasecmp(key, "tls_cert"))
23         tls_opts->tls_cert = val;
24     else if (!strcasecmp(key, "tls_key"))
25         tls_opts->tls_key = val;
26     else if (!strcasecmp(key, "tls_ca_cert"))
27         tls_opts->tls_ca_cert = val;
28     else if (!strcasecmp(key, "tls_cipher_suites"))
29         tls_opts->tls_cipher_suites = val;
30     else if (!strcasecmp(key, "tls_server_name"))
31         tls_opts->tls_server_name = val;
32     else if (!strcasecmp(key, "tls_psk_identity"))
33         tls_opts->tls_psk_identity = val;
34     else if (!strcasecmp(key, "tls_psk_key"))
35         tls_opts->tls_psk_key = val;
36     else
37         return 1;
38     return 0;
39 }
40 
atobv(char const * arg,int def)41 int atobv(char const *arg, int def)
42 {
43     if (!arg)
44         return def;
45     if (!strcasecmp(arg, "true") || !strcasecmp(arg, "yes") || !strcasecmp(arg, "on") || !strcasecmp(arg, "enable"))
46         return 1;
47     return atoi(arg);
48 }
49 
atoiv(char const * arg,int def)50 int atoiv(char const *arg, int def)
51 {
52     if (!arg)
53         return def;
54     char *endptr;
55     int val = strtol(arg, &endptr, 10);
56     if (arg == endptr)
57         return def;
58     return val;
59 }
60 
arg_param(char const * arg)61 char *arg_param(char const *arg)
62 {
63     if (!arg)
64         return NULL;
65     char *p = strchr(arg, ':');
66     char *c = strchr(arg, ',');
67     if (p && (!c || p < c))
68         return ++p;
69     else if (c)
70         return c;
71     else
72         return p;
73 }
74 
arg_float(char const * str,char const * error_hint)75 double arg_float(char const *str, char const *error_hint)
76 {
77     if (!str) {
78         fprintf(stderr, "%smissing number argument\n", error_hint);
79         exit(1);
80     }
81 
82     if (!*str) {
83         fprintf(stderr, "%sempty number argument\n", error_hint);
84         exit(1);
85     }
86 
87     // allow whitespace and equals char
88     while (*str == ' ' || *str == '=')
89         ++str;
90 
91     char *endptr;
92     double val = strtod(str, &endptr);
93 
94     if (str == endptr) {
95         fprintf(stderr, "%sinvalid number argument (%s)\n", error_hint, str);
96         exit(1);
97     }
98 
99     return val;
100 }
101 
hostport_param(char * param,char ** host,char ** port)102 char *hostport_param(char *param, char **host, char **port)
103 {
104     if (param && *param) {
105         if (param[0] == '/' && param[1] == '/') {
106             param += 2;
107         }
108         if (*param != ':' && *param != ',') {
109             *host = param;
110             if (*param == '[') {
111                 (*host)++;
112                 param = strchr(param, ']');
113                 if (param) {
114                     *param++ = '\0';
115                 }
116                 else {
117                     fprintf(stderr, "Malformed Ipv6 address!\n");
118                     exit(1);
119                 }
120             }
121         }
122         char *colon = strchr(param, ':');
123         char *comma = strchr(param, ',');
124         if (colon && (!comma || colon < comma)) {
125             *colon++ = '\0';
126             *port    = colon;
127         }
128         if (comma) {
129             *comma++ = '\0';
130             return comma;
131         }
132     }
133     return NULL;
134 }
135 
atouint32_metric(char const * str,char const * error_hint)136 uint32_t atouint32_metric(char const *str, char const *error_hint)
137 {
138     if (!str) {
139         fprintf(stderr, "%smissing number argument\n", error_hint);
140         exit(1);
141     }
142 
143     if (!*str) {
144         fprintf(stderr, "%sempty number argument\n", error_hint);
145         exit(1);
146     }
147 
148     char *endptr;
149     double val = strtod(str, &endptr);
150 
151     if (str == endptr) {
152         fprintf(stderr, "%sinvalid number argument (%s)\n", error_hint, str);
153         exit(1);
154     }
155 
156     if (val < 0.0) {
157         fprintf(stderr, "%snon-negative number argument expected (%f)\n", error_hint, val);
158         exit(1);
159     }
160 
161     // allow whitespace before suffix
162     while (*endptr == ' ' || *endptr == '\t')
163         ++endptr;
164 
165     switch (*endptr) {
166         case '\0':
167             break;
168         case 'k':
169         case 'K':
170             val *= 1e3;
171             break;
172         case 'M':
173         case 'm':
174             val *= 1e6;
175             break;
176         case 'G':
177         case 'g':
178             val *= 1e9;
179             break;
180         default:
181             fprintf(stderr, "%sunknown number suffix (%s)\n", error_hint, endptr);
182             exit(1);
183     }
184 
185     if (val > UINT32_MAX) {
186         fprintf(stderr, "%snumber argument too big (%f)\n", error_hint, val);
187         exit(1);
188     }
189 
190     val += 1e-5; // rounding (e.g. 4123456789.99999)
191     if (val - (uint32_t)val > 2e-5) {
192         fprintf(stderr, "%sdecimal fraction (%f) did you forget k, M, or G suffix?\n", error_hint, val - (uint32_t)val);
193     }
194 
195     return (uint32_t)val;
196 }
197 
atoi_time(char const * str,char const * error_hint)198 int atoi_time(char const *str, char const *error_hint)
199 {
200     if (!str) {
201         fprintf(stderr, "%smissing time argument\n", error_hint);
202         exit(1);
203     }
204 
205     if (!*str) {
206         fprintf(stderr, "%sempty time argument\n", error_hint);
207         exit(1);
208     }
209 
210     char *endptr    = NULL;
211     double val      = 0.0;
212     unsigned colons = 0;
213 
214     do {
215         double num = strtod(str, &endptr);
216 
217         if (!endptr || str == endptr) {
218             fprintf(stderr, "%sinvalid time argument (%s)\n", error_hint, str);
219             exit(1);
220         }
221 
222         // allow whitespace before suffix
223         while (*endptr == ' ' || *endptr == '\t')
224             ++endptr;
225 
226         switch (*endptr) {
227         case '\0':
228             if (colons == 0) {
229                 // assume seconds
230                 val += num;
231                 break;
232             }
233             // intentional fallthrough
234 #if defined(__GNUC__) || defined(__clang__)
235 #if __has_attribute(fallthrough)
236             __attribute__((fallthrough));
237 #endif
238 #endif
239         case ':':
240             ++colons;
241             if (colons == 1)
242                 val += num * 60 * 60;
243             else if (colons == 2)
244                 val += num * 60;
245             else if (colons == 3)
246                 val += num;
247             else {
248                 fprintf(stderr, "%stoo many colons (use HH:MM[:SS]))\n", error_hint);
249                 exit(1);
250             }
251             if (*endptr)
252                 ++endptr;
253             break;
254         case 's':
255         case 'S':
256             val += num;
257             ++endptr;
258             break;
259         case 'm':
260         case 'M':
261             val += num * 60;
262             ++endptr;
263             break;
264         case 'h':
265         case 'H':
266             val += num * 60 * 60;
267             ++endptr;
268             break;
269         case 'd':
270         case 'D':
271             val += num * 60 * 60 * 24;
272             ++endptr;
273             break;
274         default:
275             fprintf(stderr, "%sunknown time suffix (%s)\n", error_hint, endptr);
276             exit(1);
277         }
278 
279         // chew up any remaining whitespace
280         while (*endptr == ' ' || *endptr == '\t')
281             ++endptr;
282         str = endptr;
283 
284     } while (*endptr);
285 
286     if (val > INT_MAX || val < INT_MIN) {
287         fprintf(stderr, "%stime argument too big (%f)\n", error_hint, val);
288         exit(1);
289     }
290 
291     if (val < 0) {
292         val -= 1e-5; // rounding (e.g. -4123456789.99999)
293     }
294     else {
295         val += 1e-5; // rounding (e.g. 4123456789.99999)
296     }
297     if (val - (int)(val) > 2e-5) {
298         fprintf(stderr, "%sdecimal fraction (%f) did you forget m, or h suffix?\n", error_hint, val - (uint32_t)val);
299     }
300 
301     return (int)val;
302 }
303 
asepc(char ** stringp,char delim)304 char *asepc(char **stringp, char delim)
305 {
306     if (!stringp || !*stringp) return NULL;
307     char *s = strchr(*stringp, delim);
308     if (s) *s++ = '\0';
309     char *p = *stringp;
310     *stringp = s;
311     return p;
312 }
313 
achrb(char * s,int c,int b)314 static char *achrb(char *s, int c, int b)
315 {
316     for (; s && *s && *s != b; ++s)
317         if (*s == c) return (char *)s;
318     return NULL;
319 }
320 
asepcb(char ** stringp,char delim,char stop)321 char *asepcb(char **stringp, char delim, char stop)
322 {
323     if (!stringp || !*stringp) return NULL;
324     char *s = achrb(*stringp, delim, stop);
325     if (s) *s++ = '\0';
326     char *p = *stringp;
327     *stringp = s;
328     return p;
329 }
330 
kwargs_match(char const * s,char const * key,char const ** val)331 int kwargs_match(char const *s, char const *key, char const **val)
332 {
333     size_t len = strlen(key);
334     // check prefix match
335     if (strncmp(s, key, len)) {
336         return 0; // no match
337     }
338     s += len;
339     // skip whitespace after match
340     while (*s == ' ' || *s == '\t')
341         ++s;
342     if (*s == '\0' || * s == ',') {
343         if (val)
344             *val = NULL;
345         return 1; // match with no arg
346     }
347     if (*s == '=') {
348         if (val)
349             *val = s + 1;
350         return 1; // match with arg
351     }
352     return 0; // no exact match
353 }
354 
kwargs_skip(char const * s)355 char const *kwargs_skip(char const *s)
356 {
357     // skip to the next comma if possible
358     while (s && *s && *s != ',')
359         ++s;
360     // skip comma and whitespace if possible
361     while (s && *s && (*s == ',' || *s == ' ' || *s == '\t'))
362         ++s;
363     return s;
364 }
365 
getkwargs(char ** s,char ** key,char ** val)366 char *getkwargs(char **s, char **key, char **val)
367 {
368     char *v = asepc(s, ',');
369     char *k = asepc(&v, '=');
370     if (key) *key = k;
371     if (val) *val = v;
372     return k;
373 }
374 
trim_ws(char * str)375 char *trim_ws(char *str)
376 {
377     if (!str || !*str)
378         return str;
379     while (*str == ' ' || *str == '\t' || *str == '\r' || *str == '\n')
380         ++str;
381     char *e = str; // end pointer (last non ws)
382     char *p = str; // scanning pointer
383     while (*p) {
384         while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')
385             ++p;
386         if (*p)
387             e = p++;
388     }
389     *++e = '\0';
390     return str;
391 }
392 
remove_ws(char * str)393 char *remove_ws(char *str)
394 {
395     if (!str)
396         return str;
397     char *d = str; // dst pointer
398     char *s = str; // src pointer
399     while (*s) {
400         while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n')
401             ++s;
402         if (*s)
403             *d++ = *s++;
404     }
405     *d++ = '\0';
406     return str;
407 }
408 
409 // Unit testing
410 #ifdef _TEST
411 #define ASSERT_EQUALS(a,b)                                  \
412     do {                                                    \
413         if ((a) == (b))                                     \
414             ++passed;                                       \
415         else {                                              \
416             ++failed;                                       \
417             fprintf(stderr, "FAIL: %d <> %d\n", (a), (b));  \
418         }                                                   \
419     } while (0)
420 
main(void)421 int main(void)
422 {
423     unsigned passed = 0;
424     unsigned failed = 0;
425 
426     fprintf(stderr, "optparse:: atouint32_metric\n");
427     ASSERT_EQUALS(atouint32_metric("0", ""), 0);
428     ASSERT_EQUALS(atouint32_metric("1", ""), 1);
429     ASSERT_EQUALS(atouint32_metric("0.0", ""), 0);
430     ASSERT_EQUALS(atouint32_metric("1.0", ""), 1);
431     ASSERT_EQUALS(atouint32_metric("1.024k", ""), 1024);
432     ASSERT_EQUALS(atouint32_metric("433.92M", ""), 433920000);
433     ASSERT_EQUALS(atouint32_metric("433.94M", ""), 433940000);
434     ASSERT_EQUALS(atouint32_metric(" +1 G ", ""), 1000000000);
435 
436     fprintf(stderr, "optparse:: atoi_time\n");
437     ASSERT_EQUALS(atoi_time("0", ""), 0);
438     ASSERT_EQUALS(atoi_time("1", ""), 1);
439     ASSERT_EQUALS(atoi_time("0.0", ""), 0);
440     ASSERT_EQUALS(atoi_time("1.0", ""), 1);
441     ASSERT_EQUALS(atoi_time("1s", ""), 1);
442     ASSERT_EQUALS(atoi_time("2d", ""), 2 * 60 * 60 * 24);
443     ASSERT_EQUALS(atoi_time("2h", ""), 2 * 60 * 60);
444     ASSERT_EQUALS(atoi_time("2m", ""), 2 * 60);
445     ASSERT_EQUALS(atoi_time("2s", ""), 2);
446     ASSERT_EQUALS(atoi_time("2D", ""), 2 * 60 * 60 * 24);
447     ASSERT_EQUALS(atoi_time("2H", ""), 2 * 60 * 60);
448     ASSERT_EQUALS(atoi_time("2M", ""), 2 * 60);
449     ASSERT_EQUALS(atoi_time("2S", ""), 2);
450     ASSERT_EQUALS(atoi_time("2h3m4s", ""), 2 * 60 * 60 + 3 * 60 + 4);
451     ASSERT_EQUALS(atoi_time("2h 3m 4s", ""), 2 * 60 * 60 + 3 * 60 + 4);
452     ASSERT_EQUALS(atoi_time("2h3h 3m 4s 5", ""), 5 * 60 * 60 + 3 * 60 + 9);
453     ASSERT_EQUALS(atoi_time(" 2m ", ""), 2 * 60);
454     ASSERT_EQUALS(atoi_time("2 m", ""), 2 * 60);
455     ASSERT_EQUALS(atoi_time("  2  m  ", ""), 2 * 60);
456     ASSERT_EQUALS(atoi_time("-1m", ""), -60);
457     ASSERT_EQUALS(atoi_time("1h-15m", ""), 45 * 60);
458 
459     ASSERT_EQUALS(atoi_time("2:3", ""), 2 * 60 * 60 + 3 * 60);
460     ASSERT_EQUALS(atoi_time("2:3:4", ""), 2 * 60 * 60 + 3 * 60 + 4);
461     ASSERT_EQUALS(atoi_time("02:03", ""), 2 * 60 * 60 + 3 * 60);
462     ASSERT_EQUALS(atoi_time("02:03:04", ""), 2 * 60 * 60 + 3 * 60 + 4);
463     ASSERT_EQUALS(atoi_time(" 2 : 3 ", ""), 2 * 60 * 60 + 3 * 60);
464     ASSERT_EQUALS(atoi_time(" 2 : 3 : 4 ", ""), 2 * 60 * 60 + 3 * 60 + 4);
465 
466     fprintf(stderr, "optparse:: test (%u/%u) passed, (%u) failed.\n", passed, passed + failed, failed);
467 
468     return failed;
469 }
470 #endif /* _TEST */
471