/*
* Amanda, The Advanced Maryland Automatic Network Disk Archiver
* Copyright (c) 1991-1998 University of Maryland at College Park
* Copyright (c) 2007-2012 Zmanda, Inc. All Rights Reserved.
* Copyright (c) 2013-2016 Carbonite, Inc. All Rights Reserved.
* All Rights Reserved.
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of U.M. not be used in advertising or
* publicity pertaining to distribution of the software without specific,
* written prior permission. U.M. makes no representations about the
* suitability of this software for any purpose. It is provided "as is"
* without express or implied warranty.
*
* U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
* BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Authors: the Amanda Development Team. Its members are listed in a
* file named AUTHORS, in the root directory of this distribution.
*/
/*
* See match.h for function prototypes and further explanations.
*/
#include "amanda.h"
#include "match.h"
#include <regex.h>
/*
* DATA STRUCTURES, MACROS, STATIC DATA
*/
/*
* Return codes used by try_match()
*/
#define MATCH_OK (1)
#define MATCH_NONE (0)
#define MATCH_ERROR (-1)
/*
* Macro to tell whether a character is a regex metacharacter. Note that '*'
* and '?' are NOT included: they are themselves special in globs.
*/
#define IS_REGEX_META(c) ( \
(c) == '.' || (c) == '(' || (c) == ')' || (c) == '{' || (c) == '}' || \
(c) == '+' || (c) == '^' || (c) == '$' || (c) == '|' \
)
/*
* Define a specific type to hold error messages in case regex compile/matching
* fails
*/
typedef char regex_errbuf[STR_SIZE];
/*
* Structure used by amglob_to_regex() to expand particular glob characters. Its
* fields are:
* - question_mark: what the question mark ('?') should be replaced with;
* - star: what the star ('*') should be replaced with;
* - double_star: what two consecutive stars should be replaced with.
*
* Note that apart from double_star, ALL OTHER FIELDS MUST NOT BE NULL.
*/
struct subst_table {
const char *question_mark;
const char *star;
const char *double_star;
};
/*
* Susbtitution data for glob_to_regex()
*/
static struct subst_table glob_subst_stable = {
"[^/]", /* question_mark */
"[^/]*", /* star */
NULL /* double_star */
};
/*
* Substitution data for tar_to_regex()
*/
static struct subst_table tar_subst_stable = {
"[^/]", /* question_mark */
".*", /* star */
NULL /* double_star */
};
/*
* Substitution data for match_word(): dot
*/
static struct subst_table mword_dot_subst_table = {
"[^.]", /* question_mark */
"[^.]*", /* star */
".*" /* double_star */
};
/*
* Substitution data for match_word(): slash
*/
static struct subst_table mword_slash_subst_table = {
"[^/]", /* question_mark */
"[^/]*", /* star */
".*" /* double_star */
};
/*
* match_word() specific data:
* - re_double_sep: anchored regex matching two separators;
* - re_separator: regex matching the separator;
* - re_begin_full: regex matching the separator, anchored at the beginning;
* - re_end_full: regex matching the separator, andchored at the end.
*/
struct mword_regexes {
const char *re_double_sep;
const char *re_begin_full;
const char *re_separator;
const char *re_end_full;
};
static struct mword_regexes mword_dot_regexes = {
"^\\.\\.$", /* re_double_sep */
"^\\.", /* re_begin_full */
"\\.", /* re_separator */
"\\.$" /* re_end_full */
};
static struct mword_regexes mword_slash_regexes = {
"^\\/\\/$", /* re_double_sep */
"^\\/", /* re_begin_full */
"\\/", /* re_separator */
"\\/$" /* re_end_full */
};
/*
* Regular expression caches, and a static mutex to protect initialization and
* access. This may be unnecessarily coarse, but it is unknown at this time
* whether GHashTable accesses are thread-safe, and get_regex_from_cache() may
* be called from within threads, so play it safe.
*/
#if (GLIB_MAJOR_VERSION > 2 || (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 31))
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wmissing-field-initializers"
static GStaticMutex re_cache_mutex = G_STATIC_MUTEX_INIT;
# pragma GCC diagnostic pop
#else
static GStaticMutex re_cache_mutex = G_STATIC_MUTEX_INIT;
#endif
static GHashTable *regex_cache = NULL, *regex_cache_newline = NULL;
/*
* REGEX FUNCTIONS
*/
/*
* Initialize regex caches. NOTE: this function MUST be called with
* re_cache_mutex LOCKED, see get_regex_from_cache()
*/
static void init_regex_caches(void)
{
static gboolean initialized = FALSE;
if (initialized)
return;
regex_cache = g_hash_table_new(g_str_hash, g_str_equal);
regex_cache_newline = g_hash_table_new(g_str_hash, g_str_equal);
initialized = TRUE;
}
/*
* Cleanup a regular expression by escaping all non alphanumeric characters, and
* append beginning/end anchors if need be
*/
char *clean_regex(const char *str, gboolean anchor)
{
const char *src;
char *result, *dst;
result = g_malloc(2 * strlen(str) + 3);
dst = result;
if (anchor)
*dst++ = '^';
for (src = str; *src; src++) {
if (!g_ascii_isalnum((int) *src))
*dst++ = '\\';
*dst++ = *src;
}
if (anchor)
*dst++ = '$';
*dst = '\0';
return result;
}
/*
* Compile one regular expression. Return TRUE if the regex has been compiled
* successfully. Otherwise, return FALSE and copy the error message into the
* supplied regex_errbuf pointer. Also, we want to know whether flags should
* include REG_NEWLINE (See regcomp(3) for details). Since this is the more
* frequent case, add REG_NEWLINE to the default flags, and remove it only if
* match_newline is set to FALSE.
*/
static gboolean do_regex_compile(const char *str, regex_t *regex,
regex_errbuf *errbuf, gboolean match_newline)
{
int flags = REG_EXTENDED | REG_NOSUB | REG_NEWLINE;
int result;
if (!match_newline)
flags &= ~REG_NEWLINE;
result = regcomp(regex, str, flags);
if (!result)
return TRUE;
regerror(result, regex, *errbuf, sizeof(*errbuf));
return FALSE;
}
/*
* Get an already compiled buffer from the regex cache. If the regex is not in
* the cache, allocate a new one and compile it using do_regex_compile(). If the
* compile fails, call regfree() on the object and return NULL to the caller. If
* it does succeed, put the regex buffer in cache and return a pointer to it.
*/
static regex_t *get_regex_from_cache(const char *re_str, regex_errbuf *errbuf,
gboolean match_newline)
{
regex_t *ret;
GHashTable *cache;
g_static_mutex_lock(&re_cache_mutex);
init_regex_caches();
cache = (match_newline) ? regex_cache_newline: regex_cache;
ret = g_hash_table_lookup(cache, re_str);
if (ret)
goto out;
ret = g_new(regex_t, 1);
if (do_regex_compile(re_str, ret, errbuf, match_newline)) {
g_hash_table_insert(cache, g_strdup(re_str), ret);
goto out;
}
regfree(ret);
g_free(ret);
ret = NULL;
out:
g_static_mutex_unlock(&re_cache_mutex);
return ret;
}
/*
* Validate one regular expression using do_regex_compile(), and return NULL if
* the regex is valid, or the error message otherwise.
*/
char *validate_regexp(const char *regex)
{
regex_t regc;
static regex_errbuf errmsg;
gboolean valid;
valid = do_regex_compile(regex, ®c, &errmsg, TRUE);
regfree(®c);
return (valid) ? NULL : errmsg;
}
/*
* See if a string matches a compiled regular expression. Return one of MATCH_*
* defined above. If, for some reason, regexec() returns something other than
* not 0 or REG_NOMATCH, return MATCH_ERROR and print the error message in the
* supplied regex_errbuf.
*/
static int try_match(regex_t *regex, const char *str,
regex_errbuf *errbuf)
{
int result = regexec(regex, str, 0, 0, 0);
switch(result) {
case 0:
return MATCH_OK;
case REG_NOMATCH:
return MATCH_NONE;
/* Fall through: something went really wrong */
}
regerror(result, regex, *errbuf, sizeof(*errbuf));
return MATCH_ERROR;
}
/*
* Try and match a string against a regular expression, using
* do_regex_compile() and try_match(). Exit early if the regex didn't compile
* or there was an error during matching.
*/
int do_match(const char *regex, const char *str, gboolean match_newline)
{
regex_t *re;
int result;
regex_errbuf errmsg;
re = get_regex_from_cache(regex, &errmsg, match_newline);
if (!re)
error("regex \"%s\": %s", regex, errmsg);
/*NOTREACHED*/
result = try_match(re, str, &errmsg);
if (result == MATCH_ERROR)
error("regex \"%s\": %s", regex, errmsg);
/*NOTREACHED*/
return result;
}
/*
* DISK/HOST EXPRESSION HANDLING
*/
/*
* Check whether a given character should be escaped (that is, prepended with a
* backslash), EXCEPT for one character.
*/
static gboolean should_be_escaped_except(char c, char not_this_one)
{
if (c == not_this_one)
return FALSE;
switch (c) {
case '\\':
case '^':
case '$':
case '?':
case '*':
case '[':
case ']':
case '.':
case '/':
return TRUE;
}
return FALSE;
}
/*
* Take a disk/host expression and turn it into a full-blown amglob (with
* start and end anchors) following rules in amanda-match(7). The not_this_one
* argument represents a character which is NOT meant to be special in this
* case: '/' for disks and '.' for hosts.
*/
static char *full_amglob_from_expression(const char *str, char not_this_one)
{
const char *src;
char *result, *dst;
result = g_malloc(2 * strlen(str) + 3);
dst = result;
*dst++ = '^';
for (src = str; *src; src++) {
if (should_be_escaped_except(*src, not_this_one))
*dst++ = '\\';
*dst++ = *src;
}
*dst++ = '$';
*dst = '\0';
return result;
}
/*
* Turn a disk/host expression into a regex
*/
char *make_exact_disk_expression(const char *disk)
{
return full_amglob_from_expression(disk, '/');
}
char *make_exact_host_expression(const char *host)
{
return full_amglob_from_expression(host, '.');
}
/*
* GLOB HANDLING, as per amanda-match(7)
*/
/*
* Turn a glob into a regex.
*/
static char *amglob_to_regex(const char *str, const char *begin,
const char *end, struct subst_table *table)
{
const char *src;
char *result, *dst;
char c;
size_t worst_case;
gboolean double_star = (table->double_star != NULL);
/*
* There are two particular cases when building a regex out of a glob:
* character classes (anything inside [...] or [!...] and quotes (anything
* preceded by a backslash). We start with none being true.
*/
gboolean in_character_class = FALSE, in_quote = FALSE;
/*
* Allocate enough space for our string. At worst, the allocated space is
* the length of the following:
* - beginning of regex;
* - size of original string multiplied by worst-case expansion;
* - end of regex;
* - final 0.
*
* Calculate the worst case expansion by walking our struct subst_table.
*/
worst_case = strlen(table->question_mark);
if (worst_case < strlen(table->star))
worst_case = strlen(table->star);
if (double_star && worst_case < strlen(table->double_star))
worst_case = strlen(table->double_star);
result = g_malloc(strlen(begin) + strlen(str) * worst_case + strlen(end) + 1);
/*
* Start by copying the beginning of the regex...
*/
dst = g_stpcpy(result, begin);
/*
* ... Now to the meat of it.
*/
for (src = str; *src; src++) {
c = *src;
/*
* First, check that we're in a character class: each and every
* character can be copied as is. We only need to be careful is the
* character is a closing bracket: it will end the character class IF
* AND ONLY IF it is not preceded by a backslash.
*/
if (in_character_class) {
in_character_class = ((c != ']') || (*(src - 1) == '\\'));
goto straight_copy;
}
/*
* Are we in a quote? If yes, it is really simple: copy the current
* character, close the quote, the end.
*/
if (in_quote) {
in_quote = FALSE;
goto straight_copy;
}
/*
* The only thing left to handle now is the "normal" case: we are not in
* a character class nor in a quote.
*/
if (c == '\\') {
/*
* Backslash: append it, and open a new quote.
*/
in_quote = TRUE;
goto straight_copy;
} else if (c == '[') {
/*
* Opening bracket: the beginning of a character class.
*
* Look ahead the next character: if it's an exclamation mark, then
* this is a complemented character class; append a caret to make
* the result string regex-friendly, and forward one character in
* advance.
*/
*dst++ = c;
in_character_class = TRUE;
if (*(src + 1) == '!') {
*dst++ = '^';
src++;
}
} else if (IS_REGEX_META(c)) {
/*
* Regex metacharacter (except for ? and *, see below): append a
* backslash, and then the character itself.
*/
*dst++ = '\\';
goto straight_copy;
} else if (c == '?')
/*
* Question mark: take the subsitution string out of our subst_table
* and append it to the string.
*/
dst = g_stpcpy(dst, table->question_mark);
else if (c == '*') {
/*
* Star: append the subsitution string found in our subst_table.
* However, look forward the next character: if it's yet another
* star, then see if there is a substitution string for the double
* star and append this one instead.
*
* FIXME: this means that two consecutive stars in a glob string
* where there is no substition for double_star can lead to
* exponential regex execution time: consider [^/]*[^/]*.
*/
const char *p = table->star;
if (double_star && *(src + 1) == '*') {
src++;
p = table->double_star;
}
dst = g_stpcpy(dst, p);
} else {
/*
* Any other character: append each time.
*/
straight_copy:
*dst++ = c;
}
}
/*
* Done, now append the end, ONLY if we are not in a quote - a lone
* backslash at the end of a glob is illegal, just leave it as it, it will
* make the regex compile fail.
*/
if (!in_quote)
dst = g_stpcpy(dst, end);
/*
* Finalize, return.
*/
*dst = '\0';
return result;
}
/*
* File globs
*/
char *glob_to_regex(const char *glob)
{
return amglob_to_regex(glob, "^", "$", &glob_subst_stable);
}
int match_glob(const char *glob, const char *str)
{
char *regex;
regex_t *re;
int result;
regex_errbuf errmsg;
regex = glob_to_regex(glob);
re = get_regex_from_cache(regex, &errmsg, TRUE);
if (!re)
error("glob \"%s\" -> regex \"%s\": %s", glob, regex, errmsg);
/*NOTREACHED*/
result = try_match(re, str, &errmsg);
if (result == MATCH_ERROR)
error("glob \"%s\" -> regex \"%s\": %s", glob, regex, errmsg);
/*NOTREACHED*/
g_free(regex);
return result;
}
char *validate_glob(const char *glob)
{
char *regex, *ret = NULL;
regex_t regc;
static regex_errbuf errmsg;
regex = glob_to_regex(glob);
if (!do_regex_compile(regex, ®c, &errmsg, TRUE))
ret = errmsg;
regfree(®c);
g_free(regex);
return ret;
}
/*
* Tar globs
*/
static char *tar_to_regex(const char *glob)
{
return amglob_to_regex(glob, "(^|/)", "($|/)", &tar_subst_stable);
}
int match_tar(const char *glob, const char *str)
{
char *regex;
regex_t *re;
int result;
regex_errbuf errmsg;
regex = tar_to_regex(glob);
re = get_regex_from_cache(regex, &errmsg, TRUE);
if (!re)
error("glob \"%s\" -> regex \"%s\": %s", glob, regex, errmsg);
/*NOTREACHED*/
result = try_match(re, str, &errmsg);
if (result == MATCH_ERROR)
error("glob \"%s\" -> regex \"%s\": %s", glob, regex, errmsg);
/*NOTREACHED*/
g_free(regex);
return result;
}
/*
* DISK/HOST MATCHING
*
* The functions below wrap input strings with separators and attempt to match
* the result. The core of the operation is the match_word() function.
*/
/*
* Check whether a glob passed as an argument to match_word() only looks for the
* separator
*/
static gboolean glob_is_separator_only(const char *glob, char sep) {
size_t len = strlen(glob);
const char len2_1[] = { '^', sep , 0 }, len2_2[] = { sep, '$', 0 },
len3[] = { '^', sep, '$', 0 };
switch (len) {
case 1:
return (*glob == sep);
case 2:
return !(!g_str_equal(glob, len2_1) && !g_str_equal(glob, len2_2));
case 3:
return g_str_equal(glob, len3);
default:
return FALSE;
}
}
/*
* Given a word and a separator as an argument, wrap the word with separators -
* if need be. For instance, if '/' is the separator, the rules are:
*
* - "" -> "/"
* - "/" -> "//"
* - "//" -> left alone
* - "xxx" -> "/xxx/"
* - "/xxx" -> "/xxx/"
* - "xxx/" -> "/xxx/"
* - "/xxx/" -> left alone
*
* (note that xxx here may contain the separator as well)
*
* Note that the returned string is dynamically allocated: it is up to the
* caller to free it. Note also that the first argument MUST NOT BE NULL.
*/
static char *wrap_word(const char *word, const char separator, const char *glob)
{
size_t len = strlen(word);
size_t len_glob = strlen(glob);
char *result, *p;
/*
* We allocate for the worst case, which is two bytes more than the input
* (have to prepend and append a separator).
*/
result = g_malloc(len + 3);
p = result;
/*
* Zero-length: separator only
*/
if (len == 0) {
*p++ = separator;
goto out;
}
/*
* Length is one: if the only character is the separator only, the result
* string is two separators
*/
if (len == 1 && word[0] == separator) {
*p++ = separator;
*p++ = separator;
goto out;
}
/*
* Otherwise: prepend the separator if needed, append the separator if
* needed.
*/
if (word[0] != separator && glob[0] != '^')
*p++ = separator;
p = g_stpcpy(p, word);
if (word[len - 1] != separator && glob[len_glob-1] != '$')
*p++ = separator;
out:
*p++ = '\0';
return result;
}
static int match_word(const char *glob, const char *word, const char separator)
{
char *wrapped_word = wrap_word(word, separator, glob);
struct mword_regexes *regexes = &mword_slash_regexes;
struct subst_table *table = &mword_slash_subst_table;
gboolean not_slash = (separator != '/');
int ret;
/*
* We only expect two separators: '/' or '.'. If it's not '/', it has to be
* the other one...
*/
if (not_slash) {
regexes = &mword_dot_regexes;
table = &mword_dot_subst_table;
}
if(glob_is_separator_only(glob, separator)) {
ret = do_match(regexes->re_double_sep, wrapped_word, TRUE);
goto out;
} else {
/*
* Unlike what happens for tar and disk expressions, we need to
* calculate the beginning and end of our regex before calling
* amglob_to_regex().
*/
const char *begin, *end;
char *glob_copy = g_strdup(glob);
char *p, *g = glob_copy;
char *regex;
/*
* Calculate the beginning of the regex:
* - by default, it is an unanchored separator;
* - if the glob begins with a caret, make that an anchored separator,
* and increment g appropriately;
* - if it begins with a separator, make it the empty string.
*/
p = glob_copy;
begin = regexes->re_separator;
if (*p == '^') {
begin = "^";
p++, g++;
if (*p == separator) {
begin = regexes->re_begin_full;
g++;
}
} else if (*p == separator)
begin = "";
/*
* Calculate the end of the regex:
* - an unanchored separator by default;
* - if the last character is a backslash or the separator itself, it
* should be the empty string;
* - if it is a dollar sign, overwrite it with 0 and look at the
* character before it: if it is the separator, only anchor at the
* end, otherwise, add a separator before the anchor.
*/
p = &(glob_copy[strlen(glob_copy) - 1]);
end = regexes->re_separator;
if (*p == '\\' || *p == separator) {
end = "";
} else if (*p == '$') {
char prev = *(p - 1);
*p = '\0';
if (prev == separator) {
*(p-1) = '\0';
if (p-2 >= glob_copy) {
prev = *(p - 2);
if (prev == '\\') {
*(p-2) = '\0';
}
}
end = regexes->re_end_full;
} else {
end = "$";
}
}
regex = amglob_to_regex(g, begin, end, table);
ret = do_match(regex, wrapped_word, TRUE);
g_free(glob_copy);
g_free(regex);
}
out:
g_free(wrapped_word);
return ret;
}
/*
* Match a host expression
*/
int match_host(const char *glob, const char *host)
{
char *lglob, *lhost;
int ret;
if (*glob == '=') {
return strcmp(glob+1, host) == 0;
}
lglob = g_ascii_strdown(glob, -1);
lhost = g_ascii_strdown(host, -1);
ret = match_word(lglob, lhost, '.');
g_free(lglob);
g_free(lhost);
return ret;
}
/*
* Match a disk expression. Not as straightforward, since Windows paths must be
* accounted for.
*/
/*
* Convert a disk and glob from Windows expressed paths (backslashes) into Unix
* paths (slashes).
*
* Note: the resulting string is dynamically allocated, it is up to the caller
* to free it.
*
* Note 2: UNC in convert_unc_to_unix stands for Uniform Naming Convention.
*/
static char *convert_unc_to_unix(const char *unc)
{
char *result = g_strdup(unc);
return g_strdelimit(result, "\\", '/');
}
static char *convert_winglob_to_unix(const char *glob)
{
const char *src;
char *result, *dst;
result = g_malloc(strlen(glob) + 1);
dst = result;
for (src = glob; *src; src++) {
if (*src == '\\' && *(src + 1) == '\\') {
*dst++ = '/';
src++;
continue;
}
*dst++ = *src;
}
*dst = '\0';
return result;
}
/*
* Match a disk expression
*/
int match_disk(const char *glob, const char *disk)
{
char *glob2 = NULL, *disk2 = NULL;
const char *g = glob, *d = disk;
int result;
/*
* Check whether our disk potentially refers to a Windows share (the first
* two characters are '\' and there is no / in the word at all): if yes,
* build Unix paths instead and pass those as arguments to match_word()
*/
gboolean windows_share = !(strncmp(disk, "\\\\", 2) || strchr(disk, '/'));
if (*glob == '=') {
return strcmp(glob+1, disk) == 0;
}
if (windows_share) {
glob2 = convert_winglob_to_unix(glob);
disk2 = convert_unc_to_unix(disk);
g = (const char *) glob2;
d = (const char *) disk2;
}
result = match_word(g, d, '/');
/*
* We can g_free(NULL), so this is "safe"
*/
g_free(glob2);
g_free(disk2);
return result;
}
/*
* TIMESTAMPS/LEVEL MATCHING
*/
static int
alldigits(
const char *str)
{
while (*str) {
if (!isdigit((int)*(str++)))
return 0;
}
return 1;
}
int
match_datestamp(
const char * dateexp,
const char * datestamp)
{
char *dash;
size_t len, len_suffix;
size_t len_prefix;
char firstdate[100], lastdate[100];
char mydateexp[100];
int match_exact;
if(strlen(dateexp) >= 100 || strlen(dateexp) < 1) {
goto illegal;
}
/*
* datestamp matching does not involve globs or regexes, so
* handle wildcarding as a special case: if dateexp is "*",
* then all datestamps match.
*/
if (strcmp(dateexp, "*") == 0) {
return TRUE;
}
if (*dateexp == '=') {
return strcmp(dateexp+1, datestamp) == 0;
}
/* strip and ignore an initial "^" */
if(dateexp[0] == '^') {
strncpy(mydateexp, dateexp+1, sizeof(mydateexp)-1);
mydateexp[sizeof(mydateexp)-1] = '\0';
}
else {
strncpy(mydateexp, dateexp, sizeof(mydateexp)-1);
mydateexp[sizeof(mydateexp)-1] = '\0';
}
if(strlen(dateexp) < 1) {
goto illegal;
}
if(mydateexp[strlen(mydateexp)-1] == '$') {
match_exact = 1;
mydateexp[strlen(mydateexp)-1] = '\0'; /* strip the trailing $ */
}
else
match_exact = 0;
/* a single dash represents a date range */
if((dash = strchr(mydateexp,'-'))) {
if(match_exact == 1 || strchr(dash+1, '-')) {
goto illegal;
}
/* format: XXXYYYY-ZZZZ, indicating dates XXXYYYY to XXXZZZZ */
len = (size_t)(dash - mydateexp); /* length of XXXYYYY */
len_suffix = strlen(dash) - 1; /* length of ZZZZ */
if (len_suffix > len) goto illegal;
if (len < len_suffix) {
goto illegal;
}
len_prefix = len - len_suffix; /* length of XXX */
dash++;
strncpy(firstdate, mydateexp, len);
firstdate[len] = '\0';
strncpy(lastdate, mydateexp, len_prefix);
strncpy(&(lastdate[len_prefix]), dash, len_suffix);
lastdate[len] = '\0';
if (!alldigits(firstdate) || !alldigits(lastdate))
goto illegal;
if (strncmp(firstdate, lastdate, strlen(firstdate)) > 0)
goto illegal;
return ((strncmp(datestamp, firstdate, strlen(firstdate)) >= 0) &&
(strncmp(datestamp, lastdate , strlen(lastdate)) <= 0));
}
else {
if (!alldigits(mydateexp))
goto illegal;
if(match_exact == 1) {
return (g_str_equal(datestamp, mydateexp));
}
else {
return (g_str_has_prefix(datestamp, mydateexp));
}
}
illegal:
error("Illegal datestamp expression %s", dateexp);
/*NOTREACHED*/
}
int
match_level(
const char * levelexp,
const char * level)
{
char *dash;
long int low, hi, level_i;
char mylevelexp[100];
int match_exact;
if(strlen(levelexp) >= 100 || strlen(levelexp) < 1) {
error("Illegal level expression %s", levelexp);
/*NOTREACHED*/
}
if (*levelexp == '=') {
return strcmp(levelexp+1, level) == 0;
}
if(levelexp[0] == '^') {
strncpy(mylevelexp, levelexp+1, strlen(levelexp)-1);
mylevelexp[strlen(levelexp)-1] = '\0';
if (strlen(levelexp) == 0) {
error("Illegal level expression %s", levelexp);
/*NOTREACHED*/
}
}
else {
strncpy(mylevelexp, levelexp, strlen(levelexp));
mylevelexp[strlen(levelexp)] = '\0';
}
if(mylevelexp[strlen(mylevelexp)-1] == '$') {
match_exact = 1;
mylevelexp[strlen(mylevelexp)-1] = '\0';
}
else
match_exact = 0;
if((dash = strchr(mylevelexp,'-'))) {
if(match_exact == 1) {
goto illegal;
}
*dash = '\0';
if (!alldigits(mylevelexp) || !alldigits(dash+1)) goto illegal;
errno = 0;
low = strtol(mylevelexp, (char **) NULL, 10);
if (errno) goto illegal;
hi = strtol(dash+1, (char **) NULL, 10);
if (errno) goto illegal;
level_i = strtol(level, (char **) NULL, 10);
if (errno) goto illegal;
return ((level_i >= low) && (level_i <= hi));
}
else {
if (!alldigits(mylevelexp)) goto illegal;
if(match_exact == 1) {
return (g_str_equal(level, mylevelexp));
}
else {
return (g_str_has_prefix(level, mylevelexp));
}
}
illegal:
error("Illegal level expression %s", levelexp);
/*NOTREACHED*/
}
static char *
make_template(
const gboolean add_begin_and_end,
const char *al_template,
const char *barcode,
const char *meta,
const char *storage)
{
char *template = g_malloc(1024);
char *t = template;
const char *at = al_template;
if (al_template == NULL)
at = "";
if (add_begin_and_end)
*t++ = '^';
while (*at != '\0') {
if (*at == '$') {
at++;
if (*at == 'c') {
strcpy(t, get_config_name());
t += strlen(get_config_name());
at++;
} else if (*at == 'o') {
strcpy(t, getconf_str(CNF_ORG));
t += strlen(getconf_str(CNF_ORG));
at++;
} else if (*at == 'b') {
if (barcode) {
strcpy(t, barcode);
t += strlen(barcode);
}
at++;
} else if (*at == 'm') {
if (meta) {
strcpy(t, meta);
t += strlen(meta);
}
at++;
} else if (*at == 'r') {
if (storage) {
strcpy(t, storage);
t += strlen(storage);
}
at++;
} else if (*at == 's') {
*t++ = '[';
*t++ = '0';
*t++ = '-';
*t++ = '9';
*t++ = ']';
*t++ = '*';
at++;
} else if (*at >= '0' && *at <= '9') {
int num = *at - '0';
at++;
while (*at >= '0' && *at <= '9') {
num *= 10;
num += *at - '0';
}
if (*at == 's') {
int i;
for (i=0; i< num; i++) {
*t++ = '[';
*t++ = '0';
*t++ = '-';
*t++ = '9';
*t++ = ']';
}
}
at++;
} else if (*at == '$') {
/* two $, copy one */
*t++ = *at++;
} else if (*at == '\0') {
/* $ at end, copy it */
*t++ = '$';
} else {
/* Copy the $ and continue withthe next character */
*t++ = *at;
}
} else if (*at == '%') {
*t++ = '[';
*t++ = '0';
*t++ = '-';
*t++ = '9';
*t++ = ']';
at++;
} else if (*at == '!') {
*t++ = '[';
*t++ = 'A';
*t++ = '-';
*t++ = 'Z';
*t++ = ']';
at++;
} else if (*at == '\\') {
at++;
*t++ = *at++;
} else {
*t++ = *at++;
}
}
if (add_begin_and_end)
*t++ = '$';
*t = '\0';
return template;
}
int
match_labelstr_template(
const char *template,
const char *label,
const char *barcode,
const char *meta,
const char *storage)
{
char *ztemplate = make_template(FALSE, template, barcode, meta, storage);
int result;
result = match(ztemplate, label);
g_free(ztemplate);
return result;
}
int
match_labelstr(
const labelstr_s *labelstr,
const autolabel_t *autolabel,
const char *label,
const char *barcode,
const char *meta,
const char *storage)
{
char *template;
int result;
if (labelstr->match_autolabel) {
template = make_template(TRUE, autolabel->template, barcode, meta, storage);
} else {
template = make_template(FALSE, labelstr->template, barcode, meta, storage);
}
result = match(template, label);
g_free(template);
return result;
}