Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief Source for date plugin
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : *
8 : */
9 :
10 : #define _XOPEN_SOURCE
11 : #include "date.h"
12 : #include <ctype.h>
13 : #include <kdberrors.h>
14 : #include <kdbhelper.h>
15 : #include <langinfo.h>
16 : #include <locale.h>
17 : #include <stdio.h>
18 : #include <stdlib.h>
19 : #include <string.h>
20 : #include <strings.h>
21 : #include <time.h>
22 :
23 :
24 : //
25 : // use an ISO format string table to validate the key value
26 : //
27 :
28 10 : static int individualIsoStringValidation (const char * date, const RepStruct * formats, ISOType opts)
29 : {
30 : struct tm tm;
31 10 : memset (&tm, 0, sizeof (struct tm));
32 50 : for (int i = 0; formats[i].rep != END; ++i)
33 : {
34 48 : if (formats[i].rep & (opts & REPMASK))
35 : {
36 :
37 48 : if (opts & BASIC)
38 : {
39 10 : char * ptr = strptime (date, formats[i].basic, &tm);
40 10 : if (ptr && !*ptr) return 1;
41 : }
42 46 : if ((opts & EXTD) && formats[i].extended)
43 : {
44 28 : char * ptr = strptime (date, formats[i].extended, &tm);
45 28 : if (ptr && !*ptr) return 1;
46 : }
47 : }
48 : }
49 : return -1;
50 : }
51 :
52 : //
53 : // tokenize ISO format string
54 : //
55 :
56 18 : static ISOType ISOStrToToken (const char * fmtString)
57 : {
58 18 : ISOType type = NA;
59 18 : if (!strncasecmp (fmtString, "datetime", sizeof ("datetime") - 1))
60 : type = DATETIME;
61 10 : else if (!strncasecmp (fmtString, "date", sizeof ("date") - 1))
62 : type = DATE;
63 10 : else if (!strncasecmp (fmtString, "calendardate", sizeof ("calendardate") - 1))
64 : type = CALENDAR;
65 10 : else if (!strncasecmp (fmtString, "ordinaldate", sizeof ("ordinaldate") - 1))
66 : type = ORDINAL;
67 10 : else if (!strncasecmp (fmtString, "weekdate", sizeof ("weekdate") - 1))
68 : type = WEEK;
69 8 : else if (!strncasecmp (fmtString, "timeofday", sizeof ("timeofday") - 1))
70 : type = TIMEOFDAY;
71 4 : else if (!strncasecmp (fmtString, "time", sizeof ("time") - 1))
72 : type = TIME;
73 4 : else if (!strncasecmp (fmtString, "utc", sizeof ("utc") - 1))
74 4 : type = UTC;
75 :
76 18 : if (type == NA) return type;
77 :
78 18 : const char * repPtr = strchr (fmtString, ' ');
79 18 : if (repPtr == NULL)
80 : {
81 2 : type |= ((CMPLT) | (RDCD) | (TRCT) | (BASIC) | (EXTD));
82 2 : return type;
83 : }
84 : else
85 : {
86 16 : ++repPtr;
87 : }
88 16 : if (!strncasecmp (repPtr, "complete+reduced+truncated", sizeof ("complete+reduced+truncated") - 1))
89 0 : type |= ((CMPLT) | (RDCD) | (TRCT));
90 16 : else if (!strncasecmp (repPtr, "complete+reduced", sizeof ("complete+reduced") - 1))
91 0 : type |= ((CMPLT) | (RDCD));
92 16 : else if (!strncasecmp (repPtr, "reduced+truncated", sizeof ("reduced+truncated") - 1))
93 0 : type |= ((RDCD) | (TRCT));
94 16 : else if (!strncasecmp (repPtr, "complete", sizeof ("complete") - 1))
95 4 : type |= CMPLT;
96 12 : else if (!strncasecmp (repPtr, "reduced", sizeof ("reduced") - 1))
97 0 : type |= RDCD;
98 12 : else if (!strncasecmp (repPtr, "truncated", sizeof ("truncated") - 1))
99 4 : type |= TRCT;
100 :
101 16 : const char * repPtr2 = strchr (repPtr, ' ');
102 16 : if (!repPtr2 && (((type & REPMASK) & ~TYPEMASK) == 0))
103 : {
104 8 : type |= ((CMPLT) | (RDCD) | (TRCT));
105 8 : if (!strncasecmp (repPtr, "basic", sizeof ("basic") - 1))
106 2 : type |= BASIC;
107 6 : else if (!strncasecmp (repPtr, "extended", sizeof ("extended") - 1))
108 6 : type |= EXTD;
109 : }
110 8 : else if (!repPtr2)
111 : {
112 6 : type |= ((BASIC) | (EXTD));
113 : }
114 : else if (repPtr2)
115 : {
116 2 : if (!strncasecmp (repPtr2 + 1, "basic", sizeof ("basic") - 1))
117 0 : type |= BASIC;
118 2 : else if (!strncasecmp (repPtr2 + 1, "extended", sizeof ("extended") - 1))
119 0 : type |= EXTD;
120 : }
121 16 : repPtr = strrchr (fmtString, ' ');
122 16 : if (repPtr)
123 : {
124 16 : ++repPtr;
125 16 : if (!strcasecmp (repPtr, "noT"))
126 : {
127 2 : if (((type & REPMASK) & ~TYPEMASK) == 0)
128 : {
129 0 : type |= ((CMPLT) | (RDCD) | (TRCT));
130 : }
131 2 : if ((type & ~REPMASK) == 0)
132 : {
133 2 : type |= ((BASIC) | (EXTD));
134 : }
135 2 : type |= OMITT;
136 : }
137 : }
138 : return type;
139 : }
140 :
141 : //
142 : // return table matching ISOType
143 : //
144 :
145 : static const RepStruct * typeToTable (ISOType type)
146 : {
147 72 : ISOType typeStripped = (type & TYPEMASK);
148 72 : switch (typeStripped)
149 : {
150 : case CALENDAR:
151 : return iso8601calendardate;
152 : break;
153 : case ORDINAL:
154 : return iso8601ordinaldate;
155 : break;
156 : case WEEK:
157 : return iso8601weekdate;
158 : break;
159 : case TIMEOFDAY:
160 : return iso8601timeofday;
161 : break;
162 : case UTC:
163 : return iso8601UTC;
164 : break;
165 : default:
166 : return NULL;
167 : break;
168 : }
169 : }
170 :
171 : static int countLeadingHyphen (const char * date)
172 : {
173 554 : char * ptr = (char *) date;
174 554 : int count = 0;
175 664 : while (*ptr && ((*ptr == ' ') || (*ptr == '-')))
176 : {
177 110 : if (*ptr == '-') ++count;
178 110 : ++ptr;
179 : }
180 : return count;
181 : }
182 :
183 : //
184 : // create basic and extended date and time combination and
185 : // try to validate the key value
186 : //
187 :
188 402 : static int combineAndValidateISO (const char * toValidate, const RepStruct * date, const RepStruct * time, ISOType opts)
189 : {
190 402 : ssize_t basicLen = strlen (date->basic) + strlen (time->basic) + 2;
191 402 : ssize_t extendedLen = 0;
192 402 : if (date->extended && time->extended) extendedLen = strlen (date->extended) + strlen (time->extended) + 2;
193 402 : char * buffer = elektraCalloc (basicLen);
194 402 : unsigned short noT = 0;
195 402 : if (!strchr (toValidate, 'T')) noT = 1;
196 :
197 : // ISO 8601 5.4.2 Representations other than complete, rule b.
198 : // when truncation occurs in the date component of a combined date and time
199 : // expression, it is not necessary to replace the omitted higher order components
200 : // with the hypen [-];
201 :
202 402 : int toValidateHyphen = countLeadingHyphen (toValidate);
203 402 : int toDropHyphen = 0;
204 402 : if (toValidateHyphen == 0)
205 : {
206 372 : if (opts & CMPLT) toDropHyphen = countLeadingHyphen (date->basic);
207 : }
208 402 : if (opts & BASIC)
209 : {
210 402 : if (!noT)
211 396 : snprintf (buffer, basicLen, "%sT%s", (date->basic) + toDropHyphen, time->basic);
212 : else
213 6 : snprintf (buffer, basicLen, "%s%s", (date->basic) + toDropHyphen, time->basic);
214 : struct tm tm;
215 402 : memset (&tm, 0, sizeof (struct tm));
216 402 : char * ptr = strptime (toValidate, buffer, &tm);
217 402 : elektraFree (buffer);
218 402 : if (ptr && !(*ptr)) return 1;
219 : }
220 402 : if (opts & EXTD)
221 : {
222 652 : if (!extendedLen) return -1;
223 158 : buffer = elektraMalloc (extendedLen);
224 158 : if (toValidateHyphen == 0)
225 140 : toDropHyphen = countLeadingHyphen (date->extended);
226 : else
227 : toDropHyphen = 0;
228 158 : if (!noT)
229 152 : snprintf (buffer, extendedLen, "%sT%s", (date->extended) + toDropHyphen, time->extended);
230 : else
231 6 : snprintf (buffer, extendedLen, "%s%s", (date->extended) + toDropHyphen, time->extended);
232 : struct tm tm;
233 158 : memset (&tm, 0, sizeof (struct tm));
234 158 : char * ptr = strptime (toValidate, buffer, &tm);
235 158 : elektraFree (buffer);
236 158 : if (ptr && !(*ptr)) return 1;
237 : }
238 : return -1;
239 : }
240 :
241 : //
242 : // loop through iso8601 table containing rules on valid combinations
243 : // and pass them to combineAndValidateISO
244 : //
245 :
246 8 : static int combinedIsoStringValidation (const char * toValidate, ISOType opts)
247 : {
248 : const CRepStruct * formats;
249 8 : ISOType strippedOpts = ((opts & REPMASK) & ~OMITT);
250 8 : switch (strippedOpts)
251 : {
252 : case CMPLT:
253 : formats = iso8601CombinedComplete;
254 : break;
255 : case TRCT:
256 4 : formats = iso8601CombinedOther;
257 4 : break;
258 : default:
259 : return -1;
260 : }
261 38 : for (int i = 0; formats[i].dateRep != END; ++i)
262 : {
263 36 : const CRepStruct * e = &formats[i];
264 36 : const REP dateRep = e->dateRep;
265 36 : const REP timeRep = e->timeRep;
266 36 : if (!(opts & dateRep))
267 : {
268 0 : continue;
269 : }
270 72 : const RepStruct * date = typeToTable (e->date);
271 72 : const RepStruct * time = typeToTable (e->time);
272 36 : if (!date || !time) continue;
273 246 : for (int j = 0; date[j].rep != END; ++j)
274 : {
275 252 : if (date[j].rep != dateRep) continue;
276 1360 : for (int k = 0; time[k].rep != END; ++k)
277 : {
278 1366 : if (time[k].rep != timeRep) continue;
279 402 : int rc = combineAndValidateISO (toValidate, &(date[j]), &(time[k]), opts);
280 402 : if (rc == 1) return 1;
281 : }
282 : }
283 : }
284 : return -1;
285 : }
286 :
287 18 : static int isoStringValidation (const char * date, const char * fmt)
288 : {
289 18 : ISOType isoToken = NA;
290 18 : if (fmt)
291 : {
292 18 : isoToken = ISOStrToToken (fmt);
293 18 : ISOType strippedToken = (isoToken & TYPEMASK);
294 18 : ISOType strippedOpts = (isoToken & ~TYPEMASK);
295 18 : if (strippedToken == NA) return 0;
296 18 : int rc = -1;
297 18 : switch (strippedToken)
298 : {
299 : case CALENDAR:
300 0 : rc = individualIsoStringValidation (date, iso8601calendardate, strippedOpts);
301 0 : break;
302 : case ORDINAL:
303 0 : rc = individualIsoStringValidation (date, iso8601ordinaldate, strippedOpts);
304 0 : break;
305 : case WEEK:
306 2 : rc = individualIsoStringValidation (date, iso8601weekdate, strippedOpts);
307 2 : break;
308 : case TIMEOFDAY:
309 4 : rc = individualIsoStringValidation (date, iso8601timeofday, strippedOpts);
310 4 : break;
311 : case UTC:
312 4 : rc = individualIsoStringValidation (date, iso8601UTC, strippedOpts);
313 4 : break;
314 : case DATE:
315 0 : rc = individualIsoStringValidation (date, iso8601calendardate, strippedOpts);
316 0 : if (rc == 1) break;
317 0 : rc = individualIsoStringValidation (date, iso8601ordinaldate, strippedOpts);
318 0 : if (rc == 1) break;
319 0 : rc = individualIsoStringValidation (date, iso8601weekdate, strippedOpts);
320 0 : break;
321 : case TIME:
322 0 : rc = individualIsoStringValidation (date, iso8601timeofday, strippedOpts);
323 0 : if (rc == 1) break;
324 0 : rc = individualIsoStringValidation (date, iso8601UTC, strippedOpts);
325 0 : break;
326 : case DATETIME:
327 8 : if (!strchr (date, 'T'))
328 : {
329 2 : if (!(strippedOpts & OMITT)) return -1;
330 : }
331 8 : rc = combinedIsoStringValidation (date, strippedOpts);
332 8 : break;
333 : default:
334 : break;
335 : }
336 : return rc;
337 : }
338 : else
339 : {
340 0 : int rc = combinedIsoStringValidation (date, (DATETIME | CMPLT));
341 0 : if (rc != 1) rc = combinedIsoStringValidation (date, (DATETIME | TRCT));
342 : return rc;
343 : }
344 : return -1;
345 : }
346 :
347 :
348 : //
349 : // validate key value using POSIX (strptime) format string
350 : //
351 :
352 6 : static int formatStringValidation (const char * date, const char * fmt)
353 : {
354 6 : if (!fmt) return 0;
355 : struct tm tm;
356 6 : memset (&tm, 0, sizeof (struct tm));
357 6 : char * ptr = strptime (date, fmt, &tm);
358 6 : if (!ptr)
359 : return -1;
360 4 : else if (ptr && !(*ptr))
361 : return 1;
362 : else
363 0 : return -1;
364 : }
365 :
366 : //
367 : // validate key value using supplied RFC2822 format string
368 : // or all possible format strings derived from the specification
369 : //
370 :
371 10 : static int rfc2822StringValidation (const char * date)
372 : {
373 : struct tm tm;
374 10 : memset (&tm, 0, sizeof (struct tm));
375 34 : for (int i = 0; rfc2822strings[i] != NULL; ++i)
376 : {
377 30 : char * ptr = strptime (date, rfc2822strings[i], &tm);
378 30 : if (ptr)
379 : {
380 6 : if (*ptr == '\0') return 1;
381 : }
382 : }
383 : return -1;
384 : }
385 :
386 0 : static int rfc822StringValidation (const char * date)
387 : {
388 : struct tm tm;
389 0 : memset (&tm, 0, sizeof (struct tm));
390 0 : for (int i = 0; rfc2822strings[i] != NULL; ++i)
391 : {
392 0 : char * ptr = strptime (date, rfc822strings[i], &tm);
393 0 : if (ptr)
394 : {
395 0 : if (*ptr == '\0') return 1;
396 : }
397 : }
398 : return -1;
399 : }
400 :
401 34 : static int validateKey (Key * key, Key * parentKey)
402 : {
403 34 : const Key * standard = keyGetMeta (key, "check/date");
404 34 : const Key * formatStringMeta = keyGetMeta (key, "check/date/format");
405 34 : const char * date = keyString (key);
406 34 : int rc = 0;
407 34 : const char * stdString = keyString (standard);
408 34 : const char * formatString = formatStringMeta ? keyString (formatStringMeta) : NULL;
409 34 : if (!strcasecmp (stdString, "POSIX"))
410 : {
411 6 : rc = formatStringValidation (date, formatString);
412 6 : if (rc == -1)
413 : {
414 2 : ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (parentKey, "Date '%s' doesn't match format string %s", date, formatString);
415 2 : rc = 0;
416 : }
417 : }
418 28 : else if (!strcasecmp (stdString, "ISO8601"))
419 : {
420 18 : rc = isoStringValidation (date, formatString);
421 18 : if (rc == -1)
422 : {
423 4 : if (formatString)
424 4 : ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (parentKey, "Date '%s' doesn't match iso specification %s", date,
425 : formatString);
426 : else
427 0 : ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (parentKey, "Date '%s' is not a valid ISO8601 date", date);
428 : rc = 0;
429 : }
430 14 : else if (rc == 0)
431 : {
432 0 : ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (parentKey, "Syntax error in ISO8601 format string '%s'", formatString);
433 : }
434 : }
435 10 : else if (!strcasecmp (stdString, "RFC2822"))
436 : {
437 10 : rc = rfc2822StringValidation (date);
438 10 : if (rc == -1)
439 : {
440 4 : ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (parentKey, "Date '%s' doesn't match rfc2822 specification", date);
441 4 : rc = 0;
442 : }
443 : }
444 0 : else if (!strcasecmp (stdString, "RFC822"))
445 : {
446 0 : rc = rfc822StringValidation (date);
447 0 : if (rc == -1)
448 : {
449 0 : ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (parentKey, "Date '%s' doesn't match format string %s", date, formatString);
450 0 : rc = 0;
451 : }
452 : }
453 :
454 34 : return rc;
455 : }
456 :
457 :
458 54 : int elektraDateGet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned ELEKTRA_UNUSED, Key * parentKey ELEKTRA_UNUSED)
459 : {
460 54 : if (!elektraStrCmp (keyName (parentKey), "system/elektra/modules/date"))
461 : {
462 20 : KeySet * contract =
463 20 : ksNew (30, keyNew ("system/elektra/modules/date", KEY_VALUE, "date plugin waits for your orders", KEY_END),
464 : keyNew ("system/elektra/modules/date/exports", KEY_END),
465 : keyNew ("system/elektra/modules/date/exports/get", KEY_FUNC, elektraDateGet, KEY_END),
466 : keyNew ("system/elektra/modules/date/exports/set", KEY_FUNC, elektraDateSet, KEY_END),
467 : keyNew ("system/elektra/modules/date/exports/validateKey", KEY_FUNC, validateKey, KEY_END),
468 : #include ELEKTRA_README
469 : keyNew ("system/elektra/modules/date/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END), KS_END);
470 20 : ksAppend (returned, contract);
471 20 : ksDel (contract);
472 :
473 20 : return 1; // success
474 : }
475 : // get all keys
476 : Key * cur;
477 : int rc = 1;
478 68 : while ((cur = ksNext (returned)) != NULL)
479 : {
480 34 : const Key * meta = keyGetMeta (cur, "check/date");
481 34 : if (meta)
482 : {
483 34 : int r = validateKey (cur, parentKey);
484 34 : if (r == 0)
485 : {
486 10 : rc = -1;
487 : }
488 : }
489 : }
490 : return rc; // success
491 : }
492 :
493 0 : int elektraDateSet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned ELEKTRA_UNUSED, Key * parentKey ELEKTRA_UNUSED)
494 : {
495 : // set all keys
496 : // this function is optional
497 : Key * cur;
498 0 : int rc = 1;
499 0 : while ((cur = ksNext (returned)) != NULL)
500 : {
501 0 : const Key * meta = keyGetMeta (cur, "check/date");
502 0 : if (meta)
503 : {
504 0 : int r = validateKey (cur, parentKey);
505 0 : if (r == 0)
506 : {
507 0 : rc = -1;
508 : }
509 : }
510 : }
511 0 : return rc; // success
512 : }
513 :
514 54 : Plugin * ELEKTRA_PLUGIN_EXPORT
515 : {
516 : // clang-format off
517 54 : return elektraPluginExport ("date",
518 : ELEKTRA_PLUGIN_GET, &elektraDateGet,
519 : ELEKTRA_PLUGIN_SET, &elektraDateSet,
520 : ELEKTRA_PLUGIN_END);
521 : }
522 :
|