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