matcher.c 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869
  1. /*
  2. * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
  3. * Copyright (C) 2002-2004 by the Claws Mail Team and Hiroyuki Yamamoto
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program; if not, write to the Free Software
  17. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. */
  19. #ifdef HAVE_CONFIG_H
  20. # include "config.h"
  21. #endif
  22. #include <glib.h>
  23. #include <glib/gi18n.h>
  24. #include <ctype.h>
  25. #include <string.h>
  26. #include <stdlib.h>
  27. #include <errno.h>
  28. #ifdef USE_PTHREAD
  29. #include <pthread.h>
  30. #endif
  31. #include "defs.h"
  32. #include "utils.h"
  33. #include "procheader.h"
  34. #include "matcher.h"
  35. #include "matcher_parser.h"
  36. #include "prefs_gtk.h"
  37. #include "addr_compl.h"
  38. #include "codeconv.h"
  39. #include "quoted-printable.h"
  40. #include "claws.h"
  41. #include <ctype.h>
  42. /*!
  43. *\brief Keyword lookup element
  44. */
  45. struct _MatchParser {
  46. gint id; /*!< keyword id */
  47. gchar *str; /*!< keyword */
  48. };
  49. typedef struct _MatchParser MatchParser;
  50. /*!
  51. *\brief Table with strings and ids used by the lexer and
  52. * the parser. New keywords can be added here.
  53. */
  54. static const MatchParser matchparser_tab[] = {
  55. /* msginfo flags */
  56. {MATCHCRITERIA_ALL, "all"},
  57. {MATCHCRITERIA_UNREAD, "unread"},
  58. {MATCHCRITERIA_NOT_UNREAD, "~unread"},
  59. {MATCHCRITERIA_NEW, "new"},
  60. {MATCHCRITERIA_NOT_NEW, "~new"},
  61. {MATCHCRITERIA_MARKED, "marked"},
  62. {MATCHCRITERIA_NOT_MARKED, "~marked"},
  63. {MATCHCRITERIA_DELETED, "deleted"},
  64. {MATCHCRITERIA_NOT_DELETED, "~deleted"},
  65. {MATCHCRITERIA_REPLIED, "replied"},
  66. {MATCHCRITERIA_NOT_REPLIED, "~replied"},
  67. {MATCHCRITERIA_FORWARDED, "forwarded"},
  68. {MATCHCRITERIA_NOT_FORWARDED, "~forwarded"},
  69. {MATCHCRITERIA_LOCKED, "locked"},
  70. {MATCHCRITERIA_NOT_LOCKED, "~locked"},
  71. {MATCHCRITERIA_COLORLABEL, "colorlabel"},
  72. {MATCHCRITERIA_NOT_COLORLABEL, "~colorlabel"},
  73. {MATCHCRITERIA_IGNORE_THREAD, "ignore_thread"},
  74. {MATCHCRITERIA_NOT_IGNORE_THREAD, "~ignore_thread"},
  75. /* msginfo headers */
  76. {MATCHCRITERIA_SUBJECT, "subject"},
  77. {MATCHCRITERIA_NOT_SUBJECT, "~subject"},
  78. {MATCHCRITERIA_FROM, "from"},
  79. {MATCHCRITERIA_NOT_FROM, "~from"},
  80. {MATCHCRITERIA_TO, "to"},
  81. {MATCHCRITERIA_NOT_TO, "~to"},
  82. {MATCHCRITERIA_CC, "cc"},
  83. {MATCHCRITERIA_NOT_CC, "~cc"},
  84. {MATCHCRITERIA_TO_OR_CC, "to_or_cc"},
  85. {MATCHCRITERIA_NOT_TO_AND_NOT_CC, "~to_or_cc"},
  86. {MATCHCRITERIA_AGE_GREATER, "age_greater"},
  87. {MATCHCRITERIA_AGE_LOWER, "age_lower"},
  88. {MATCHCRITERIA_NEWSGROUPS, "newsgroups"},
  89. {MATCHCRITERIA_NOT_NEWSGROUPS, "~newsgroups"},
  90. {MATCHCRITERIA_INREPLYTO, "inreplyto"},
  91. {MATCHCRITERIA_NOT_INREPLYTO, "~inreplyto"},
  92. {MATCHCRITERIA_REFERENCES, "references"},
  93. {MATCHCRITERIA_NOT_REFERENCES, "~references"},
  94. {MATCHCRITERIA_SCORE_GREATER, "score_greater"},
  95. {MATCHCRITERIA_SCORE_LOWER, "score_lower"},
  96. {MATCHCRITERIA_SCORE_EQUAL, "score_equal"},
  97. {MATCHCRITERIA_PARTIAL, "partial"},
  98. {MATCHCRITERIA_NOT_PARTIAL, "~partial"},
  99. {MATCHCRITERIA_FOUND_IN_ADDRESSBOOK, "found_in_addressbook"},
  100. {MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK, "~found_in_addressbook"},
  101. {MATCHCRITERIA_SIZE_GREATER, "size_greater"},
  102. {MATCHCRITERIA_SIZE_SMALLER, "size_smaller"},
  103. {MATCHCRITERIA_SIZE_EQUAL, "size_equal"},
  104. /* content have to be read */
  105. {MATCHCRITERIA_HEADER, "header"},
  106. {MATCHCRITERIA_NOT_HEADER, "~header"},
  107. {MATCHCRITERIA_HEADERS_PART, "headers_part"},
  108. {MATCHCRITERIA_NOT_HEADERS_PART, "~headers_part"},
  109. {MATCHCRITERIA_MESSAGE, "message"},
  110. {MATCHCRITERIA_NOT_MESSAGE, "~message"},
  111. {MATCHCRITERIA_BODY_PART, "body_part"},
  112. {MATCHCRITERIA_NOT_BODY_PART, "~body_part"},
  113. {MATCHCRITERIA_TEST, "test"},
  114. {MATCHCRITERIA_NOT_TEST, "~test"},
  115. /* match type */
  116. {MATCHTYPE_MATCHCASE, "matchcase"},
  117. {MATCHTYPE_MATCH, "match"},
  118. {MATCHTYPE_REGEXPCASE, "regexpcase"},
  119. {MATCHTYPE_REGEXP, "regexp"},
  120. /* actions */
  121. {MATCHACTION_SCORE, "score"}, /* for backward compatibility */
  122. {MATCHACTION_MOVE, "move"},
  123. {MATCHACTION_COPY, "copy"},
  124. {MATCHACTION_DELETE, "delete"},
  125. {MATCHACTION_MARK, "mark"},
  126. {MATCHACTION_UNMARK, "unmark"},
  127. {MATCHACTION_LOCK, "lock"},
  128. {MATCHACTION_UNLOCK, "unlock"},
  129. {MATCHACTION_MARK_AS_READ, "mark_as_read"},
  130. {MATCHACTION_MARK_AS_UNREAD, "mark_as_unread"},
  131. {MATCHACTION_FORWARD, "forward"},
  132. {MATCHACTION_FORWARD_AS_ATTACHMENT, "forward_as_attachment"},
  133. {MATCHACTION_EXECUTE, "execute"},
  134. {MATCHACTION_COLOR, "color"},
  135. {MATCHACTION_REDIRECT, "redirect"},
  136. {MATCHACTION_CHANGE_SCORE, "change_score"},
  137. {MATCHACTION_SET_SCORE, "set_score"},
  138. {MATCHACTION_STOP, "stop"},
  139. {MATCHACTION_HIDE, "hide"},
  140. {MATCHACTION_IGNORE, "ignore"},
  141. };
  142. enum {
  143. MATCH_ANY = 0,
  144. MATCH_ALL = 1,
  145. MATCH_ONE = 2
  146. };
  147. /*!
  148. *\brief Look up table with keywords defined in \sa matchparser_tab
  149. */
  150. static GHashTable *matchparser_hashtab;
  151. /*!
  152. *\brief Translate keyword id to keyword string
  153. *
  154. *\param id Id of keyword
  155. *
  156. *\return const gchar * Keyword
  157. */
  158. const gchar *get_matchparser_tab_str(gint id)
  159. {
  160. gint i;
  161. for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++) {
  162. if (matchparser_tab[i].id == id)
  163. return matchparser_tab[i].str;
  164. }
  165. return NULL;
  166. }
  167. /*!
  168. *\brief Create keyword lookup table
  169. */
  170. static void create_matchparser_hashtab(void)
  171. {
  172. int i;
  173. if (matchparser_hashtab) return;
  174. matchparser_hashtab = g_hash_table_new(g_str_hash, g_str_equal);
  175. for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++)
  176. g_hash_table_insert(matchparser_hashtab,
  177. matchparser_tab[i].str,
  178. (gpointer) &matchparser_tab[i]);
  179. }
  180. /*!
  181. *\brief Return a keyword id from a keyword string
  182. *
  183. *\param str Keyword string
  184. *
  185. *\return gint Keyword id
  186. */
  187. gint get_matchparser_tab_id(const gchar *str)
  188. {
  189. MatchParser *res;
  190. if (NULL != (res = g_hash_table_lookup(matchparser_hashtab, str))) {
  191. return res->id;
  192. } else
  193. return -1;
  194. }
  195. /* **************** data structure allocation **************** */
  196. /*!
  197. *\brief Allocate a structure for a filtering / scoring
  198. * "condition" (a matcher structure)
  199. *
  200. *\param criteria Criteria ID (MATCHCRITERIA_XXXX)
  201. *\param header Header string (if criteria is MATCHCRITERIA_HEADER
  202. or MATCHCRITERIA_FOUND_IN_ADDRESSBOOK)
  203. *\param matchtype Type of action (MATCHTYPE_XXX)
  204. *\param expr String value or expression to check
  205. *\param value Integer value to check
  206. *
  207. *\return MatcherProp * Pointer to newly allocated structure
  208. */
  209. MatcherProp *matcherprop_new(gint criteria, const gchar *header,
  210. gint matchtype, const gchar *expr,
  211. int value)
  212. {
  213. MatcherProp *prop;
  214. prop = g_new0(MatcherProp, 1);
  215. prop->criteria = criteria;
  216. prop->header = header != NULL ? g_strdup(header) : NULL;
  217. prop->expr = expr != NULL ? g_strdup(expr) : NULL;
  218. prop->matchtype = matchtype;
  219. prop->preg = NULL;
  220. prop->value = value;
  221. prop->error = 0;
  222. return prop;
  223. }
  224. /*!
  225. *\brief Free a matcher structure
  226. *
  227. *\param prop Pointer to matcher structure allocated with
  228. * #matcherprop_new
  229. */
  230. void matcherprop_free(MatcherProp *prop)
  231. {
  232. g_free(prop->expr);
  233. g_free(prop->header);
  234. if (prop->preg != NULL) {
  235. regfree(prop->preg);
  236. g_free(prop->preg);
  237. }
  238. g_free(prop);
  239. }
  240. /*!
  241. *\brief Copy a matcher structure
  242. *
  243. *\param src Matcher structure to copy
  244. *
  245. *\return MatcherProp * Pointer to newly allocated matcher structure
  246. */
  247. MatcherProp *matcherprop_copy(const MatcherProp *src)
  248. {
  249. MatcherProp *prop = g_new0(MatcherProp, 1);
  250. prop->criteria = src->criteria;
  251. prop->header = src->header ? g_strdup(src->header) : NULL;
  252. prop->expr = src->expr ? g_strdup(src->expr) : NULL;
  253. prop->matchtype = src->matchtype;
  254. prop->preg = NULL; /* will be re-evaluated */
  255. prop->value = src->value;
  256. prop->error = src->error;
  257. return prop;
  258. }
  259. /* ************** match ******************************/
  260. static gboolean match_with_addresses_in_addressbook
  261. (MatcherProp *prop, GSList *address_list, gint type,
  262. gchar* folderpath, gint match)
  263. {
  264. GSList *walk = NULL;
  265. gboolean found = FALSE;
  266. gchar *path = NULL;
  267. g_return_val_if_fail(address_list != NULL, FALSE);
  268. debug_print("match_with_addresses_in_addressbook(%d, %s)\n",
  269. g_slist_length(address_list), folderpath);
  270. if (folderpath == NULL ||
  271. strcasecmp(folderpath, _("Any")) == 0 ||
  272. *folderpath == '\0')
  273. path = NULL;
  274. else
  275. path = folderpath;
  276. start_address_completion(path);
  277. for (walk = address_list; walk != NULL; walk = walk->next) {
  278. /* exact matching of email address */
  279. guint num_addr = complete_address(walk->data);
  280. found = FALSE;
  281. if (num_addr > 1) {
  282. /* skip first item (this is the search string itself) */
  283. int i = 1;
  284. for (; i < num_addr && !found; i++) {
  285. gchar *addr = get_complete_address(i);
  286. extract_address(addr);
  287. if (strcasecmp(addr, walk->data) == 0)
  288. found = TRUE;
  289. g_free(addr);
  290. }
  291. }
  292. g_free(walk->data);
  293. if (match == MATCH_ALL) {
  294. /* if matching all addresses, stop if one doesn't match */
  295. if (!found)
  296. break;
  297. } else if (match == MATCH_ANY) {
  298. /* if matching any address, stop if one does match */
  299. if (found)
  300. break;
  301. }
  302. /* MATCH_ONE: there should be only one loop iteration */
  303. }
  304. end_address_completion();
  305. return found;
  306. }
  307. /*!
  308. *\brief Find out if a string matches a condition
  309. *
  310. *\param prop Matcher structure
  311. *\param str String to check
  312. *
  313. *\return gboolean TRUE if str matches the condition in the
  314. * matcher structure
  315. */
  316. static gboolean matcherprop_string_match(MatcherProp *prop, const gchar *str)
  317. {
  318. gchar *str1;
  319. gchar *str2;
  320. if (str == NULL)
  321. return FALSE;
  322. switch (prop->matchtype) {
  323. case MATCHTYPE_REGEXPCASE:
  324. case MATCHTYPE_REGEXP:
  325. if (!prop->preg && (prop->error == 0)) {
  326. prop->preg = g_new0(regex_t, 1);
  327. /* if regexp then don't use the escaped string */
  328. if (regcomp(prop->preg, prop->expr,
  329. REG_NOSUB | REG_EXTENDED
  330. | ((prop->matchtype == MATCHTYPE_REGEXPCASE)
  331. ? REG_ICASE : 0)) != 0) {
  332. prop->error = 1;
  333. g_free(prop->preg);
  334. prop->preg = NULL;
  335. }
  336. }
  337. if (prop->preg == NULL)
  338. return FALSE;
  339. if (regexec(prop->preg, str, 0, NULL, 0) == 0)
  340. return TRUE;
  341. else
  342. return FALSE;
  343. case MATCHTYPE_MATCH:
  344. return (strstr(str, prop->expr) != NULL);
  345. /* FIXME: put upper in unesc_str */
  346. case MATCHTYPE_MATCHCASE:
  347. str2 = alloca(strlen(prop->expr) + 1);
  348. strcpy(str2, prop->expr);
  349. g_strup(str2);
  350. str1 = alloca(strlen(str) + 1);
  351. strcpy(str1, str);
  352. g_strup(str1);
  353. return (strstr(str1, str2) != NULL);
  354. default:
  355. return FALSE;
  356. }
  357. }
  358. /* FIXME body search is a hack. */
  359. static gboolean matcherprop_string_decode_match(MatcherProp *prop, const gchar *str)
  360. {
  361. gchar *utf = NULL;
  362. gchar tmp[BUFFSIZE];
  363. gboolean res = FALSE;
  364. if (str == NULL)
  365. return FALSE;
  366. /* we try to decode QP first, because it's faster than base64 */
  367. qp_decode_const(tmp, BUFFSIZE-1, str);
  368. if (!g_utf8_validate(tmp, -1, NULL)) {
  369. utf = conv_codeset_strdup
  370. (tmp, conv_get_locale_charset_str_no_utf8(),
  371. CS_INTERNAL);
  372. res = matcherprop_string_match(prop, utf);
  373. g_free(utf);
  374. } else {
  375. res = matcherprop_string_match(prop, tmp);
  376. }
  377. if (res == FALSE && (strchr(prop->expr, '=') || strchr(prop->expr, '_')
  378. || strchr(str, '=') || strchr(str, '_'))) {
  379. /* if searching for something with an equal char, maybe
  380. * we should try to match the non-decoded string.
  381. * In case it was not qp-encoded. */
  382. if (!g_utf8_validate(str, -1, NULL)) {
  383. utf = conv_codeset_strdup
  384. (str, conv_get_locale_charset_str_no_utf8(),
  385. CS_INTERNAL);
  386. res = matcherprop_string_match(prop, utf);
  387. g_free(utf);
  388. } else {
  389. res = matcherprop_string_match(prop, str);
  390. }
  391. }
  392. /* FIXME base64 decoding is too slow, especially since text can
  393. * easily be handled as base64. Don't even try now. */
  394. return res;
  395. }
  396. #ifdef USE_PTHREAD
  397. typedef struct _thread_data {
  398. const gchar *cmd;
  399. gboolean done;
  400. } thread_data;
  401. #endif
  402. #ifdef USE_PTHREAD
  403. void *matcher_test_thread(void *data)
  404. {
  405. thread_data *td = (thread_data *)data;
  406. int result = -1;
  407. pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
  408. pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
  409. result = system(td->cmd);
  410. if (result) perror("system");
  411. td->done = TRUE; /* let the caller thread join() */
  412. return GINT_TO_POINTER(result);
  413. }
  414. #endif
  415. /*!
  416. *\brief Execute a command defined in the matcher structure
  417. *
  418. *\param prop Pointer to matcher structure
  419. *\param info Pointer to message info structure
  420. *
  421. *\return gboolean TRUE if command was executed succesfully
  422. */
  423. static gboolean matcherprop_match_test(const MatcherProp *prop,
  424. MsgInfo *info)
  425. {
  426. gchar *file;
  427. gchar *cmd;
  428. gint retval;
  429. #ifdef USE_PTHREAD
  430. pthread_t pt;
  431. thread_data *td = g_new0(thread_data, 1);
  432. void *res = NULL;
  433. time_t start_time = time(NULL);
  434. #endif
  435. file = procmsg_get_message_file(info);
  436. if (file == NULL)
  437. return FALSE;
  438. g_free(file);
  439. cmd = matching_build_command(prop->expr, info);
  440. if (cmd == NULL)
  441. return FALSE;
  442. #if (defined USE_PTHREAD && defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 3)))
  443. td->cmd = cmd;
  444. td->done = FALSE;
  445. if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE,
  446. matcher_test_thread, td) != 0)
  447. retval = system(cmd);
  448. else {
  449. printf("waiting for test thread\n");
  450. while(!td->done) {
  451. /* don't let the interface freeze while waiting */
  452. claws_do_idle();
  453. if (time(NULL) - start_time > 30) {
  454. pthread_cancel(pt);
  455. td->done = TRUE;
  456. retval = -1;
  457. }
  458. }
  459. pthread_join(pt, &res);
  460. retval = GPOINTER_TO_INT(res);
  461. printf(" test thread returned %d\n", retval);
  462. }
  463. g_free(td);
  464. #else
  465. retval = system(cmd);
  466. #endif
  467. debug_print("Command exit code: %d\n", retval);
  468. g_free(cmd);
  469. return (retval == 0);
  470. }
  471. /*!
  472. *\brief Check if a message matches the condition in a matcher
  473. * structure.
  474. *
  475. *\param prop Pointer to matcher structure
  476. *\param info Pointer to message info
  477. *
  478. *\return gboolean TRUE if a match
  479. */
  480. gboolean matcherprop_match(MatcherProp *prop,
  481. MsgInfo *info)
  482. {
  483. time_t t;
  484. switch(prop->criteria) {
  485. case MATCHCRITERIA_ALL:
  486. return 1;
  487. case MATCHCRITERIA_UNREAD:
  488. return MSG_IS_UNREAD(info->flags);
  489. case MATCHCRITERIA_NOT_UNREAD:
  490. return !MSG_IS_UNREAD(info->flags);
  491. case MATCHCRITERIA_NEW:
  492. return MSG_IS_NEW(info->flags);
  493. case MATCHCRITERIA_NOT_NEW:
  494. return !MSG_IS_NEW(info->flags);
  495. case MATCHCRITERIA_MARKED:
  496. return MSG_IS_MARKED(info->flags);
  497. case MATCHCRITERIA_NOT_MARKED:
  498. return !MSG_IS_MARKED(info->flags);
  499. case MATCHCRITERIA_DELETED:
  500. return MSG_IS_DELETED(info->flags);
  501. case MATCHCRITERIA_NOT_DELETED:
  502. return !MSG_IS_DELETED(info->flags);
  503. case MATCHCRITERIA_REPLIED:
  504. return MSG_IS_REPLIED(info->flags);
  505. case MATCHCRITERIA_NOT_REPLIED:
  506. return !MSG_IS_REPLIED(info->flags);
  507. case MATCHCRITERIA_FORWARDED:
  508. return MSG_IS_FORWARDED(info->flags);
  509. case MATCHCRITERIA_NOT_FORWARDED:
  510. return !MSG_IS_FORWARDED(info->flags);
  511. case MATCHCRITERIA_LOCKED:
  512. return MSG_IS_LOCKED(info->flags);
  513. case MATCHCRITERIA_NOT_LOCKED:
  514. return !MSG_IS_LOCKED(info->flags);
  515. case MATCHCRITERIA_COLORLABEL:
  516. return MSG_GET_COLORLABEL_VALUE(info->flags) == prop->value;
  517. case MATCHCRITERIA_NOT_COLORLABEL:
  518. return MSG_GET_COLORLABEL_VALUE(info->flags) != prop->value;
  519. case MATCHCRITERIA_IGNORE_THREAD:
  520. return MSG_IS_IGNORE_THREAD(info->flags);
  521. case MATCHCRITERIA_NOT_IGNORE_THREAD:
  522. return !MSG_IS_IGNORE_THREAD(info->flags);
  523. case MATCHCRITERIA_SUBJECT:
  524. return matcherprop_string_match(prop, info->subject);
  525. case MATCHCRITERIA_NOT_SUBJECT:
  526. return !matcherprop_string_match(prop, info->subject);
  527. case MATCHCRITERIA_FROM:
  528. return matcherprop_string_match(prop, info->from);
  529. case MATCHCRITERIA_NOT_FROM:
  530. return !matcherprop_string_match(prop, info->from);
  531. case MATCHCRITERIA_TO:
  532. return matcherprop_string_match(prop, info->to);
  533. case MATCHCRITERIA_NOT_TO:
  534. return !matcherprop_string_match(prop, info->to);
  535. case MATCHCRITERIA_CC:
  536. return matcherprop_string_match(prop, info->cc);
  537. case MATCHCRITERIA_NOT_CC:
  538. return !matcherprop_string_match(prop, info->cc);
  539. case MATCHCRITERIA_TO_OR_CC:
  540. return matcherprop_string_match(prop, info->to)
  541. || matcherprop_string_match(prop, info->cc);
  542. case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
  543. return !(matcherprop_string_match(prop, info->to)
  544. || matcherprop_string_match(prop, info->cc));
  545. case MATCHCRITERIA_AGE_GREATER:
  546. t = time(NULL);
  547. return ((t - info->date_t) / (60 * 60 * 24)) > prop->value;
  548. case MATCHCRITERIA_AGE_LOWER:
  549. t = time(NULL);
  550. return ((t - info->date_t) / (60 * 60 * 24)) < prop->value;
  551. case MATCHCRITERIA_SCORE_GREATER:
  552. return info->score > prop->value;
  553. case MATCHCRITERIA_SCORE_LOWER:
  554. return info->score < prop->value;
  555. case MATCHCRITERIA_SCORE_EQUAL:
  556. return info->score == prop->value;
  557. case MATCHCRITERIA_SIZE_GREATER:
  558. /* FIXME: info->size is an off_t */
  559. return info->size > (off_t) prop->value;
  560. case MATCHCRITERIA_SIZE_EQUAL:
  561. /* FIXME: info->size is an off_t */
  562. return info->size == (off_t) prop->value;
  563. case MATCHCRITERIA_SIZE_SMALLER:
  564. /* FIXME: info->size is an off_t */
  565. return info->size < (off_t) prop->value;
  566. case MATCHCRITERIA_PARTIAL:
  567. /* FIXME: info->size is an off_t */
  568. return (info->total_size != 0 && info->size != (off_t)info->total_size);
  569. case MATCHCRITERIA_NOT_PARTIAL:
  570. /* FIXME: info->size is an off_t */
  571. return (info->total_size == 0 || info->size == (off_t)info->total_size);
  572. case MATCHCRITERIA_NEWSGROUPS:
  573. return matcherprop_string_match(prop, info->newsgroups);
  574. case MATCHCRITERIA_NOT_NEWSGROUPS:
  575. return !matcherprop_string_match(prop, info->newsgroups);
  576. case MATCHCRITERIA_INREPLYTO:
  577. return matcherprop_string_match(prop, info->inreplyto);
  578. case MATCHCRITERIA_NOT_INREPLYTO:
  579. return !matcherprop_string_match(prop, info->inreplyto);
  580. /* FIXME: Using inreplyto, but matching the (newly implemented)
  581. * list of references is better */
  582. case MATCHCRITERIA_REFERENCES:
  583. return matcherprop_string_match(prop, info->inreplyto);
  584. case MATCHCRITERIA_NOT_REFERENCES:
  585. return !matcherprop_string_match(prop, info->inreplyto);
  586. case MATCHCRITERIA_TEST:
  587. return matcherprop_match_test(prop, info);
  588. case MATCHCRITERIA_NOT_TEST:
  589. return !matcherprop_match_test(prop, info);
  590. default:
  591. return FALSE;
  592. }
  593. }
  594. /* ********************* MatcherList *************************** */
  595. /*!
  596. *\brief Create a new list of matchers
  597. *
  598. *\param matchers List of matcher structures
  599. *\param bool_and Operator
  600. *
  601. *\return MatcherList * New list
  602. */
  603. MatcherList *matcherlist_new(GSList *matchers, gboolean bool_and)
  604. {
  605. MatcherList *cond;
  606. cond = g_new0(MatcherList, 1);
  607. cond->matchers = matchers;
  608. cond->bool_and = bool_and;
  609. return cond;
  610. }
  611. /*!
  612. *\brief Frees a list of matchers
  613. *
  614. *\param cond List of matchers
  615. */
  616. void matcherlist_free(MatcherList *cond)
  617. {
  618. GSList *l;
  619. g_return_if_fail(cond);
  620. for (l = cond->matchers ; l != NULL ; l = g_slist_next(l)) {
  621. matcherprop_free((MatcherProp *) l->data);
  622. }
  623. g_free(cond);
  624. }
  625. /*!
  626. *\brief Skip all headers in a message file
  627. *
  628. *\param fp Message file
  629. */
  630. static void matcherlist_skip_headers(FILE *fp)
  631. {
  632. gchar buf[BUFFSIZE];
  633. while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1)
  634. ;
  635. }
  636. /*!
  637. *\brief Check if a header matches a matcher condition
  638. *
  639. *\param matcher Matcher structure to check header for
  640. *\param buf Header name
  641. *
  642. *\return boolean TRUE if matching header
  643. */
  644. static gboolean matcherprop_match_one_header(MatcherProp *matcher,
  645. gchar *buf)
  646. {
  647. gboolean result = FALSE;
  648. Header *header = NULL;
  649. switch (matcher->criteria) {
  650. case MATCHCRITERIA_HEADER:
  651. case MATCHCRITERIA_NOT_HEADER:
  652. header = procheader_parse_header(buf);
  653. if (!header)
  654. return FALSE;
  655. if (procheader_headername_equal(header->name,
  656. matcher->header)) {
  657. if (matcher->criteria == MATCHCRITERIA_HEADER)
  658. result = matcherprop_string_match(matcher, header->body);
  659. else
  660. result = !matcherprop_string_match(matcher, header->body);
  661. procheader_header_free(header);
  662. return result;
  663. }
  664. else {
  665. procheader_header_free(header);
  666. }
  667. break;
  668. case MATCHCRITERIA_HEADERS_PART:
  669. return matcherprop_string_match(matcher, buf);
  670. case MATCHCRITERIA_MESSAGE:
  671. return matcherprop_string_decode_match(matcher, buf);
  672. case MATCHCRITERIA_NOT_MESSAGE:
  673. return !matcherprop_string_decode_match(matcher, buf);
  674. case MATCHCRITERIA_NOT_HEADERS_PART:
  675. return !matcherprop_string_match(matcher, buf);
  676. case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
  677. case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK:
  678. {
  679. GSList *address_list = NULL;
  680. gint match = MATCH_ONE;
  681. gboolean found = FALSE;
  682. /* how many address headers are me trying to mach? */
  683. if (strcasecmp(matcher->header, _("Any")) == 0)
  684. match = MATCH_ANY;
  685. else if (strcasecmp(matcher->header, Q_("Filtering Matcher Menu|All")) == 0)
  686. match = MATCH_ALL;
  687. if (match == MATCH_ONE) {
  688. /* matching one address header exactly, is that the right one? */
  689. header = procheader_parse_header(buf);
  690. if (!header ||
  691. !procheader_headername_equal(header->name, matcher->header))
  692. return FALSE;
  693. address_list = address_list_append(address_list, header->body);
  694. if (address_list == NULL)
  695. return FALSE;
  696. } else {
  697. header = procheader_parse_header(buf);
  698. if (!header)
  699. return FALSE;
  700. /* address header is one of the headers we have to match when checking
  701. for any address header or all address headers? */
  702. if (procheader_headername_equal(header->name, "From") ||
  703. procheader_headername_equal(header->name, "To") ||
  704. procheader_headername_equal(header->name, "Cc") ||
  705. procheader_headername_equal(header->name, "Reply-To") ||
  706. procheader_headername_equal(header->name, "Sender"))
  707. address_list = address_list_append(address_list, header->body);
  708. if (address_list == NULL)
  709. return FALSE;
  710. }
  711. found = match_with_addresses_in_addressbook
  712. (matcher, address_list, matcher->criteria,
  713. matcher->expr, match);
  714. g_slist_free(address_list);
  715. if (matcher->criteria == MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK)
  716. return !found;
  717. else
  718. return found;
  719. }
  720. }
  721. return FALSE;
  722. }
  723. /*!
  724. *\brief Check if the matcher structure wants headers to
  725. * be matched
  726. *
  727. *\param matcher Matcher structure
  728. *
  729. *\return gboolean TRUE if the matcher structure describes
  730. * a header match condition
  731. */
  732. static gboolean matcherprop_criteria_headers(const MatcherProp *matcher)
  733. {
  734. switch (matcher->criteria) {
  735. case MATCHCRITERIA_HEADER:
  736. case MATCHCRITERIA_NOT_HEADER:
  737. case MATCHCRITERIA_HEADERS_PART:
  738. case MATCHCRITERIA_NOT_HEADERS_PART:
  739. case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
  740. case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK:
  741. return TRUE;
  742. default:
  743. return FALSE;
  744. }
  745. }
  746. /*!
  747. *\brief Check if the matcher structure wants the message
  748. * to be matched (just perform an action on any
  749. * message)
  750. *
  751. *\param matcher Matcher structure
  752. *
  753. *\return gboolean TRUE if matcher condition should match
  754. * a message
  755. */
  756. static gboolean matcherprop_criteria_message(MatcherProp *matcher)
  757. {
  758. switch (matcher->criteria) {
  759. case MATCHCRITERIA_MESSAGE:
  760. case MATCHCRITERIA_NOT_MESSAGE:
  761. return TRUE;
  762. default:
  763. return FALSE;
  764. }
  765. }
  766. /*!
  767. *\brief Check if a list of conditions matches one header in
  768. * a message file.
  769. *
  770. *\param matchers List of conditions
  771. *\param fp Message file
  772. *
  773. *\return gboolean TRUE if one of the headers is matched by
  774. * the list of conditions.
  775. */
  776. static gboolean matcherlist_match_headers(MatcherList *matchers, FILE *fp)
  777. {
  778. GSList *l;
  779. gchar buf[BUFFSIZE];
  780. while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) {
  781. for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
  782. MatcherProp *matcher = (MatcherProp *) l->data;
  783. gint match = MATCH_ANY;
  784. if (matcher->done)
  785. continue;
  786. /* determine the match range (all, any are our concern here) */
  787. if (matcher->criteria == MATCHCRITERIA_NOT_HEADERS_PART ||
  788. matcher->criteria == MATCHCRITERIA_NOT_MESSAGE) {
  789. match = MATCH_ALL;
  790. } else if (matcher->criteria == MATCHCRITERIA_FOUND_IN_ADDRESSBOOK ||
  791. matcher->criteria == MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK) {
  792. Header *header = NULL;
  793. /* address header is one of the headers we have to match when checking
  794. for any address header or all address headers? */
  795. header = procheader_parse_header(buf);
  796. if (header &&
  797. (procheader_headername_equal(header->name, "From") ||
  798. procheader_headername_equal(header->name, "To") ||
  799. procheader_headername_equal(header->name, "Cc") ||
  800. procheader_headername_equal(header->name, "Reply-To") ||
  801. procheader_headername_equal(header->name, "Sender"))) {
  802. if (strcasecmp(matcher->header, _("Any")) == 0)
  803. match = MATCH_ANY;
  804. else if (strcasecmp(matcher->header, Q_("Filtering Matcher Menu|All")) == 0)
  805. match = MATCH_ALL;
  806. else
  807. match = MATCH_ONE;
  808. } else {
  809. /* further call to matcherprop_match_one_header() can't match
  810. and it irrelevant, so: don't alter the match result */
  811. continue;
  812. }
  813. }
  814. /* ZERO line must NOT match for the rule to match.
  815. */
  816. if (match == MATCH_ALL) {
  817. if (matcherprop_match_one_header(matcher, buf)) {
  818. matcher->result = TRUE;
  819. } else {
  820. matcher->result = FALSE;
  821. matcher->done = TRUE;
  822. }
  823. /* else, just one line matching is enough for the rule to match
  824. */
  825. } else if (matcherprop_criteria_headers(matcher) ||
  826. matcherprop_criteria_message(matcher)) {
  827. if (matcherprop_match_one_header(matcher, buf)) {
  828. matcher->result = TRUE;
  829. matcher->done = TRUE;
  830. }
  831. }
  832. /* if the rule matched and the matchers are OR, no need to
  833. * check the others */
  834. if (matcher->result && matcher->done) {
  835. if (!matchers->bool_and)
  836. return TRUE;
  837. }
  838. }
  839. }
  840. return FALSE;
  841. }
  842. /*!
  843. *\brief Check if a matcher wants to check the message body
  844. *
  845. *\param matcher Matcher structure
  846. *
  847. *\return gboolean TRUE if body must be matched.
  848. */
  849. static gboolean matcherprop_criteria_body(const MatcherProp *matcher)
  850. {
  851. switch (matcher->criteria) {
  852. case MATCHCRITERIA_BODY_PART:
  853. case MATCHCRITERIA_NOT_BODY_PART:
  854. return TRUE;
  855. default:
  856. return FALSE;
  857. }
  858. }
  859. /*!
  860. *\brief Check if a (line) string matches the criteria
  861. * described by a matcher structure
  862. *
  863. *\param matcher Matcher structure
  864. *\param line String
  865. *
  866. *\return gboolean TRUE if string matches criteria
  867. */
  868. static gboolean matcherprop_match_line(MatcherProp *matcher, const gchar *line)
  869. {
  870. switch (matcher->criteria) {
  871. case MATCHCRITERIA_BODY_PART:
  872. case MATCHCRITERIA_MESSAGE:
  873. return matcherprop_string_decode_match(matcher, line);
  874. case MATCHCRITERIA_NOT_BODY_PART:
  875. case MATCHCRITERIA_NOT_MESSAGE:
  876. return !matcherprop_string_decode_match(matcher, line);
  877. }
  878. return FALSE;
  879. }
  880. /*!
  881. *\brief Check if a line in a message file's body matches
  882. * the criteria
  883. *
  884. *\param matchers List of conditions
  885. *\param fp Message file
  886. *
  887. *\return gboolean TRUE if succesful match
  888. */
  889. static gboolean matcherlist_match_body(MatcherList *matchers, FILE *fp)
  890. {
  891. GSList *l;
  892. gchar buf[BUFFSIZE];
  893. while (fgets(buf, sizeof(buf), fp) != NULL) {
  894. for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
  895. MatcherProp *matcher = (MatcherProp *) l->data;
  896. if (matcher->done)
  897. continue;
  898. /* if the criteria is ~body_part or ~message, ZERO lines
  899. * must NOT match for the rule to match. */
  900. if (matcher->criteria == MATCHCRITERIA_NOT_BODY_PART ||
  901. matcher->criteria == MATCHCRITERIA_NOT_MESSAGE) {
  902. if (matcherprop_match_line(matcher, buf)) {
  903. matcher->result = TRUE;
  904. } else {
  905. matcher->result = FALSE;
  906. matcher->done = TRUE;
  907. }
  908. /* else, just one line has to match */
  909. } else if (matcherprop_criteria_body(matcher) ||
  910. matcherprop_criteria_message(matcher)) {
  911. if (matcherprop_match_line(matcher, buf)) {
  912. matcher->result = TRUE;
  913. matcher->done = TRUE;
  914. }
  915. }
  916. /* if the matchers are OR'ed and the rule matched,
  917. * no need to check the others. */
  918. if (matcher->result && matcher->done) {
  919. if (!matchers->bool_and)
  920. return TRUE;
  921. }
  922. }
  923. }
  924. return FALSE;
  925. }
  926. /*!
  927. *\brief Check if a message file matches criteria
  928. *
  929. *\param matchers Criteria
  930. *\param info Message info
  931. *\param result Default result
  932. *
  933. *\return gboolean TRUE if matched
  934. */
  935. gboolean matcherlist_match_file(MatcherList *matchers, MsgInfo *info,
  936. gboolean result)
  937. {
  938. gboolean read_headers;
  939. gboolean read_body;
  940. GSList *l;
  941. FILE *fp;
  942. gchar *file;
  943. /* file need to be read ? */
  944. read_headers = FALSE;
  945. read_body = FALSE;
  946. for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
  947. MatcherProp *matcher = (MatcherProp *) l->data;
  948. if (matcherprop_criteria_headers(matcher))
  949. read_headers = TRUE;
  950. if (matcherprop_criteria_body(matcher))
  951. read_body = TRUE;
  952. if (matcherprop_criteria_message(matcher)) {
  953. read_headers = TRUE;
  954. read_body = TRUE;
  955. }
  956. matcher->result = FALSE;
  957. matcher->done = FALSE;
  958. }
  959. if (!read_headers && !read_body)
  960. return result;
  961. file = procmsg_get_message_file_full(info, read_headers, read_body);
  962. if (file == NULL)
  963. return FALSE;
  964. if ((fp = g_fopen(file, "rb")) == NULL) {
  965. FILE_OP_ERROR(file, "fopen");
  966. g_free(file);
  967. return result;
  968. }
  969. /* read the headers */
  970. if (read_headers) {
  971. if (matcherlist_match_headers(matchers, fp))
  972. read_body = FALSE;
  973. } else {
  974. matcherlist_skip_headers(fp);
  975. }
  976. /* read the body */
  977. if (read_body) {
  978. matcherlist_match_body(matchers, fp);
  979. }
  980. for (l = matchers->matchers; l != NULL; l = g_slist_next(l)) {
  981. MatcherProp *matcher = (MatcherProp *) l->data;
  982. if (matcherprop_criteria_headers(matcher) ||
  983. matcherprop_criteria_body(matcher) ||
  984. matcherprop_criteria_message(matcher)) {
  985. if (matcher->result) {
  986. if (!matchers->bool_and) {
  987. result = TRUE;
  988. break;
  989. }
  990. }
  991. else {
  992. if (matchers->bool_and) {
  993. result = FALSE;
  994. break;
  995. }
  996. }
  997. }
  998. }
  999. g_free(file);
  1000. fclose(fp);
  1001. return result;
  1002. }
  1003. /*!
  1004. *\brief Test list of conditions on a message.
  1005. *
  1006. *\param matchers List of conditions
  1007. *\param info Message info
  1008. *
  1009. *\return gboolean TRUE if matched
  1010. */
  1011. gboolean matcherlist_match(MatcherList *matchers, MsgInfo *info)
  1012. {
  1013. GSList *l;
  1014. gboolean result;
  1015. if (!matchers)
  1016. return FALSE;
  1017. if (matchers->bool_and)
  1018. result = TRUE;
  1019. else
  1020. result = FALSE;
  1021. /* test the cached elements */
  1022. for (l = matchers->matchers; l != NULL ;l = g_slist_next(l)) {
  1023. MatcherProp *matcher = (MatcherProp *) l->data;
  1024. switch(matcher->criteria) {
  1025. case MATCHCRITERIA_ALL:
  1026. case MATCHCRITERIA_UNREAD:
  1027. case MATCHCRITERIA_NOT_UNREAD:
  1028. case MATCHCRITERIA_NEW:
  1029. case MATCHCRITERIA_NOT_NEW:
  1030. case MATCHCRITERIA_MARKED:
  1031. case MATCHCRITERIA_NOT_MARKED:
  1032. case MATCHCRITERIA_DELETED:
  1033. case MATCHCRITERIA_NOT_DELETED:
  1034. case MATCHCRITERIA_REPLIED:
  1035. case MATCHCRITERIA_NOT_REPLIED:
  1036. case MATCHCRITERIA_FORWARDED:
  1037. case MATCHCRITERIA_NOT_FORWARDED:
  1038. case MATCHCRITERIA_LOCKED:
  1039. case MATCHCRITERIA_NOT_LOCKED:
  1040. case MATCHCRITERIA_COLORLABEL:
  1041. case MATCHCRITERIA_NOT_COLORLABEL:
  1042. case MATCHCRITERIA_IGNORE_THREAD:
  1043. case MATCHCRITERIA_NOT_IGNORE_THREAD:
  1044. case MATCHCRITERIA_SUBJECT:
  1045. case MATCHCRITERIA_NOT_SUBJECT:
  1046. case MATCHCRITERIA_FROM:
  1047. case MATCHCRITERIA_NOT_FROM:
  1048. case MATCHCRITERIA_TO:
  1049. case MATCHCRITERIA_NOT_TO:
  1050. case MATCHCRITERIA_CC:
  1051. case MATCHCRITERIA_NOT_CC:
  1052. case MATCHCRITERIA_TO_OR_CC:
  1053. case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
  1054. case MATCHCRITERIA_AGE_GREATER:
  1055. case MATCHCRITERIA_AGE_LOWER:
  1056. case MATCHCRITERIA_NEWSGROUPS:
  1057. case MATCHCRITERIA_NOT_NEWSGROUPS:
  1058. case MATCHCRITERIA_INREPLYTO:
  1059. case MATCHCRITERIA_NOT_INREPLYTO:
  1060. case MATCHCRITERIA_REFERENCES:
  1061. case MATCHCRITERIA_NOT_REFERENCES:
  1062. case MATCHCRITERIA_SCORE_GREATER:
  1063. case MATCHCRITERIA_SCORE_LOWER:
  1064. case MATCHCRITERIA_SCORE_EQUAL:
  1065. case MATCHCRITERIA_SIZE_GREATER:
  1066. case MATCHCRITERIA_SIZE_SMALLER:
  1067. case MATCHCRITERIA_SIZE_EQUAL:
  1068. case MATCHCRITERIA_TEST:
  1069. case MATCHCRITERIA_NOT_TEST:
  1070. case MATCHCRITERIA_PARTIAL:
  1071. case MATCHCRITERIA_NOT_PARTIAL:
  1072. if (matcherprop_match(matcher, info)) {
  1073. if (!matchers->bool_and) {
  1074. return TRUE;
  1075. }
  1076. }
  1077. else {
  1078. if (matchers->bool_and) {
  1079. return FALSE;
  1080. }
  1081. }
  1082. }
  1083. }
  1084. /* test the condition on the file */
  1085. if (matcherlist_match_file(matchers, info, result)) {
  1086. if (!matchers->bool_and)
  1087. return TRUE;
  1088. }
  1089. else {
  1090. if (matchers->bool_and)
  1091. return FALSE;
  1092. }
  1093. return result;
  1094. }
  1095. static gint quote_filter_str(gchar * result, guint size,
  1096. const gchar * path)
  1097. {
  1098. const gchar * p;
  1099. gchar * result_p;
  1100. guint remaining;
  1101. result_p = result;
  1102. remaining = size;
  1103. for(p = path ; * p != '\0' ; p ++) {
  1104. if ((* p != '\"') && (* p != '\\')) {
  1105. if (remaining > 0) {
  1106. * result_p = * p;
  1107. result_p ++;
  1108. remaining --;
  1109. }
  1110. else {
  1111. result[size - 1] = '\0';
  1112. return -1;
  1113. }
  1114. }
  1115. else {
  1116. if (remaining >= 2) {
  1117. * result_p = '\\';
  1118. result_p ++;
  1119. * result_p = * p;
  1120. result_p ++;
  1121. remaining -= 2;
  1122. }
  1123. else {
  1124. result[size - 1] = '\0';
  1125. return -1;
  1126. }
  1127. }
  1128. }
  1129. if (remaining > 0) {
  1130. * result_p = '\0';
  1131. }
  1132. else {
  1133. result[size - 1] = '\0';
  1134. return -1;
  1135. }
  1136. return 0;
  1137. }
  1138. gchar * matcher_quote_str(const gchar * src)
  1139. {
  1140. gchar * res;
  1141. gint len;
  1142. len = strlen(src) * 2 + 1;
  1143. res = g_malloc(len);
  1144. quote_filter_str(res, len, src);
  1145. return res;
  1146. }
  1147. /*!
  1148. *\brief Convert a matcher structure to a string
  1149. *
  1150. *\param matcher Matcher structure
  1151. *
  1152. *\return gchar * Newly allocated string
  1153. */
  1154. gchar *matcherprop_to_string(MatcherProp *matcher)
  1155. {
  1156. gchar *matcher_str = NULL;
  1157. const gchar *criteria_str;
  1158. const gchar *matchtype_str;
  1159. int i;
  1160. gchar * quoted_expr;
  1161. gchar * quoted_header;
  1162. criteria_str = NULL;
  1163. for (i = 0; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)); i++) {
  1164. if (matchparser_tab[i].id == matcher->criteria)
  1165. criteria_str = matchparser_tab[i].str;
  1166. }
  1167. if (criteria_str == NULL)
  1168. return NULL;
  1169. switch (matcher->criteria) {
  1170. case MATCHCRITERIA_AGE_GREATER:
  1171. case MATCHCRITERIA_AGE_LOWER:
  1172. case MATCHCRITERIA_SCORE_GREATER:
  1173. case MATCHCRITERIA_SCORE_LOWER:
  1174. case MATCHCRITERIA_SCORE_EQUAL:
  1175. case MATCHCRITERIA_SIZE_GREATER:
  1176. case MATCHCRITERIA_SIZE_SMALLER:
  1177. case MATCHCRITERIA_SIZE_EQUAL:
  1178. case MATCHCRITERIA_COLORLABEL:
  1179. case MATCHCRITERIA_NOT_COLORLABEL:
  1180. return g_strdup_printf("%s %i", criteria_str, matcher->value);
  1181. case MATCHCRITERIA_ALL:
  1182. case MATCHCRITERIA_UNREAD:
  1183. case MATCHCRITERIA_NOT_UNREAD:
  1184. case MATCHCRITERIA_NEW:
  1185. case MATCHCRITERIA_NOT_NEW:
  1186. case MATCHCRITERIA_MARKED:
  1187. case MATCHCRITERIA_NOT_MARKED:
  1188. case MATCHCRITERIA_DELETED:
  1189. case MATCHCRITERIA_NOT_DELETED:
  1190. case MATCHCRITERIA_REPLIED:
  1191. case MATCHCRITERIA_NOT_REPLIED:
  1192. case MATCHCRITERIA_FORWARDED:
  1193. case MATCHCRITERIA_NOT_FORWARDED:
  1194. case MATCHCRITERIA_LOCKED:
  1195. case MATCHCRITERIA_NOT_LOCKED:
  1196. case MATCHCRITERIA_PARTIAL:
  1197. case MATCHCRITERIA_NOT_PARTIAL:
  1198. case MATCHCRITERIA_IGNORE_THREAD:
  1199. case MATCHCRITERIA_NOT_IGNORE_THREAD:
  1200. return g_strdup(criteria_str);
  1201. case MATCHCRITERIA_TEST:
  1202. case MATCHCRITERIA_NOT_TEST:
  1203. quoted_expr = matcher_quote_str(matcher->expr);
  1204. matcher_str = g_strdup_printf("%s \"%s\"",
  1205. criteria_str, quoted_expr);
  1206. g_free(quoted_expr);
  1207. return matcher_str;
  1208. case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
  1209. case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK:
  1210. quoted_header = matcher_quote_str(matcher->header);
  1211. quoted_expr = matcher_quote_str(matcher->expr);
  1212. matcher_str = g_strdup_printf("%s \"%s\" in \"%s\"",
  1213. criteria_str, quoted_header, quoted_expr);
  1214. g_free(quoted_header);
  1215. g_free(quoted_expr);
  1216. return matcher_str;
  1217. }
  1218. matchtype_str = NULL;
  1219. for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++) {
  1220. if (matchparser_tab[i].id == matcher->matchtype)
  1221. matchtype_str = matchparser_tab[i].str;
  1222. }
  1223. if (matchtype_str == NULL)
  1224. return NULL;
  1225. switch (matcher->matchtype) {
  1226. case MATCHTYPE_MATCH:
  1227. case MATCHTYPE_MATCHCASE:
  1228. case MATCHTYPE_REGEXP:
  1229. case MATCHTYPE_REGEXPCASE:
  1230. quoted_expr = matcher_quote_str(matcher->expr);
  1231. if (matcher->header) {
  1232. quoted_header = matcher_quote_str(matcher->header);
  1233. matcher_str = g_strdup_printf
  1234. ("%s \"%s\" %s \"%s\"",
  1235. criteria_str, quoted_header,
  1236. matchtype_str, quoted_expr);
  1237. g_free(quoted_header);
  1238. }
  1239. else
  1240. matcher_str = g_strdup_printf
  1241. ("%s %s \"%s\"", criteria_str,
  1242. matchtype_str, quoted_expr);
  1243. g_free(quoted_expr);
  1244. break;
  1245. }
  1246. return matcher_str;
  1247. }
  1248. /*!
  1249. *\brief Convert a list of conditions to a string
  1250. *
  1251. *\param matchers List of conditions
  1252. *
  1253. *\return gchar * Newly allocated string
  1254. */
  1255. gchar *matcherlist_to_string(const MatcherList *matchers)
  1256. {
  1257. gint count;
  1258. gchar **vstr;
  1259. GSList *l;
  1260. gchar **cur_str;
  1261. gchar *result = NULL;
  1262. count = g_slist_length(matchers->matchers);
  1263. vstr = g_new(gchar *, count + 1);
  1264. for (l = matchers->matchers, cur_str = vstr; l != NULL;
  1265. l = g_slist_next(l), cur_str ++) {
  1266. *cur_str = matcherprop_to_string((MatcherProp *) l->data);
  1267. if (*cur_str == NULL)
  1268. break;
  1269. }
  1270. *cur_str = NULL;
  1271. if (matchers->bool_and)
  1272. result = g_strjoinv(" & ", vstr);
  1273. else
  1274. result = g_strjoinv(" | ", vstr);
  1275. for (cur_str = vstr ; *cur_str != NULL ; cur_str ++)
  1276. g_free(*cur_str);
  1277. g_free(vstr);
  1278. return result;
  1279. }
  1280. #define STRLEN_ZERO(s) ((s) ? strlen(s) : 0)
  1281. #define STRLEN_DEFAULT(s,d) ((s) ? strlen(s) : STRLEN_ZERO(d))
  1282. static void add_str_default(gchar ** dest,
  1283. const gchar * s, const gchar * d)
  1284. {
  1285. gchar quoted_str[4096];
  1286. const gchar * str;
  1287. if (s != NULL)
  1288. str = s;
  1289. else
  1290. str = d;
  1291. quote_cmd_argument(quoted_str, sizeof(quoted_str), str);
  1292. strcpy(* dest, quoted_str);
  1293. (* dest) += strlen(* dest);
  1294. }
  1295. /* matching_build_command() - preferably cmd should be unescaped */
  1296. /*!
  1297. *\brief Build the command line to execute
  1298. *
  1299. *\param cmd String with command line specifiers
  1300. *\param info Message info to use for command
  1301. *
  1302. *\return gchar * Newly allocated string
  1303. */
  1304. gchar *matching_build_command(const gchar *cmd, MsgInfo *info)
  1305. {
  1306. const gchar *s = cmd;
  1307. gchar *filename = NULL;
  1308. gchar *processed_cmd;
  1309. gchar *p;
  1310. gint size;
  1311. const gchar *const no_subject = _("(none)") ;
  1312. const gchar *const no_from = _("(none)") ;
  1313. const gchar *const no_to = _("(none)") ;
  1314. const gchar *const no_cc = _("(none)") ;
  1315. const gchar *const no_date = _("(none)") ;
  1316. const gchar *const no_msgid = _("(none)") ;
  1317. const gchar *const no_newsgroups = _("(none)") ;
  1318. const gchar *const no_references = _("(none)") ;
  1319. size = STRLEN_ZERO(cmd) + 1;
  1320. while (*s != '\0') {
  1321. if (*s == '%') {
  1322. s++;
  1323. switch (*s) {
  1324. case '%':
  1325. size -= 1;
  1326. break;
  1327. case 's': /* subject */
  1328. size += STRLEN_DEFAULT(info->subject, no_subject) - 2;
  1329. break;
  1330. case 'f': /* from */
  1331. size += STRLEN_DEFAULT(info->from, no_from) - 2;
  1332. break;
  1333. case 't': /* to */
  1334. size += STRLEN_DEFAULT(info->to, no_to) - 2;
  1335. break;
  1336. case 'c': /* cc */
  1337. size += STRLEN_DEFAULT(info->cc, no_cc) - 2;
  1338. break;
  1339. case 'd': /* date */
  1340. size += STRLEN_DEFAULT(info->date, no_date) - 2;
  1341. break;
  1342. case 'i': /* message-id */
  1343. size += STRLEN_DEFAULT(info->msgid, no_msgid) - 2;
  1344. break;
  1345. case 'n': /* newsgroups */
  1346. size += STRLEN_DEFAULT(info->newsgroups, no_newsgroups) - 2;
  1347. break;
  1348. case 'r': /* references */
  1349. /* FIXME: using the inreplyto header for reference */
  1350. size += STRLEN_DEFAULT(info->inreplyto, no_references) - 2;
  1351. break;
  1352. case 'F': /* file */
  1353. if (filename == NULL)
  1354. filename = folder_item_fetch_msg(info->folder, info->msgnum);
  1355. if (filename == NULL) {
  1356. g_warning("filename is not set");
  1357. return NULL;
  1358. }
  1359. else {
  1360. size += strlen(filename) - 2;
  1361. }
  1362. break;
  1363. }
  1364. s++;
  1365. }
  1366. else s++;
  1367. }
  1368. /* as the string can be quoted, we double the result */
  1369. size *= 2;
  1370. processed_cmd = g_new0(gchar, size);
  1371. s = cmd;
  1372. p = processed_cmd;
  1373. while (*s != '\0') {
  1374. if (*s == '%') {
  1375. s++;
  1376. switch (*s) {
  1377. case '%':
  1378. *p = '%';
  1379. p++;
  1380. break;
  1381. case 's': /* subject */
  1382. add_str_default(&p, info->subject,
  1383. no_subject);
  1384. break;
  1385. case 'f': /* from */
  1386. add_str_default(&p, info->from,
  1387. no_from);
  1388. break;
  1389. case 't': /* to */
  1390. add_str_default(&p, info->to,
  1391. no_to);
  1392. break;
  1393. case 'c': /* cc */
  1394. add_str_default(&p, info->cc,
  1395. no_cc);
  1396. break;
  1397. case 'd': /* date */
  1398. add_str_default(&p, info->date,
  1399. no_date);
  1400. break;
  1401. case 'i': /* message-id */
  1402. add_str_default(&p, info->msgid,
  1403. no_msgid);
  1404. break;
  1405. case 'n': /* newsgroups */
  1406. add_str_default(&p, info->newsgroups,
  1407. no_newsgroups);
  1408. break;
  1409. case 'r': /* references */
  1410. /* FIXME: using the inreplyto header for references */
  1411. add_str_default(&p, info->inreplyto, no_references);
  1412. break;
  1413. case 'F': /* file */
  1414. if (filename != NULL)
  1415. add_str_default(&p, filename, NULL);
  1416. break;
  1417. default:
  1418. *p = '%';
  1419. p++;
  1420. *p = *s;
  1421. p++;
  1422. break;
  1423. }
  1424. s++;
  1425. }
  1426. else {
  1427. *p = *s;
  1428. p++;
  1429. s++;
  1430. }
  1431. }
  1432. g_free(filename);
  1433. return processed_cmd;
  1434. }
  1435. #undef STRLEN_DEFAULT
  1436. #undef STRLEN_ZERO
  1437. /* ************************************************************ */
  1438. /*!
  1439. *\brief Write filtering list to file
  1440. *
  1441. *\param fp File
  1442. *\param prefs_filtering List of filtering conditions
  1443. */
  1444. static void prefs_filtering_write(FILE *fp, GSList *prefs_filtering)
  1445. {
  1446. GSList *cur = NULL;
  1447. for (cur = prefs_filtering; cur != NULL; cur = cur->next) {
  1448. gchar *filtering_str = NULL;
  1449. gchar *tmp_name = NULL;
  1450. FilteringProp *prop = NULL;
  1451. if (NULL == (prop = (FilteringProp *) cur->data))
  1452. continue;
  1453. if (NULL == (filtering_str = filteringprop_to_string(prop)))
  1454. continue;
  1455. if (prop->enabled) {
  1456. if (fputs("enabled ", fp) == EOF) {
  1457. FILE_OP_ERROR("filtering config", "fputs");
  1458. return;
  1459. }
  1460. } else {
  1461. if (fputs("disabled ", fp) == EOF) {
  1462. FILE_OP_ERROR("filtering config", "fputs");
  1463. return;
  1464. }
  1465. }
  1466. if (fputs("rulename \"", fp) == EOF) {
  1467. FILE_OP_ERROR("filtering config", "fputs");
  1468. g_free(filtering_str);
  1469. return;
  1470. }
  1471. tmp_name = prop->name;
  1472. while (tmp_name && *tmp_name != '\0') {
  1473. if (*tmp_name != '"') {
  1474. if (fputc(*tmp_name, fp) == EOF) {
  1475. FILE_OP_ERROR("filtering config", "fputs || fputc");
  1476. g_free(filtering_str);
  1477. return;
  1478. }
  1479. } else if (*tmp_name == '"') {
  1480. if (fputc('\\', fp) == EOF ||
  1481. fputc('"', fp) == EOF) {
  1482. FILE_OP_ERROR("filtering config", "fputs || fputc");
  1483. g_free(filtering_str);
  1484. return;
  1485. }
  1486. }
  1487. tmp_name ++;
  1488. }
  1489. if (fputs("\" ", fp) == EOF) {
  1490. FILE_OP_ERROR("filtering config", "fputs");
  1491. g_free(filtering_str);
  1492. return;
  1493. }
  1494. if (prop->account_id != 0) {
  1495. gchar *tmp = NULL;
  1496. tmp = g_strdup_printf("account %d ", prop->account_id);
  1497. if (fputs(tmp, fp) == EOF) {
  1498. FILE_OP_ERROR("filtering config", "fputs");
  1499. g_free(tmp);
  1500. return;
  1501. }
  1502. g_free(tmp);
  1503. }
  1504. if(fputs(filtering_str, fp) == EOF ||
  1505. fputc('\n', fp) == EOF) {
  1506. FILE_OP_ERROR("filtering config", "fputs || fputc");
  1507. g_free(filtering_str);
  1508. return;
  1509. }
  1510. g_free(filtering_str);
  1511. }
  1512. }
  1513. /*!
  1514. *\brief Write matchers from a folder item
  1515. *
  1516. *\param node Node with folder info
  1517. *\param data File pointer
  1518. *
  1519. *\return gboolean FALSE
  1520. */
  1521. static gboolean prefs_matcher_write_func(GNode *node, gpointer data)
  1522. {
  1523. FolderItem *item;
  1524. FILE *fp = data;
  1525. gchar *id;
  1526. GSList *prefs_filtering;
  1527. item = node->data;
  1528. /* prevent warning */
  1529. if (item->path == NULL)
  1530. return FALSE;
  1531. id = folder_item_get_identifier(item);
  1532. if (id == NULL)
  1533. return FALSE;
  1534. prefs_filtering = item->prefs->processing;
  1535. if (prefs_filtering != NULL) {
  1536. fprintf(fp, "[%s]\n", id);
  1537. prefs_filtering_write(fp, prefs_filtering);
  1538. fputc('\n', fp);
  1539. }
  1540. g_free(id);
  1541. return FALSE;
  1542. }
  1543. /*!
  1544. *\brief Save matchers from folder items
  1545. *
  1546. *\param fp File
  1547. */
  1548. static void prefs_matcher_save(FILE *fp)
  1549. {
  1550. GList *cur;
  1551. for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
  1552. Folder *folder;
  1553. folder = (Folder *) cur->data;
  1554. g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
  1555. prefs_matcher_write_func, fp);
  1556. }
  1557. /* pre global rules */
  1558. fprintf(fp, "[preglobal]\n");
  1559. prefs_filtering_write(fp, pre_global_processing);
  1560. fputc('\n', fp);
  1561. /* post global rules */
  1562. fprintf(fp, "[postglobal]\n");
  1563. prefs_filtering_write(fp, post_global_processing);
  1564. fputc('\n', fp);
  1565. /* filtering rules */
  1566. fprintf(fp, "[filtering]\n");
  1567. prefs_filtering_write(fp, filtering_rules);
  1568. fputc('\n', fp);
  1569. }
  1570. /*!
  1571. *\brief Write filtering / matcher configuration file
  1572. */
  1573. void prefs_matcher_write_config(void)
  1574. {
  1575. gchar *rcpath;
  1576. PrefFile *pfile;
  1577. debug_print("Writing matcher configuration...\n");
  1578. rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
  1579. MATCHER_RC, NULL);
  1580. if ((pfile = prefs_write_open(rcpath)) == NULL) {
  1581. g_warning("failed to write configuration to file\n");
  1582. g_free(rcpath);
  1583. return;
  1584. }
  1585. prefs_matcher_save(pfile->fp);
  1586. g_free(rcpath);
  1587. if (prefs_file_close(pfile) < 0) {
  1588. g_warning("failed to write configuration to file\n");
  1589. return;
  1590. }
  1591. }
  1592. /* ******************************************************************* */
  1593. void matcher_add_rulenames(const gchar *rcpath)
  1594. {
  1595. gchar *newpath = g_strconcat(rcpath, ".new", NULL);
  1596. FILE *src = g_fopen(rcpath, "rb");
  1597. FILE *dst = g_fopen(newpath, "wb");
  1598. gchar buf[BUFFSIZE];
  1599. if (dst == NULL) {
  1600. perror("fopen");
  1601. g_free(newpath);
  1602. return;
  1603. }
  1604. while (fgets (buf, sizeof(buf), src) != NULL) {
  1605. if (strlen(buf) > 2 && buf[0] != '['
  1606. && strncmp(buf, "rulename \"", 10)) {
  1607. fwrite("rulename \"\" ",
  1608. strlen("rulename \"\" "), 1, dst);
  1609. }
  1610. fwrite(buf, strlen(buf), 1, dst);
  1611. }
  1612. fclose(dst);
  1613. fclose(src);
  1614. move_file(newpath, rcpath, TRUE);
  1615. g_free(newpath);
  1616. }
  1617. /*!
  1618. *\brief Read matcher configuration
  1619. */
  1620. void prefs_matcher_read_config(void)
  1621. {
  1622. gchar *rcpath;
  1623. gchar *rc_old_format;
  1624. FILE *f;
  1625. create_matchparser_hashtab();
  1626. prefs_filtering_clear();
  1627. rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, MATCHER_RC, NULL);
  1628. rc_old_format = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, MATCHER_RC,
  1629. ".pre_names", NULL);
  1630. if (!is_file_exist(rc_old_format) && is_file_exist(rcpath)) {
  1631. /* backup file with no rules names, in case
  1632. * anything goes wrong */
  1633. copy_file(rcpath, rc_old_format, FALSE);
  1634. /* now hack the file in order to have it to the new format */
  1635. matcher_add_rulenames(rcpath);
  1636. }
  1637. g_free(rc_old_format);
  1638. f = g_fopen(rcpath, "rb");
  1639. g_free(rcpath);
  1640. if (f != NULL) {
  1641. matcher_parser_start_parsing(f);
  1642. fclose(matcher_parserin);
  1643. }
  1644. else {
  1645. /* previous version compatibility */
  1646. /* printf("reading filtering\n"); */
  1647. rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
  1648. FILTERING_RC, NULL);
  1649. f = g_fopen(rcpath, "rb");
  1650. g_free(rcpath);
  1651. if (f != NULL) {
  1652. matcher_parser_start_parsing(f);
  1653. fclose(matcher_parserin);
  1654. }
  1655. /* printf("reading scoring\n"); */
  1656. rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
  1657. SCORING_RC, NULL);
  1658. f = g_fopen(rcpath, "rb");
  1659. g_free(rcpath);
  1660. if (f != NULL) {
  1661. matcher_parser_start_parsing(f);
  1662. fclose(matcher_parserin);
  1663. }
  1664. }
  1665. }