• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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