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