LCOV - code coverage report
Current view: top level - src/libs/globbing - globbing.c (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 68 68 100.0 %
Date: 2022-05-21 16:19:22 Functions: 4 4 100.0 %

          Line data    Source code
       1             : /**
       2             :  * @file
       3             :  *
       4             :  * @brief Library for performing globbing on keynames.
       5             :  *
       6             :  * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
       7             :  */
       8             : 
       9             : #include <kdb.h>
      10             : #include <kdbease.h>
      11             : #include <kdbglobbing.h>
      12             : #include <kdbhelper.h>
      13             : 
      14             : #include <ctype.h>
      15             : #include <fnmatch.h>
      16             : #include <stdlib.h>
      17             : #include <string.h>
      18             : 
      19             : static size_t strcnt (const char * s, char c)
      20             : {
      21        7975 :         size_t count = 0;
      22       47129 :         while ((s = strchr (s + 1, c)) != NULL)
      23             :         {
      24       39154 :                 count++;
      25             :         }
      26        7975 :         return count;
      27             : }
      28             : 
      29        3587 : static char * elektraToFnmatchGlob (char * pattern)
      30             : {
      31        3587 :         char * ptr = pattern;
      32       23834 :         while ((ptr = strchr (ptr, '/')) != NULL)
      33             :         {
      34       20247 :                 ++ptr;
      35       20247 :                 if ((*(ptr + 1) == '/' || *(ptr + 1) == '\0') && (*ptr == '#' || *ptr == '_'))
      36             :                 {
      37         216 :                         *ptr = '*'; // replace /#/ and /_/ with /*/
      38             :                 }
      39             :         }
      40        3587 :         return pattern;
      41             : }
      42             : 
      43             : /**
      44             :  * @brief checks whether the given string is a valid elektra array item name
      45             :  *
      46             :  * Valid array items consist of a '#' followed by <code>n</code> underscores ('_'),
      47             :  * followed by <code>n+1</code> digits ('0'-'9'). Additionally the digits describe a
      48             :  * valid 32-bit Integer (i.e. <code>0 <= x < 2^32</code>).
      49             :  *
      50             :  * @param name the string to check
      51             :  * @retval true if @p name is a valid array item
      52             :  * @retval false otherwise
      53             :  */
      54             : static bool isArrayName (const char * name)
      55             : {
      56         122 :         return elektraArrayValidateBaseNameString (name) > 0;
      57             : }
      58             : 
      59         645 : static int checkElektraExtensions (const char * name, const char * pattern)
      60             : {
      61         645 :         const char * ptr = pattern;
      62         645 :         const char * keyPtr = name;
      63        3135 :         while ((ptr = strchr (ptr + 1, '/')) != NULL && (keyPtr = strchr (keyPtr + 1, '/')) != NULL)
      64             :         {
      65        2538 :                 if (*(ptr + 2) == '/' || *(ptr + 2) == '\0')
      66             :                 {
      67         239 :                         if (*(ptr + 1) == '#' && !isArrayName (keyPtr + 1))
      68             :                         {
      69             :                                 return ELEKTRA_GLOB_NOMATCH;
      70             :                         }
      71             : 
      72         194 :                         if (*(ptr + 1) == '_' && isArrayName (keyPtr + 1))
      73             :                         {
      74             :                                 return ELEKTRA_GLOB_NOMATCH;
      75             :                         }
      76             :                 }
      77             :         }
      78             : 
      79             :         return 0;
      80             : }
      81             : 
      82             : /**
      83             :  * @brief checks whether a given Key matches a given globbing pattern
      84             :  *
      85             :  * WARNING: this method will not work correctly, if key parts contain embedded (escaped) slashes.
      86             :  *
      87             :  * The globbing patterns for this function are a superset of those from glob(7)
      88             :  * used with the FNM_PATHNAME flag:
      89             :  * <ul>
      90             :  *      <li> '*' matches any series of characters other than '/'</li>
      91             :  *      <li> '?' matches any single character except '/' </li>
      92             :  *      <li> '#', when used as "/#/" (or "/#" at the end of @p pattern), matches a valid array item </li>
      93             :  *      <li> '_', when used as "/_/"(or "/_" at the end of @p pattern), matches a key part that is <b>not</b> a valid array item </li>
      94             :  *      <li>
      95             :  *              everything between '[' and ']' is treated as a character class, matching exactly one of the
      96             :  *              given characters (see glob(7) for details)
      97             :  *      </li>
      98             :  *      <li> if the pattern ends with "/__", matching key names may contain arbitrary suffixes </li>
      99             :  * </ul>
     100             :  *
     101             :  * @note '*' cannot match an empty key name part. This also means patterns like "something&#47;*" will
     102             :  * not match the key "something". This is because each slash ('/') in the pattern has to correspond to
     103             :  * a slash in the canonical key name, which neither end in a slash nor contain multiple slashes in sequence.
     104             :  *
     105             :  * @note use "[_]", "[#]", "[*]", "[?]" and "[[]" to match the literal characters '_', '#', '*', '?' and '['.
     106             :  * Using backslash ('\') for escaping is not supported.
     107             :  *
     108             :  * @param key the Key to match against the globbing pattern
     109             :  * @param pattern the globbing pattern used
     110             :  * @retval 0 if @p key is not NULL, @p pattern is not NULL and @p pattern matches @p key
     111             :  * @retval ELEKTRA_GLOB_NOMATCH otherwise
     112             :  *
     113             :  * @see isArrayName(), for info on valid array items
     114             :  */
     115        7978 : int elektraKeyGlob (const Key * key, const char * pattern)
     116             : {
     117        7978 :         if (key == NULL || pattern == NULL)
     118             :         {
     119             :                 return ELEKTRA_GLOB_NOMATCH;
     120             :         }
     121             : 
     122        7975 :         size_t nameSize = (size_t) keyGetNameSize (key);
     123        7975 :         char * name = elektraMalloc (nameSize);
     124        7975 :         keyGetName (key, name, nameSize);
     125             : 
     126        7975 :         size_t len = strlen (pattern);
     127        7975 :         bool prefixMode = len >= 2 && elektraStrCmp (pattern + len - 3, "/__") == 0;
     128             : 
     129        7975 :         size_t patternSlashes = strcnt (pattern, '/');
     130             : 
     131        7975 :         if (prefixMode)
     132             :         {
     133             :                 // last slash in pattern is treated specially
     134          18 :                 patternSlashes--;
     135             :         }
     136             : 
     137        7975 :         char * patternEnd = name;
     138       43933 :         for (size_t i = 0; i < patternSlashes; ++i)
     139             :         {
     140       38266 :                 patternEnd = strchr (patternEnd + 1, '/');
     141             : 
     142       38266 :                 if (patternEnd == NULL)
     143             :                 {
     144             :                         // more slashes in pattern, cannot match
     145        2308 :                         free (name);
     146        2308 :                         return ELEKTRA_GLOB_NOMATCH;
     147             :                 }
     148             :         }
     149             : 
     150        5667 :         if (prefixMode)
     151             :         {
     152             :                 // mark end of relevant part
     153          18 :                 char * next = strchr (patternEnd + 1, '/');
     154          18 :                 if (next != NULL)
     155             :                 {
     156          15 :                         *(next) = '\0';
     157             :                 }
     158             :         }
     159        5649 :         else if (strchr (patternEnd + 1, '/') != NULL)
     160             :         {
     161             :                 // more slashes in name, cannot match
     162        2080 :                 free (name);
     163        2080 :                 return ELEKTRA_GLOB_NOMATCH;
     164             :         }
     165             : 
     166        3587 :         char * fnmPattern = elektraToFnmatchGlob (elektraStrDup (pattern));
     167        3587 :         if (prefixMode)
     168             :         {
     169             :                 // remove __ from end
     170          18 :                 *(fnmPattern + len - 3) = '\0';
     171             :         }
     172             : 
     173        3587 :         int rc = fnmatch (fnmPattern, name, FNM_PATHNAME | FNM_NOESCAPE);
     174        3587 :         elektraFree (fnmPattern);
     175             : 
     176        3587 :         if (rc == FNM_NOMATCH)
     177             :         {
     178        2942 :                 free (name);
     179        2942 :                 return ELEKTRA_GLOB_NOMATCH;
     180             :         }
     181             : 
     182         645 :         rc = checkElektraExtensions (name, pattern);
     183             : 
     184         645 :         free (name);
     185             : 
     186         645 :         return rc;
     187             : }
     188             : 
     189             : /**
     190             :  * @brief filters a given KeySet by applying a globbing pattern
     191             :  *
     192             :  * @param result the KeySet to which the matching keys should be appended
     193             :  * @param input the KeySet whose keys should be filtered
     194             :  * @param pattern the globbing pattern used
     195             :  * @return the number of Keys appended to result or -1,
     196             :  *         if @p result, @p input or @p pattern are NULL
     197             :  *
     198             :  * @see elektraKeyGlob(), for explanation of globbing pattern
     199             :  */
     200           3 : int elektraKsGlob (KeySet * result, KeySet * input, const char * pattern)
     201             : {
     202           3 :         if (!result) return ELEKTRA_GLOB_NOMATCH;
     203             : 
     204           3 :         if (!input) return ELEKTRA_GLOB_NOMATCH;
     205             : 
     206           3 :         if (!pattern) return ELEKTRA_GLOB_NOMATCH;
     207             : 
     208           3 :         int ret = 0;
     209           3 :         Key * current;
     210             : 
     211           3 :         elektraCursor cursor = ksGetCursor (input);
     212           3 :         ksRewind (input);
     213          18 :         while ((current = ksNext (input)) != 0)
     214             :         {
     215          12 :                 int rc = elektraKeyGlob (current, pattern);
     216          12 :                 if (rc == 0)
     217             :                 {
     218           6 :                         ++ret;
     219           6 :                         ksAppendKey (result, keyDup (current, KEY_CP_ALL));
     220             :                 }
     221             :         }
     222           3 :         ksSetCursor (input, cursor);
     223           3 :         return ret;
     224             : }

Generated by: LCOV version 1.13