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/*" 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 : }
|