/*1* Search routines for CUPS.2*3* Copyright 2007-2018 by Apple Inc.4* Copyright 1997-2006 by Easy Software Products.5*6* Licensed under Apache License v2.0. See the file "LICENSE" for more7* information.8*/910/*11* Include necessary headers...12*/1314#include "cgi-private.h"15#include <regex.h>161718/*19* 'cgiCompileSearch()' - Compile a search string.20*/2122void * /* O - Search context */23cgiCompileSearch(const char *query) /* I - Query string */24{25regex_t *re; /* Regular expression */26char *s, /* Regular expression string */27*sptr, /* Pointer into RE string */28*sword; /* Pointer to start of word */29size_t slen; /* Allocated size of RE string */30const char *qptr, /* Pointer into query string */31*qend; /* End of current word */32const char *prefix; /* Prefix to add to next word */33int quoted; /* Word is quoted */34size_t wlen; /* Word length */35char *lword; /* Last word in query */363738/*39* Range check input...40*/4142if (!query)43return (NULL);4445/*46* Allocate a regular expression storage structure...47*/4849if ((re = (regex_t *)calloc(1, sizeof(regex_t))) == NULL)50return (NULL);5152/*53* Allocate a buffer to hold the regular expression string, starting54* at 1024 bytes or 3 times the length of the query string, whichever55* is greater. We'll expand the string as needed...56*/5758slen = strlen(query) * 3;59if (slen < 1024)60slen = 1024;6162if ((s = (char *)malloc(slen)) == NULL)63{64free(re);65return (NULL);66}6768/*69* Copy the query string to the regular expression, handling basic70* AND and OR logic...71*/7273prefix = ".*";74qptr = query;75sptr = s;76lword = NULL;7778while (*qptr)79{80/*81* Skip leading whitespace...82*/8384while (isspace(*qptr & 255))85qptr ++;8687if (!*qptr)88break;8990/*91* Find the end of the current word...92*/9394if (*qptr == '\"' || *qptr == '\'')95{96/*97* Scan quoted string...98*/99100quoted = *qptr ++;101for (qend = qptr; *qend && *qend != quoted; qend ++);102103if (!*qend)104{105/*106* No closing quote, error out!107*/108109free(s);110free(re);111112if (lword)113free(lword);114115return (NULL);116}117}118else119{120/*121* Scan whitespace-delimited string...122*/123124quoted = 0;125for (qend = qptr + 1; *qend && !isspace(*qend); qend ++);126}127128wlen = (size_t)(qend - qptr);129130/*131* Look for logic words: AND, OR132*/133134if (wlen == 3 && !_cups_strncasecmp(qptr, "AND", 3))135{136/*137* Logical AND with the following text...138*/139140if (sptr > s)141prefix = ".*";142143qptr = qend;144}145else if (wlen == 2 && !_cups_strncasecmp(qptr, "OR", 2))146{147/*148* Logical OR with the following text...149*/150151if (sptr > s)152prefix = ".*|.*";153154qptr = qend;155}156else157{158/*159* Add a search word, making sure we have enough room for the160* string + RE overhead...161*/162163wlen = (size_t)(sptr - s) + 2 * 4 * wlen + 2 * strlen(prefix) + 11;164if (lword)165wlen += strlen(lword);166167if (wlen > slen)168{169/*170* Expand the RE string buffer...171*/172173char *temp; /* Temporary string pointer */174175176slen = wlen + 128;177temp = (char *)realloc(s, slen);178if (!temp)179{180free(s);181free(re);182183if (lword)184free(lword);185186return (NULL);187}188189sptr = temp + (sptr - s);190s = temp;191}192193/*194* Add the prefix string...195*/196197memcpy(sptr, prefix, strlen(prefix) + 1);198sptr += strlen(sptr);199200/*201* Then quote the remaining word characters as needed for the202* RE...203*/204205sword = sptr;206207while (qptr < qend)208{209/*210* Quote: ^ . [ $ ( ) | * + ? { \211*/212213if (strchr("^.[$()|*+?{\\", *qptr))214*sptr++ = '\\';215216*sptr++ = *qptr++;217}218219*sptr = '\0';220221/*222* For "word1 AND word2", add reciprocal "word2 AND word1"...223*/224225if (!strcmp(prefix, ".*") && lword)226{227char *lword2; /* New "last word" */228229230if ((lword2 = strdup(sword)) == NULL)231{232free(lword);233free(s);234free(re);235return (NULL);236}237238memcpy(sptr, ".*|.*", 6);239sptr += 5;240241memcpy(sptr, lword2, strlen(lword2) + 1);242sptr += strlen(sptr);243244memcpy(sptr, ".*", 3);245sptr += 2;246247memcpy(sptr, lword, strlen(lword) + 1);248sptr += strlen(sptr);249250free(lword);251lword = lword2;252}253else254{255if (lword)256free(lword);257258lword = strdup(sword);259}260261prefix = ".*|.*";262}263264/*265* Advance to the next string...266*/267268if (quoted)269qptr ++;270}271272if (lword)273free(lword);274275if (sptr > s)276memcpy(sptr, ".*", 3);277else278{279/*280* No query data, return NULL...281*/282283free(s);284free(re);285286return (NULL);287}288289/*290* Compile the regular expression...291*/292293if (regcomp(re, s, REG_EXTENDED | REG_ICASE))294{295free(re);296free(s);297298return (NULL);299}300301/*302* Free the RE string and return the new regular expression we compiled...303*/304305free(s);306307return ((void *)re);308}309310311/*312* 'cgiDoSearch()' - Do a search of some text.313*/314315int /* O - Number of matches */316cgiDoSearch(void *search, /* I - Search context */317const char *text) /* I - Text to search */318{319int i; /* Looping var */320regmatch_t matches[100]; /* RE matches */321322323/*324* Range check...325*/326327if (!search || !text)328return (0);329330/*331* Do a lookup...332*/333334if (!regexec((regex_t *)search, text, sizeof(matches) / sizeof(matches[0]),335matches, 0))336{337/*338* Figure out the number of matches in the string...339*/340341for (i = 0; i < (int)(sizeof(matches) / sizeof(matches[0])); i ++)342if (matches[i].rm_so < 0)343break;344345return (i);346}347else348return (0);349}350351352/*353* 'cgiFreeSearch()' - Free a compiled search context.354*/355356void357cgiFreeSearch(void *search) /* I - Search context */358{359regfree((regex_t *)search);360free(search);361}362363364