1 /*
2 * Search routines for CUPS.
3 *
4 * Copyright 2007-2018 by Apple Inc.
5 * Copyright 1997-2006 by Easy Software Products.
6 *
7 * Licensed under Apache License v2.0. See the file "LICENSE" for more
8 * information.
9 */
10
11 /*
12 * Include necessary headers...
13 */
14
15 #include "cgi-private.h"
16 #include <regex.h>
17
18
19 /*
20 * 'cgiCompileSearch()' - Compile a search string.
21 */
22
23 void * /* O - Search context */
cgiCompileSearch(const char * query)24 cgiCompileSearch(const char *query) /* I - Query string */
25 {
26 regex_t *re; /* Regular expression */
27 char *s, /* Regular expression string */
28 *sptr, /* Pointer into RE string */
29 *sword; /* Pointer to start of word */
30 size_t slen; /* Allocated size of RE string */
31 const char *qptr, /* Pointer into query string */
32 *qend; /* End of current word */
33 const char *prefix; /* Prefix to add to next word */
34 int quoted; /* Word is quoted */
35 size_t wlen; /* Word length */
36 char *lword; /* Last word in query */
37
38
39 /*
40 * Range check input...
41 */
42
43 if (!query)
44 return (NULL);
45
46 /*
47 * Allocate a regular expression storage structure...
48 */
49
50 if ((re = (regex_t *)calloc(1, sizeof(regex_t))) == NULL)
51 return (NULL);
52
53 /*
54 * Allocate a buffer to hold the regular expression string, starting
55 * at 1024 bytes or 3 times the length of the query string, whichever
56 * is greater. We'll expand the string as needed...
57 */
58
59 slen = strlen(query) * 3;
60 if (slen < 1024)
61 slen = 1024;
62
63 if ((s = (char *)malloc(slen)) == NULL)
64 {
65 free(re);
66 return (NULL);
67 }
68
69 /*
70 * Copy the query string to the regular expression, handling basic
71 * AND and OR logic...
72 */
73
74 prefix = ".*";
75 qptr = query;
76 sptr = s;
77 lword = NULL;
78
79 while (*qptr)
80 {
81 /*
82 * Skip leading whitespace...
83 */
84
85 while (isspace(*qptr & 255))
86 qptr ++;
87
88 if (!*qptr)
89 break;
90
91 /*
92 * Find the end of the current word...
93 */
94
95 if (*qptr == '\"' || *qptr == '\'')
96 {
97 /*
98 * Scan quoted string...
99 */
100
101 quoted = *qptr ++;
102 for (qend = qptr; *qend && *qend != quoted; qend ++);
103
104 if (!*qend)
105 {
106 /*
107 * No closing quote, error out!
108 */
109
110 free(s);
111 free(re);
112
113 if (lword)
114 free(lword);
115
116 return (NULL);
117 }
118 }
119 else
120 {
121 /*
122 * Scan whitespace-delimited string...
123 */
124
125 quoted = 0;
126 for (qend = qptr + 1; *qend && !isspace(*qend); qend ++);
127 }
128
129 wlen = (size_t)(qend - qptr);
130
131 /*
132 * Look for logic words: AND, OR
133 */
134
135 if (wlen == 3 && !_cups_strncasecmp(qptr, "AND", 3))
136 {
137 /*
138 * Logical AND with the following text...
139 */
140
141 if (sptr > s)
142 prefix = ".*";
143
144 qptr = qend;
145 }
146 else if (wlen == 2 && !_cups_strncasecmp(qptr, "OR", 2))
147 {
148 /*
149 * Logical OR with the following text...
150 */
151
152 if (sptr > s)
153 prefix = ".*|.*";
154
155 qptr = qend;
156 }
157 else
158 {
159 /*
160 * Add a search word, making sure we have enough room for the
161 * string + RE overhead...
162 */
163
164 wlen = (size_t)(sptr - s) + 2 * 4 * wlen + 2 * strlen(prefix) + 11;
165 if (lword)
166 wlen += strlen(lword);
167
168 if (wlen > slen)
169 {
170 /*
171 * Expand the RE string buffer...
172 */
173
174 char *temp; /* Temporary string pointer */
175
176
177 slen = wlen + 128;
178 temp = (char *)realloc(s, slen);
179 if (!temp)
180 {
181 free(s);
182 free(re);
183
184 if (lword)
185 free(lword);
186
187 return (NULL);
188 }
189
190 sptr = temp + (sptr - s);
191 s = temp;
192 }
193
194 /*
195 * Add the prefix string...
196 */
197
198 memcpy(sptr, prefix, strlen(prefix) + 1);
199 sptr += strlen(sptr);
200
201 /*
202 * Then quote the remaining word characters as needed for the
203 * RE...
204 */
205
206 sword = sptr;
207
208 while (qptr < qend)
209 {
210 /*
211 * Quote: ^ . [ $ ( ) | * + ? { \
212 */
213
214 if (strchr("^.[$()|*+?{\\", *qptr))
215 *sptr++ = '\\';
216
217 *sptr++ = *qptr++;
218 }
219
220 *sptr = '\0';
221
222 /*
223 * For "word1 AND word2", add reciprocal "word2 AND word1"...
224 */
225
226 if (!strcmp(prefix, ".*") && lword)
227 {
228 char *lword2; /* New "last word" */
229
230
231 if ((lword2 = strdup(sword)) == NULL)
232 {
233 free(lword);
234 free(s);
235 free(re);
236 return (NULL);
237 }
238
239 memcpy(sptr, ".*|.*", 6);
240 sptr += 5;
241
242 memcpy(sptr, lword2, strlen(lword2) + 1);
243 sptr += strlen(sptr);
244
245 memcpy(sptr, ".*", 3);
246 sptr += 2;
247
248 memcpy(sptr, lword, strlen(lword) + 1);
249 sptr += strlen(sptr);
250
251 free(lword);
252 lword = lword2;
253 }
254 else
255 {
256 if (lword)
257 free(lword);
258
259 lword = strdup(sword);
260 }
261
262 prefix = ".*|.*";
263 }
264
265 /*
266 * Advance to the next string...
267 */
268
269 if (quoted)
270 qptr ++;
271 }
272
273 if (lword)
274 free(lword);
275
276 if (sptr > s)
277 memcpy(sptr, ".*", 3);
278 else
279 {
280 /*
281 * No query data, return NULL...
282 */
283
284 free(s);
285 free(re);
286
287 return (NULL);
288 }
289
290 /*
291 * Compile the regular expression...
292 */
293
294 if (regcomp(re, s, REG_EXTENDED | REG_ICASE))
295 {
296 free(re);
297 free(s);
298
299 return (NULL);
300 }
301
302 /*
303 * Free the RE string and return the new regular expression we compiled...
304 */
305
306 free(s);
307
308 return ((void *)re);
309 }
310
311
312 /*
313 * 'cgiDoSearch()' - Do a search of some text.
314 */
315
316 int /* O - Number of matches */
cgiDoSearch(void * search,const char * text)317 cgiDoSearch(void *search, /* I - Search context */
318 const char *text) /* I - Text to search */
319 {
320 int i; /* Looping var */
321 regmatch_t matches[100]; /* RE matches */
322
323
324 /*
325 * Range check...
326 */
327
328 if (!search || !text)
329 return (0);
330
331 /*
332 * Do a lookup...
333 */
334
335 if (!regexec((regex_t *)search, text, sizeof(matches) / sizeof(matches[0]),
336 matches, 0))
337 {
338 /*
339 * Figure out the number of matches in the string...
340 */
341
342 for (i = 0; i < (int)(sizeof(matches) / sizeof(matches[0])); i ++)
343 if (matches[i].rm_so < 0)
344 break;
345
346 return (i);
347 }
348 else
349 return (0);
350 }
351
352
353 /*
354 * 'cgiFreeSearch()' - Free a compiled search context.
355 */
356
357 void
cgiFreeSearch(void * search)358 cgiFreeSearch(void *search) /* I - Search context */
359 {
360 regfree((regex_t *)search);
361 free(search);
362 }
363