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: 2019-09-12 12:28:41 Functions: 5 5 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        4459 : static size_t strcnt (const char * s, char c)
      20             : {
      21        4459 :         size_t count = 0;
      22       28588 :         while ((s = strchr (s + 1, c)) != NULL)
      23             :         {
      24       19670 :                 count++;
      25             :         }
      26        4459 :         return count;
      27             : }
      28             : 
      29        2320 : static char * elektraToFnmatchGlob (char * pattern)
      30             : {
      31        2320 :         char * ptr = pattern;
      32       17034 :         while ((ptr = strchr (ptr, '/')) != NULL)
      33             :         {
      34       12394 :                 ++ptr;
      35       12394 :                 if ((*(ptr + 1) == '/' || *(ptr + 1) == '\0') && (*ptr == '#' || *ptr == '_'))
      36             :                 {
      37         151 :                         *ptr = '*'; // replace /#/ and /_/ with /*/
      38             :                 }
      39             :         }
      40        2320 :         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          81 :         return elektraArrayValidateBaseNameString (name) > 0;
      57             : }
      58             : 
      59         437 : static int checkElektraExtensions (const char * name, const char * pattern)
      60             : {
      61         437 :         const char * ptr = pattern;
      62         437 :         const char * keyPtr = name;
      63        2651 :         while ((ptr = strchr (ptr + 1, '/')) != NULL && (keyPtr = strchr (keyPtr + 1, '/')) != NULL)
      64             :         {
      65        1809 :                 if (*(ptr + 2) == '/' || *(ptr + 2) == '\0')
      66             :                 {
      67         191 :                         if (*(ptr + 1) == '#' && !isArrayName (keyPtr + 1))
      68             :                         {
      69             :                                 return ELEKTRA_GLOB_NOMATCH;
      70             :                         }
      71             : 
      72         174 :                         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        4471 : int elektraKeyGlob (const Key * key, const char * pattern)
     116             : {
     117        4471 :         if (key == NULL || pattern == NULL)
     118             :         {
     119             :                 return ELEKTRA_GLOB_NOMATCH;
     120             :         }
     121             : 
     122        4459 :         size_t nameSize = (size_t) keyGetNameSize (key);
     123        4459 :         char * name = elektraMalloc (nameSize);
     124        4459 :         keyGetName (key, name, nameSize);
     125             : 
     126        4459 :         size_t len = strlen (pattern);
     127        4459 :         bool prefixMode = len >= 2 && elektraStrCmp (pattern + len - 3, "/__") == 0;
     128             : 
     129        4459 :         size_t patternSlashes = strcnt (pattern, '/');
     130             : 
     131        4459 :         if (prefixMode)
     132             :         {
     133             :                 // last slash in pattern is treated specially
     134          12 :                 patternSlashes--;
     135             :         }
     136             : 
     137        4459 :         char * patternEnd = name;
     138       22996 :         for (size_t i = 0; i < patternSlashes; ++i)
     139             :         {
     140       19437 :                 patternEnd = strchr (patternEnd + 1, '/');
     141             : 
     142       19437 :                 if (patternEnd == NULL)
     143             :                 {
     144             :                         // more slashes in pattern, cannot match
     145         900 :                         free (name);
     146         900 :                         return ELEKTRA_GLOB_NOMATCH;
     147             :                 }
     148             :         }
     149             : 
     150        3559 :         if (prefixMode)
     151             :         {
     152             :                 // mark end of relevant part
     153          12 :                 char * next = strchr (patternEnd + 1, '/');
     154          12 :                 if (next != NULL)
     155             :                 {
     156          10 :                         *(next) = '\0';
     157             :                 }
     158             :         }
     159        3547 :         else if (strchr (patternEnd + 1, '/') != NULL)
     160             :         {
     161             :                 // more slashes in name, cannot match
     162        1239 :                 free (name);
     163        1239 :                 return ELEKTRA_GLOB_NOMATCH;
     164             :         }
     165             : 
     166        2320 :         char * fnmPattern = elektraToFnmatchGlob (elektraStrDup (pattern));
     167        2320 :         if (prefixMode)
     168             :         {
     169             :                 // remove __ from end
     170          12 :                 *(fnmPattern + len - 3) = '\0';
     171             :         }
     172             : 
     173        2320 :         int rc = fnmatch (fnmPattern, name, FNM_PATHNAME | FNM_NOESCAPE);
     174        2320 :         elektraFree (fnmPattern);
     175             : 
     176        2320 :         if (rc == FNM_NOMATCH)
     177             :         {
     178        1883 :                 free (name);
     179        1883 :                 return ELEKTRA_GLOB_NOMATCH;
     180             :         }
     181             : 
     182         437 :         rc = checkElektraExtensions (name, pattern);
     183             : 
     184         437 :         free (name);
     185             : 
     186         437 :         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           2 : int elektraKsGlob (KeySet * result, KeySet * input, const char * pattern)
     201             : {
     202           2 :         if (!result) return ELEKTRA_GLOB_NOMATCH;
     203             : 
     204           2 :         if (!input) return ELEKTRA_GLOB_NOMATCH;
     205             : 
     206           2 :         if (!pattern) return ELEKTRA_GLOB_NOMATCH;
     207             : 
     208           2 :         int ret = 0;
     209             :         Key * current;
     210             : 
     211           2 :         cursor_t cursor = ksGetCursor (input);
     212           2 :         ksRewind (input);
     213          12 :         while ((current = ksNext (input)) != 0)
     214             :         {
     215           8 :                 int rc = elektraKeyGlob (current, pattern);
     216           8 :                 if (rc == 0)
     217             :                 {
     218           4 :                         ++ret;
     219           4 :                         ksAppendKey (result, keyDup (current));
     220             :                 }
     221             :         }
     222           2 :         ksSetCursor (input, cursor);
     223           2 :         return ret;
     224             : }

Generated by: LCOV version 1.13