LCOV - code coverage report
Current view: top level - src/plugins/date - date.c (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 181 228 79.4 %
Date: 2019-09-12 12:28:41 Functions: 10 13 76.9 %

          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             : 

Generated by: LCOV version 1.13