blob: 8dc5b32d5cdbd55c99dcda6a61fc9d44ea4f9f3d [file] [log] [blame]
/*
* Search routines for CUPS.
*
* Copyright 2007-2018 by Apple Inc.
* Copyright 1997-2006 by Easy Software Products.
*
* Licensed under Apache License v2.0. See the file "LICENSE" for more
* information.
*/
/*
* Include necessary headers...
*/
#include "cgi-private.h"
#include <regex.h>
/*
* 'cgiCompileSearch()' - Compile a search string.
*/
void * /* O - Search context */
cgiCompileSearch(const char *query) /* I - Query string */
{
regex_t *re; /* Regular expression */
char *s, /* Regular expression string */
*sptr, /* Pointer into RE string */
*sword; /* Pointer to start of word */
size_t slen; /* Allocated size of RE string */
const char *qptr, /* Pointer into query string */
*qend; /* End of current word */
const char *prefix; /* Prefix to add to next word */
int quoted; /* Word is quoted */
size_t wlen; /* Word length */
char *lword; /* Last word in query */
/*
* Range check input...
*/
if (!query)
return (NULL);
/*
* Allocate a regular expression storage structure...
*/
if ((re = (regex_t *)calloc(1, sizeof(regex_t))) == NULL)
return (NULL);
/*
* Allocate a buffer to hold the regular expression string, starting
* at 1024 bytes or 3 times the length of the query string, whichever
* is greater. We'll expand the string as needed...
*/
slen = strlen(query) * 3;
if (slen < 1024)
slen = 1024;
if ((s = (char *)malloc(slen)) == NULL)
{
free(re);
return (NULL);
}
/*
* Copy the query string to the regular expression, handling basic
* AND and OR logic...
*/
prefix = ".*";
qptr = query;
sptr = s;
lword = NULL;
while (*qptr)
{
/*
* Skip leading whitespace...
*/
while (isspace(*qptr & 255))
qptr ++;
if (!*qptr)
break;
/*
* Find the end of the current word...
*/
if (*qptr == '\"' || *qptr == '\'')
{
/*
* Scan quoted string...
*/
quoted = *qptr ++;
for (qend = qptr; *qend && *qend != quoted; qend ++);
if (!*qend)
{
/*
* No closing quote, error out!
*/
free(s);
free(re);
if (lword)
free(lword);
return (NULL);
}
}
else
{
/*
* Scan whitespace-delimited string...
*/
quoted = 0;
for (qend = qptr + 1; *qend && !isspace(*qend); qend ++);
}
wlen = (size_t)(qend - qptr);
/*
* Look for logic words: AND, OR
*/
if (wlen == 3 && !_cups_strncasecmp(qptr, "AND", 3))
{
/*
* Logical AND with the following text...
*/
if (sptr > s)
prefix = ".*";
qptr = qend;
}
else if (wlen == 2 && !_cups_strncasecmp(qptr, "OR", 2))
{
/*
* Logical OR with the following text...
*/
if (sptr > s)
prefix = ".*|.*";
qptr = qend;
}
else
{
/*
* Add a search word, making sure we have enough room for the
* string + RE overhead...
*/
wlen = (size_t)(sptr - s) + 2 * 4 * wlen + 2 * strlen(prefix) + 11;
if (lword)
wlen += strlen(lword);
if (wlen > slen)
{
/*
* Expand the RE string buffer...
*/
char *temp; /* Temporary string pointer */
slen = wlen + 128;
temp = (char *)realloc(s, slen);
if (!temp)
{
free(s);
free(re);
if (lword)
free(lword);
return (NULL);
}
sptr = temp + (sptr - s);
s = temp;
}
/*
* Add the prefix string...
*/
memcpy(sptr, prefix, strlen(prefix) + 1);
sptr += strlen(sptr);
/*
* Then quote the remaining word characters as needed for the
* RE...
*/
sword = sptr;
while (qptr < qend)
{
/*
* Quote: ^ . [ $ ( ) | * + ? { \
*/
if (strchr("^.[$()|*+?{\\", *qptr))
*sptr++ = '\\';
*sptr++ = *qptr++;
}
*sptr = '\0';
/*
* For "word1 AND word2", add reciprocal "word2 AND word1"...
*/
if (!strcmp(prefix, ".*") && lword)
{
char *lword2; /* New "last word" */
if ((lword2 = strdup(sword)) == NULL)
{
free(lword);
free(s);
free(re);
return (NULL);
}
memcpy(sptr, ".*|.*", 6);
sptr += 5;
memcpy(sptr, lword2, strlen(lword2) + 1);
sptr += strlen(sptr);
memcpy(sptr, ".*", 3);
sptr += 2;
memcpy(sptr, lword, strlen(lword) + 1);
sptr += strlen(sptr);
free(lword);
lword = lword2;
}
else
{
if (lword)
free(lword);
lword = strdup(sword);
}
prefix = ".*|.*";
}
/*
* Advance to the next string...
*/
if (quoted)
qptr ++;
}
if (lword)
free(lword);
if (sptr > s)
memcpy(sptr, ".*", 3);
else
{
/*
* No query data, return NULL...
*/
free(s);
free(re);
return (NULL);
}
/*
* Compile the regular expression...
*/
if (regcomp(re, s, REG_EXTENDED | REG_ICASE))
{
free(re);
free(s);
return (NULL);
}
/*
* Free the RE string and return the new regular expression we compiled...
*/
free(s);
return ((void *)re);
}
/*
* 'cgiDoSearch()' - Do a search of some text.
*/
int /* O - Number of matches */
cgiDoSearch(void *search, /* I - Search context */
const char *text) /* I - Text to search */
{
int i; /* Looping var */
regmatch_t matches[100]; /* RE matches */
/*
* Range check...
*/
if (!search || !text)
return (0);
/*
* Do a lookup...
*/
if (!regexec((regex_t *)search, text, sizeof(matches) / sizeof(matches[0]),
matches, 0))
{
/*
* Figure out the number of matches in the string...
*/
for (i = 0; i < (int)(sizeof(matches) / sizeof(matches[0])); i ++)
if (matches[i].rm_so < 0)
break;
return (i);
}
else
return (0);
}
/*
* 'cgiFreeSearch()' - Free a compiled search context.
*/
void
cgiFreeSearch(void *search) /* I - Search context */
{
regfree((regex_t *)search);
free(search);
}