PageRenderTime 55ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/amanda/tags/3_3_0beta1/common-src/match.c

#
C | 942 lines | 607 code | 127 blank | 208 comment | 102 complexity | 9d63657c2962eb6490b33f66b202122b MD5 | raw file
  1. /*
  2. * Amanda, The Advanced Maryland Automatic Network Disk Archiver
  3. * Copyright (c) 1991-1998 University of Maryland at College Park
  4. * All Rights Reserved.
  5. *
  6. * Permission to use, copy, modify, distribute, and sell this software and its
  7. * documentation for any purpose is hereby granted without fee, provided that
  8. * the above copyright notice appear in all copies and that both that
  9. * copyright notice and this permission notice appear in supporting
  10. * documentation, and that the name of U.M. not be used in advertising or
  11. * publicity pertaining to distribution of the software without specific,
  12. * written prior permission. U.M. makes no representations about the
  13. * suitability of this software for any purpose. It is provided "as is"
  14. * without express or implied warranty.
  15. *
  16. * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
  17. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
  18. * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  19. * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
  20. * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  21. * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  22. *
  23. * Authors: the Amanda Development Team. Its members are listed in a
  24. * file named AUTHORS, in the root directory of this distribution.
  25. */
  26. /*
  27. * $Id: match.c,v 1.23 2006/05/25 01:47:12 johnfranks Exp $
  28. *
  29. * functions for checking and matching regular expressions
  30. */
  31. #include "amanda.h"
  32. #include "match.h"
  33. #include <regex.h>
  34. static int match_word(const char *glob, const char *word, const char separator);
  35. static char *tar_to_regex(const char *glob);
  36. /*
  37. * REGEX MATCHING FUNCTIONS
  38. */
  39. /*
  40. * Define a specific type to hold error messages in case regex compile/matching
  41. * fails
  42. */
  43. typedef char regex_errbuf[STR_SIZE];
  44. /*
  45. * Validate one regular expression. If the regex is invalid, copy the error
  46. * message into the supplied regex_errbuf pointer. Also, we want to know whether
  47. * flags should include REG_NEWLINE (See regcomp(3) for details). Since this is
  48. * the more frequent case, add REG_NEWLINE to the default flags, and remove it
  49. * only if match_newline is set to FALSE.
  50. */
  51. static gboolean do_validate_regex(const char *str, regex_t *regex,
  52. regex_errbuf *errbuf, gboolean match_newline)
  53. {
  54. int flags = REG_EXTENDED | REG_NOSUB | REG_NEWLINE;
  55. int result;
  56. if (!match_newline)
  57. CLR(flags, REG_NEWLINE);
  58. result = regcomp(regex, str, flags);
  59. if (!result)
  60. return TRUE;
  61. regerror(result, regex, *errbuf, SIZEOF(*errbuf));
  62. return FALSE;
  63. }
  64. /*
  65. * See if a string matches a regular expression. Return one of MATCH_* defined
  66. * below. If, for some reason, regexec() returns something other than not 0 or
  67. * REG_NOMATCH, return MATCH_ERROR and print the error message in the supplied
  68. * regex_errbuf.
  69. */
  70. #define MATCH_OK (1)
  71. #define MATCH_NONE (0)
  72. #define MATCH_ERROR (-1)
  73. static int try_match(regex_t *regex, const char *str,
  74. regex_errbuf *errbuf)
  75. {
  76. int result = regexec(regex, str, 0, 0, 0);
  77. switch(result) {
  78. case 0:
  79. return MATCH_OK;
  80. case REG_NOMATCH:
  81. return MATCH_NONE;
  82. /* Fall through: something went really wrong */
  83. }
  84. regerror(result, regex, *errbuf, SIZEOF(*errbuf));
  85. return MATCH_ERROR;
  86. }
  87. char *
  88. validate_regexp(
  89. const char * regex)
  90. {
  91. regex_t regc;
  92. static regex_errbuf errmsg;
  93. gboolean valid;
  94. valid = do_validate_regex(regex, &regc, &errmsg, TRUE);
  95. regfree(&regc);
  96. return (valid) ? NULL : errmsg;
  97. }
  98. char *
  99. clean_regex(
  100. const char * str,
  101. gboolean anchor)
  102. {
  103. char *result;
  104. int j;
  105. size_t i;
  106. result = alloc(2*strlen(str)+3);
  107. j = 0;
  108. if (anchor)
  109. result[j++] = '^';
  110. for(i=0;i<strlen(str);i++) {
  111. if(!isalnum((int)str[i]))
  112. result[j++]='\\';
  113. result[j++]=str[i];
  114. }
  115. if (anchor)
  116. result[j++] = '$';
  117. result[j] = '\0';
  118. return result;
  119. }
  120. /*
  121. * Check whether a given character should be escaped (that is, prepended with a
  122. * backslash), EXCEPT for one character.
  123. */
  124. static gboolean should_be_escaped_except(char c, char not_this_one)
  125. {
  126. if (c == not_this_one)
  127. return FALSE;
  128. switch (c) {
  129. case '\\':
  130. case '^':
  131. case '$':
  132. case '?':
  133. case '*':
  134. case '[':
  135. case ']':
  136. case '.':
  137. case '/':
  138. return TRUE;
  139. }
  140. return FALSE;
  141. }
  142. /*
  143. * Take a disk/host expression and turn it into a full-blown amglob (with
  144. * start and end anchors) following rules in amanda-match(7). The not_this_one
  145. * argument represents a character which is NOT meant to be special in this
  146. * case: '/' for disks and '.' for hosts.
  147. */
  148. static char *full_amglob_from_expression(const char *str, char not_this_one)
  149. {
  150. const char *src;
  151. char *result, *dst;
  152. result = alloc(2 * strlen(str) + 3);
  153. dst = result;
  154. *(dst++) = '^';
  155. for (src = str; *src; src++) {
  156. if (should_be_escaped_except(*src, not_this_one))
  157. *(dst++) = '\\';
  158. *(dst++) = *src;
  159. }
  160. *(dst++) = '$';
  161. *dst = '\0';
  162. return result;
  163. }
  164. char *
  165. make_exact_host_expression(
  166. const char * host)
  167. {
  168. return full_amglob_from_expression(host, '.');
  169. }
  170. char *
  171. make_exact_disk_expression(
  172. const char * disk)
  173. {
  174. return full_amglob_from_expression(disk, '/');
  175. }
  176. int do_match(const char *regex, const char *str, gboolean match_newline)
  177. {
  178. regex_t regc;
  179. int result;
  180. regex_errbuf errmsg;
  181. gboolean ok;
  182. ok = do_validate_regex(regex, &regc, &errmsg, match_newline);
  183. if (!ok)
  184. error(_("regex \"%s\": %s"), regex, errmsg);
  185. /*NOTREACHED*/
  186. result = try_match(&regc, str, &errmsg);
  187. if (result == MATCH_ERROR)
  188. error(_("regex \"%s\": %s"), regex, errmsg);
  189. /*NOTREACHED*/
  190. regfree(&regc);
  191. return result;
  192. }
  193. char *
  194. validate_glob(
  195. const char * glob)
  196. {
  197. char *regex, *ret = NULL;
  198. regex_t regc;
  199. static regex_errbuf errmsg;
  200. regex = glob_to_regex(glob);
  201. if (!do_validate_regex(regex, &regc, &errmsg, TRUE))
  202. ret = errmsg;
  203. regfree(&regc);
  204. amfree(regex);
  205. return ret;
  206. }
  207. int
  208. match_glob(
  209. const char * glob,
  210. const char * str)
  211. {
  212. char *regex;
  213. regex_t regc;
  214. int result;
  215. regex_errbuf errmsg;
  216. gboolean ok;
  217. regex = glob_to_regex(glob);
  218. ok = do_validate_regex(regex, &regc, &errmsg, TRUE);
  219. if (!ok)
  220. error(_("glob \"%s\" -> regex \"%s\": %s"), glob, regex, errmsg);
  221. /*NOTREACHED*/
  222. result = try_match(&regc, str, &errmsg);
  223. if (result == MATCH_ERROR)
  224. error(_("glob \"%s\" -> regex \"%s\": %s"), glob, regex, errmsg);
  225. /*NOTREACHED*/
  226. regfree(&regc);
  227. amfree(regex);
  228. return result;
  229. }
  230. /*
  231. * Macro to tell whether a character is a regex metacharacter. Note that '*'
  232. * and '?' are NOT included: they are themselves special in globs.
  233. */
  234. #define IS_REGEX_META(c) ( \
  235. (c) == '.' || (c) == '(' || (c) == ')' || (c) == '{' || (c) == '}' || \
  236. (c) == '+' || (c) == '^' || (c) == '$' || (c) == '|' \
  237. )
  238. /*
  239. * EXPANDING A MATCH TO A REGEX (as per amanda-match(7))
  240. *
  241. * The function at the code of this operation is amglob_to_regex(). It
  242. * takes three arguments: the string to convert, a substitution table and a
  243. * worst-case expansion.
  244. *
  245. * The substitution table, defined right below, is used to replace particular
  246. * string positions and/or characters. Its fields are:
  247. * - begin: what the beginnin of the string should be replaced with;
  248. * - end: what the end of the string should be replaced with;
  249. * - question_mark: what the question mark ('?') should be replaced with;
  250. * - star: what the star ('*') should be replaced with;
  251. * - double_star: what two consecutive stars should be replaced with.
  252. *
  253. * Note that apart from double_star, ALL OTHER FIELDS MUST NOT BE NULL
  254. */
  255. struct subst_table {
  256. const char *begin;
  257. const char *end;
  258. const char *question_mark;
  259. const char *star;
  260. const char *double_star;
  261. };
  262. static char *amglob_to_regex(const char *str, struct subst_table *table,
  263. size_t worst_case)
  264. {
  265. const char *src;
  266. char *result, *dst;
  267. char c;
  268. /*
  269. * There are two particular cases when building a regex out of a glob:
  270. * character classes (anything inside [...] or [!...] and quotes (anything
  271. * preceded by a backslash). We start with none being true.
  272. */
  273. gboolean in_character_class = FALSE, in_quote = FALSE;
  274. /*
  275. * Allocate enough space for our string. At worst, the allocated space is
  276. * the length of the following:
  277. * - beginning of regex;
  278. * - size of original string multiplied by worst-case expansion;
  279. * - end of regex;
  280. * - final 0.
  281. */
  282. result = alloc(strlen(table->begin) + strlen(str) * worst_case
  283. + strlen(table->end) + 1);
  284. /*
  285. * Start by copying the beginning of the regex...
  286. */
  287. dst = g_stpcpy(result, table->begin);
  288. /*
  289. * ... Now to the meat of it.
  290. */
  291. for (src = str; *src; src++) {
  292. c = *src;
  293. /*
  294. * First, check that we're in a character class: each and every
  295. * character can be copied as is. We only need to be careful is the
  296. * character is a closing bracket: it will end the character class IF
  297. * AND ONLY IF it is not preceded by a backslash.
  298. */
  299. if (in_character_class) {
  300. in_character_class = ((c != ']') || (*(src - 1) == '\\'));
  301. goto straight_copy;
  302. }
  303. /*
  304. * Are we in a quote? If yes, it is really simple: copy the current
  305. * character, close the quote, the end.
  306. */
  307. if (in_quote) {
  308. in_quote = FALSE;
  309. goto straight_copy;
  310. }
  311. /*
  312. * The only thing left to handle now is the "normal" case: we are not in
  313. * a character class nor in a quote.
  314. */
  315. if (c == '\\') {
  316. /*
  317. * Backslash: append it, and open a new quote.
  318. */
  319. in_quote = TRUE;
  320. goto straight_copy;
  321. } else if (c == '[') {
  322. /*
  323. * Opening bracket: the beginning of a character class.
  324. *
  325. * Look ahead the next character: if it's an exclamation mark, then
  326. * this is a complemented character class; append a caret to make
  327. * the result string regex-friendly, and forward one character in
  328. * advance.
  329. */
  330. *dst++ = c;
  331. in_character_class = TRUE;
  332. if (*(src + 1) == '!') {
  333. *dst++ = '^';
  334. src++;
  335. }
  336. } else if (IS_REGEX_META(c)) {
  337. /*
  338. * Regex metacharacter (except for ? and *, see below): append a
  339. * backslash, and then the character itself.
  340. */
  341. *dst++ = '\\';
  342. goto straight_copy;
  343. } else if (c == '?')
  344. /*
  345. * Question mark: take the subsitution string out of our subst_table
  346. * and append it to the string.
  347. */
  348. dst = g_stpcpy(dst, table->question_mark);
  349. else if (c == '*') {
  350. /*
  351. * Star: append the subsitution string found in our subst_table.
  352. * However, look forward the next character: if it's yet another
  353. * star, then see if there is a substitution string for the double
  354. * star and append this one instead.
  355. *
  356. * FIXME: this means that two consecutive stars in a glob string
  357. * where there is no substition for double_star can lead to
  358. * exponential regex execution time: consider [^/]*[^/]*.
  359. */
  360. const char *p = table->star;
  361. if (*(src + 1) == '*' && table->double_star) {
  362. src++;
  363. p = table->double_star;
  364. }
  365. dst = g_stpcpy(dst, p);
  366. } else {
  367. /*
  368. * Any other character: append each time.
  369. */
  370. straight_copy:
  371. *dst++ = c;
  372. }
  373. }
  374. /*
  375. * Done, now append the end, ONLY if we are not in a quote - a lone
  376. * backslash at the end of a glob is illegal, just leave it as it, it will
  377. * make the regex compile fail.
  378. */
  379. if (!in_quote)
  380. dst = g_stpcpy(dst, table->end);
  381. /*
  382. * Finalize, return.
  383. */
  384. *dst = '\0';
  385. return result;
  386. }
  387. static struct subst_table glob_subst_stable = {
  388. "^", /* begin */
  389. "$", /* end */
  390. "[^/]", /* question_mark */
  391. "[^/]*", /* star */
  392. NULL /* double_star */
  393. };
  394. static size_t glob_worst_case = 5; /* star */
  395. char *
  396. glob_to_regex(
  397. const char * glob)
  398. {
  399. return amglob_to_regex(glob, &glob_subst_stable, glob_worst_case);
  400. }
  401. int
  402. match_tar(
  403. const char * glob,
  404. const char * str)
  405. {
  406. char *regex;
  407. regex_t regc;
  408. int result;
  409. regex_errbuf errmsg;
  410. gboolean ok;
  411. regex = tar_to_regex(glob);
  412. ok = do_validate_regex(regex, &regc, &errmsg, TRUE);
  413. if (!ok)
  414. error(_("glob \"%s\" -> regex \"%s\": %s"), glob, regex, errmsg);
  415. /*NOTREACHED*/
  416. result = try_match(&regc, str, &errmsg);
  417. if (result == MATCH_ERROR)
  418. error(_("glob \"%s\" -> regex \"%s\": %s"), glob, regex, errmsg);
  419. /*NOTREACHED*/
  420. regfree(&regc);
  421. amfree(regex);
  422. return result;
  423. }
  424. static struct subst_table tar_subst_stable = {
  425. "(^|/)", /* begin */
  426. "($|/)", /* end */
  427. "[^/]", /* question_mark */
  428. ".*", /* star */
  429. NULL /* double_star */
  430. };
  431. static size_t tar_worst_case = 5; /* begin or end */
  432. static char *
  433. tar_to_regex(
  434. const char * glob)
  435. {
  436. return amglob_to_regex(glob, &tar_subst_stable, tar_worst_case);
  437. }
  438. /*
  439. * Two utility functions used by match_disk() below: they are used to convert a
  440. * disk and glob from Windows expressed paths (backslashes) into Unix paths
  441. * (slashes).
  442. *
  443. * Note: the resulting string is dynamically allocated, it is up to the caller
  444. * to free it.
  445. *
  446. * Note 2: UNC in convert_unc_to_unix stands for Uniform Naming Convention.
  447. */
  448. static char *convert_unc_to_unix(const char *unc)
  449. {
  450. const char *src;
  451. char *result, *dst;
  452. result = alloc(strlen(unc) + 1);
  453. dst = result;
  454. for (src = unc; *src; src++)
  455. *(dst++) = (*src == '\\') ? '/' : *src;
  456. *dst = '\0';
  457. return result;
  458. }
  459. static char *convert_winglob_to_unix(const char *glob)
  460. {
  461. const char *src;
  462. char *result, *dst;
  463. result = alloc(strlen(glob) + 1);
  464. dst = result;
  465. for (src = glob; *src; src++) {
  466. if (*src == '\\' && *(src + 1) == '\\') {
  467. *(dst++) = '/';
  468. src++;
  469. continue;
  470. }
  471. *(dst++) = *src;
  472. }
  473. *dst = '\0';
  474. return result;
  475. }
  476. /*
  477. * Check whether a glob passed as an argument to match_word() only looks for the
  478. * separator
  479. */
  480. static gboolean glob_is_separator_only(const char *glob, char sep) {
  481. size_t len = strlen(glob);
  482. const char len2_1[] = { '^', sep , 0 }, len2_2[] = { sep, '$', 0 },
  483. len3[] = { '^', sep, '$', 0 };
  484. switch (len) {
  485. case 1:
  486. return (*glob == sep);
  487. case 2:
  488. return !(strcmp(glob, len2_1) && strcmp(glob, len2_2));
  489. case 3:
  490. return !strcmp(glob, len3);
  491. default:
  492. return FALSE;
  493. }
  494. }
  495. static int
  496. match_word(
  497. const char * glob,
  498. const char * word,
  499. const char separator)
  500. {
  501. char *regex;
  502. char *dst;
  503. size_t len;
  504. size_t lenword;
  505. char *nword;
  506. char *nglob;
  507. const char *src;
  508. int ret;
  509. lenword = strlen(word);
  510. nword = (char *)alloc(lenword + 3);
  511. dst = nword;
  512. src = word;
  513. if(lenword == 1 && *src == separator) {
  514. *dst++ = separator;
  515. *dst++ = separator;
  516. }
  517. else {
  518. if(*src != separator)
  519. *dst++ = separator;
  520. while(*src != '\0')
  521. *dst++ = *src++;
  522. if(*(dst-1) != separator)
  523. *dst++ = separator;
  524. }
  525. *dst = '\0';
  526. len = strlen(glob);
  527. nglob = stralloc(glob);
  528. if(glob_is_separator_only(nglob, separator)) {
  529. regex = alloc(7); /* Length of what is written below plus '\0' */
  530. dst = regex;
  531. *dst++ = '^';
  532. *dst++ = '\\';
  533. *dst++ = separator;
  534. *dst++ = '\\';
  535. *dst++ = separator;
  536. *dst++ = '$';
  537. *dst = '\0';
  538. } else {
  539. /*
  540. * Unlike what happens for tar and disk expressions, here the
  541. * substitution table needs to be dynamically allocated. When we enter
  542. * here, we know what the expansions will be for the question mark, the
  543. * star and the double star, and also the worst case expansion. We
  544. * calculate the begin and end expansions below.
  545. */
  546. #define MATCHWORD_STAR_EXPANSION(c) (const char []) { \
  547. '[', '^', (c), ']', '*', 0 \
  548. }
  549. #define MATCHWORD_QUESTIONMARK_EXPANSION(c) (const char []) { \
  550. '[', '^', (c), ']', 0 \
  551. }
  552. #define MATCHWORD_DOUBLESTAR_EXPANSION ".*"
  553. struct subst_table table;
  554. size_t worst_case = 5;
  555. const char *begin, *end;
  556. char *p, *g = nglob;
  557. /*
  558. * Calculate the beginning of the regex:
  559. * - by default, it is an unanchored separator;
  560. * - if the glob begins with a caret, make that an anchored separator,
  561. * and increment g appropriately;
  562. * - if it begins with a separator, make it the empty string.
  563. */
  564. p = nglob;
  565. #define REGEX_BEGIN_FULL(c) (const char[]) { '^', '\\', (c), 0 }
  566. #define REGEX_BEGIN_NOANCHOR(c) (const char[]) { '\\', (c), 0 }
  567. #define REGEX_BEGIN_ANCHORONLY "^" /* Unused, but defined for consistency */
  568. #define REGEX_BEGIN_EMPTY ""
  569. begin = REGEX_BEGIN_NOANCHOR(separator);
  570. if (*p == '^') {
  571. begin = REGEX_BEGIN_FULL(separator);
  572. p++, g++;
  573. if (*p == separator)
  574. g++;
  575. } else if (*p == separator)
  576. begin = REGEX_BEGIN_EMPTY;
  577. /*
  578. * Calculate the end of the regex:
  579. * - an unanchored separator by default;
  580. * - if the last character is a backslash or the separator itself, it
  581. * should be the empty string;
  582. * - if it is a dollar sign, overwrite it with 0 and look at the
  583. * character before it: if it is the separator, only anchor at the
  584. * end, otherwise, add a separator before the anchor.
  585. */
  586. p = &(nglob[strlen(nglob) - 1]);
  587. #define REGEX_END_FULL(c) (const char[]) { '\\', (c), '$', 0 }
  588. #define REGEX_END_NOANCHOR(c) REGEX_BEGIN_NOANCHOR(c)
  589. #define REGEX_END_ANCHORONLY "$"
  590. #define REGEX_END_EMPTY REGEX_BEGIN_EMPTY
  591. end = REGEX_END_NOANCHOR(separator);
  592. if (*p == '\\' || *p == separator)
  593. end = REGEX_END_EMPTY;
  594. else if (*p == '$') {
  595. char prev = *(p - 1);
  596. *p = '\0';
  597. if (prev == separator)
  598. end = REGEX_END_ANCHORONLY;
  599. else
  600. end = REGEX_END_FULL(separator);
  601. }
  602. /*
  603. * Fill in our substitution table and generate the regex
  604. */
  605. table.begin = begin;
  606. table.end = end;
  607. table.question_mark = MATCHWORD_QUESTIONMARK_EXPANSION(separator);
  608. table.star = MATCHWORD_STAR_EXPANSION(separator);
  609. table.double_star = MATCHWORD_DOUBLESTAR_EXPANSION;
  610. regex = amglob_to_regex(g, &table, worst_case);
  611. }
  612. ret = do_match(regex, nword, TRUE);
  613. amfree(nword);
  614. amfree(nglob);
  615. amfree(regex);
  616. return ret;
  617. }
  618. int
  619. match_host(
  620. const char * glob,
  621. const char * host)
  622. {
  623. char *lglob, *lhost;
  624. int ret;
  625. lglob = g_ascii_strdown(glob, -1);
  626. lhost = g_ascii_strdown(host, -1);
  627. ret = match_word(lglob, lhost, '.');
  628. amfree(lglob);
  629. amfree(lhost);
  630. return ret;
  631. }
  632. int
  633. match_disk(
  634. const char * glob,
  635. const char * disk)
  636. {
  637. char *glob2 = NULL, *disk2 = NULL;
  638. const char *g = glob, *d = disk;
  639. int result;
  640. /*
  641. * Check whether our disk potentially refers to a Windows share (the first
  642. * two characters are '\' and there is no / in the word at all): if yes,
  643. * convert all double backslashes to slashes in the glob, and simple
  644. * backslashes into slashes in the disk, and pass these new strings as
  645. * arguments instead of the originals.
  646. */
  647. gboolean windows_share = !(strncmp(disk, "\\\\", 2) || strchr(disk, '/'));
  648. if (windows_share) {
  649. glob2 = convert_winglob_to_unix(glob);
  650. disk2 = convert_unc_to_unix(disk);
  651. g = (const char *) glob2;
  652. d = (const char *) disk2;
  653. }
  654. result = match_word(g, d, '/');
  655. /*
  656. * We can amfree(NULL), so this is "safe"
  657. */
  658. amfree(glob2);
  659. amfree(disk2);
  660. return result;
  661. }
  662. static int
  663. alldigits(
  664. const char *str)
  665. {
  666. while (*str) {
  667. if (!isdigit((int)*(str++)))
  668. return 0;
  669. }
  670. return 1;
  671. }
  672. int
  673. match_datestamp(
  674. const char * dateexp,
  675. const char * datestamp)
  676. {
  677. char *dash;
  678. size_t len, len_suffix;
  679. size_t len_prefix;
  680. char firstdate[100], lastdate[100];
  681. char mydateexp[100];
  682. int match_exact;
  683. if(strlen(dateexp) >= 100 || strlen(dateexp) < 1) {
  684. goto illegal;
  685. }
  686. /* strip and ignore an initial "^" */
  687. if(dateexp[0] == '^') {
  688. strncpy(mydateexp, dateexp+1, sizeof(mydateexp)-1);
  689. mydateexp[sizeof(mydateexp)-1] = '\0';
  690. }
  691. else {
  692. strncpy(mydateexp, dateexp, sizeof(mydateexp)-1);
  693. mydateexp[sizeof(mydateexp)-1] = '\0';
  694. }
  695. if(mydateexp[strlen(mydateexp)-1] == '$') {
  696. match_exact = 1;
  697. mydateexp[strlen(mydateexp)-1] = '\0'; /* strip the trailing $ */
  698. }
  699. else
  700. match_exact = 0;
  701. /* a single dash represents a date range */
  702. if((dash = strchr(mydateexp,'-'))) {
  703. if(match_exact == 1 || strchr(dash+1, '-')) {
  704. goto illegal;
  705. }
  706. /* format: XXXYYYY-ZZZZ, indicating dates XXXYYYY to XXXZZZZ */
  707. len = (size_t)(dash - mydateexp); /* length of XXXYYYY */
  708. len_suffix = strlen(dash) - 1; /* length of ZZZZ */
  709. if (len_suffix > len) goto illegal;
  710. len_prefix = len - len_suffix; /* length of XXX */
  711. dash++;
  712. strncpy(firstdate, mydateexp, len);
  713. firstdate[len] = '\0';
  714. strncpy(lastdate, mydateexp, len_prefix);
  715. strncpy(&(lastdate[len_prefix]), dash, len_suffix);
  716. lastdate[len] = '\0';
  717. if (!alldigits(firstdate) || !alldigits(lastdate))
  718. goto illegal;
  719. if (strncmp(firstdate, lastdate, strlen(firstdate)) > 0)
  720. goto illegal;
  721. return ((strncmp(datestamp, firstdate, strlen(firstdate)) >= 0) &&
  722. (strncmp(datestamp, lastdate , strlen(lastdate)) <= 0));
  723. }
  724. else {
  725. if (!alldigits(mydateexp))
  726. goto illegal;
  727. if(match_exact == 1) {
  728. return (strcmp(datestamp, mydateexp) == 0);
  729. }
  730. else {
  731. return (strncmp(datestamp, mydateexp, strlen(mydateexp)) == 0);
  732. }
  733. }
  734. illegal:
  735. error(_("Illegal datestamp expression %s"),dateexp);
  736. /*NOTREACHED*/
  737. }
  738. int
  739. match_level(
  740. const char * levelexp,
  741. const char * level)
  742. {
  743. char *dash;
  744. long int low, hi, level_i;
  745. char mylevelexp[100];
  746. int match_exact;
  747. if(strlen(levelexp) >= 100 || strlen(levelexp) < 1) {
  748. error(_("Illegal level expression %s"),levelexp);
  749. /*NOTREACHED*/
  750. }
  751. if(levelexp[0] == '^') {
  752. strncpy(mylevelexp, levelexp+1, strlen(levelexp)-1);
  753. mylevelexp[strlen(levelexp)-1] = '\0';
  754. }
  755. else {
  756. strncpy(mylevelexp, levelexp, strlen(levelexp));
  757. mylevelexp[strlen(levelexp)] = '\0';
  758. }
  759. if(mylevelexp[strlen(mylevelexp)-1] == '$') {
  760. match_exact = 1;
  761. mylevelexp[strlen(mylevelexp)-1] = '\0';
  762. }
  763. else
  764. match_exact = 0;
  765. if((dash = strchr(mylevelexp,'-'))) {
  766. if(match_exact == 1) {
  767. goto illegal;
  768. }
  769. *dash = '\0';
  770. if (!alldigits(mylevelexp) || !alldigits(dash+1)) goto illegal;
  771. errno = 0;
  772. low = strtol(mylevelexp, (char **) NULL, 10);
  773. if (errno) goto illegal;
  774. hi = strtol(dash+1, (char **) NULL, 10);
  775. if (errno) goto illegal;
  776. level_i = strtol(level, (char **) NULL, 10);
  777. if (errno) goto illegal;
  778. return ((level_i >= low) && (level_i <= hi));
  779. }
  780. else {
  781. if (!alldigits(mylevelexp)) goto illegal;
  782. if(match_exact == 1) {
  783. return (strcmp(level, mylevelexp) == 0);
  784. }
  785. else {
  786. return (strncmp(level, mylevelexp, strlen(mylevelexp)) == 0);
  787. }
  788. }
  789. illegal:
  790. error(_("Illegal level expression %s"),levelexp);
  791. /*NOTREACHED*/
  792. }