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