PageRenderTime 54ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/tags/release-1-2-0-rc1/noffle/src/filter.c

#
C | 542 lines | 441 code | 65 blank | 36 comment | 95 complexity | 38dcf800aadbdbff36ff783cba6936d1 MD5 | raw file
Possible License(s): GPL-2.0
  1. /*
  2. filter.c
  3. Article filtering.
  4. $Id: filter.c 628 2004-10-13 21:59:41Z bears $
  5. */
  6. #if HAVE_CONFIG_H
  7. #include <config.h>
  8. #endif
  9. #include <ctype.h>
  10. #include "common.h"
  11. #include "configfile.h"
  12. #include "itemlist.h"
  13. #include "log.h"
  14. #include "wildmat.h"
  15. #include "group.h"
  16. #include "util.h"
  17. #include "filter.h"
  18. struct
  19. {
  20. int nFilters;
  21. int maxFilters;
  22. const Filter **filters;
  23. Bool needGroups;
  24. } filter = { 0, 0, NULL, FALSE };
  25. static unsigned long
  26. countGroups( const char *grps )
  27. {
  28. unsigned long res;
  29. res = 1;
  30. while ( *grps != '\0' )
  31. {
  32. if ( *grps == ',' )
  33. res++;
  34. grps++;
  35. }
  36. return res;
  37. }
  38. static unsigned long
  39. countRefs( const char *refs )
  40. {
  41. unsigned long res;
  42. Bool inRef;
  43. res = 0;
  44. inRef = FALSE;
  45. while ( *refs != '\0' )
  46. {
  47. if ( inRef )
  48. {
  49. if ( *refs == '>' )
  50. {
  51. inRef = FALSE;
  52. res++;
  53. }
  54. }
  55. else if ( *refs == '<' )
  56. inRef = TRUE;
  57. refs++;
  58. }
  59. return res;
  60. }
  61. /* Check a single rule to see if it passes. */
  62. static Bool
  63. checkRule( const char *thisGrp, const char *newsgroups,
  64. const Over *ov, const FilterRule *r )
  65. {
  66. unsigned long ul;
  67. ItemList *grps;
  68. const char *p;
  69. time_t articletime;
  70. Bool res;
  71. switch( r->type )
  72. {
  73. case RULE_NEWSGROUP:
  74. if ( Wld_match( thisGrp, r->data.grp ) )
  75. {
  76. Log_dbg( LOG_DBG_FILTER,
  77. "Newsgroup rule: %s matches current group",
  78. r->data.grp, thisGrp );
  79. return TRUE;
  80. }
  81. if ( newsgroups != NULL )
  82. {
  83. grps = new_Itl( newsgroups, " ,\t" );
  84. for ( p = Itl_first( grps ); p != NULL; p = Itl_next( grps ) )
  85. if ( Wld_match( p, r->data.grp ) )
  86. {
  87. Log_dbg( LOG_DBG_FILTER,
  88. "Newsgroup rule: %s matched in %s",
  89. r->data.grp, newsgroups );
  90. return TRUE;
  91. }
  92. del_Itl( grps );
  93. }
  94. return FALSE;
  95. case RULE_SUBJECT:
  96. res = ( regexec( &r->data.regex, Ov_subj( ov ), 0, NULL, 0 ) == 0 );
  97. if ( res )
  98. Log_dbg( LOG_DBG_FILTER,
  99. "Subject rule: %s matches",
  100. Ov_subj( ov ) );
  101. return res;
  102. case RULE_REFERENCE: /* kill thread by Msg-Id in References: */
  103. res = ( regexec( &r->data.regex, Ov_ref( ov ), 0, NULL, 0 ) == 0 );
  104. if ( res )
  105. Log_dbg( LOG_DBG_FILTER,
  106. "Reference rule: %s matches",
  107. Ov_ref( ov ) );
  108. return res;
  109. case RULE_FROM:
  110. res = ( regexec( &r->data.regex, Ov_from( ov ), 0, NULL, 0 ) == 0 );
  111. if ( res )
  112. Log_dbg( LOG_DBG_FILTER,
  113. "From rule: %s matches",
  114. Ov_from( ov ) );
  115. return res;
  116. case RULE_BYTES_LT:
  117. res = ( Ov_bytes( ov ) < r->data.amount );
  118. if ( res )
  119. Log_dbg( LOG_DBG_FILTER,
  120. "Length rule: bytes %d < %d",
  121. Ov_bytes( ov ), r->data.amount );
  122. return res;
  123. case RULE_BYTES_EQ:
  124. res = ( Ov_bytes( ov ) == r->data.amount );
  125. if ( res )
  126. Log_dbg( LOG_DBG_FILTER,
  127. "Length rule: bytes %d = %d",
  128. Ov_bytes( ov ), r->data.amount );
  129. return res;
  130. case RULE_BYTES_GT:
  131. res = ( Ov_bytes( ov ) > r->data.amount );
  132. if ( res )
  133. Log_dbg( LOG_DBG_FILTER,
  134. "Length rule: bytes %d > %d",
  135. Ov_bytes( ov ), r->data.amount );
  136. return res;
  137. case RULE_LINES_LT:
  138. res = ( Ov_lines( ov ) < r->data.amount );
  139. if ( res )
  140. Log_dbg( LOG_DBG_FILTER,
  141. "Length rule: lines %d < %d",
  142. Ov_lines( ov ), r->data.amount );
  143. return res;
  144. case RULE_LINES_EQ:
  145. res = ( Ov_lines( ov ) == r->data.amount );
  146. if ( res )
  147. Log_dbg( LOG_DBG_FILTER,
  148. "Length rule: lines %d = %d",
  149. Ov_lines( ov ), r->data.amount );
  150. return res;
  151. case RULE_LINES_GT:
  152. res = ( Ov_lines( ov ) > r->data.amount );
  153. if ( res )
  154. Log_dbg( LOG_DBG_FILTER,
  155. "Length rule: lines %d > %d",
  156. Ov_lines( ov ), r->data.amount );
  157. return res;
  158. case RULE_MSGID:
  159. res = ( regexec( &r->data.regex, Ov_msgId( ov ), 0, NULL, 0 ) == 0 );
  160. if ( res )
  161. Log_dbg( LOG_DBG_FILTER,
  162. "Msg-Id rule: %s matches",
  163. Ov_msgId( ov ) );
  164. return res;
  165. case RULE_DATE_LT:
  166. /* Utl_parseNewsDate() is quite picky. I'm not entirely happy
  167. about this, but I won't implement a relaxed date parser. */
  168. articletime = Utl_parseNewsDate( Ov_date( ov ) );
  169. if ( articletime == (time_t) -1 )
  170. return FALSE;
  171. res = ( articletime < r->data.reftime.calctime );
  172. if ( res )
  173. Log_dbg( LOG_DBG_FILTER,
  174. "Date before rule: %s matches",
  175. Ov_date( ov ) );
  176. return res;
  177. case RULE_DATE_EQ:
  178. articletime = Utl_parseNewsDate( Ov_date( ov ) );
  179. if ( ( articletime == (time_t) -1)
  180. && ( r->data.reftime.vartime == INVALID ))
  181. {
  182. Log_dbg( LOG_DBG_FILTER,
  183. "Date equals rule: invalid date matches" );
  184. return TRUE;
  185. }
  186. if ( ( articletime == (time_t) -1)
  187. != ( r->data.reftime.vartime == INVALID ))
  188. return FALSE;
  189. res = ( ( articletime <= r->data.reftime.calctime
  190. + RULE_DATE_EQ_PRECISION )
  191. && ( articletime >= r->data.reftime.calctime
  192. - RULE_DATE_EQ_PRECISION ) );
  193. if ( res )
  194. Log_dbg( LOG_DBG_FILTER,
  195. "Date equals rule: %s matches",
  196. Ov_date( ov ) );
  197. return res;
  198. case RULE_DATE_GT:
  199. articletime = Utl_parseNewsDate( Ov_date( ov ) );
  200. if ( articletime == (time_t) -1 )
  201. return FALSE;
  202. res = ( articletime > r->data.reftime.calctime );
  203. if ( res )
  204. Log_dbg( LOG_DBG_FILTER,
  205. "Date after rule: %s matches",
  206. Ov_date( ov ) );
  207. return res;
  208. case RULE_NOREFS_LT:
  209. ul = countRefs( Ov_ref( ov ) );
  210. res = ( ul < r->data.amount );
  211. if ( res )
  212. Log_dbg( LOG_DBG_FILTER,
  213. "Number of references rule: %d < %d",
  214. ul, r->data.amount );
  215. return res;
  216. case RULE_NOREFS_EQ:
  217. ul = countRefs( Ov_ref( ov ) );
  218. res = ( ul == r->data.amount );
  219. if ( res )
  220. Log_dbg( LOG_DBG_FILTER,
  221. "Number of references rule: %d = %d",
  222. ul, r->data.amount );
  223. return res;
  224. case RULE_NOREFS_GT:
  225. ul = countRefs( Ov_ref( ov ) );
  226. res = ( ul > r->data.amount );
  227. if ( res )
  228. Log_dbg( LOG_DBG_FILTER,
  229. "Number of references rule: %d > %d",
  230. ul, r->data.amount );
  231. return res;
  232. case RULE_XPOSTS_LT:
  233. if ( newsgroups == NULL )
  234. return FALSE;
  235. ul = countGroups( newsgroups );
  236. res = ( ul < r->data.amount );
  237. if ( res )
  238. Log_dbg( LOG_DBG_FILTER,
  239. "Number of cross-posts rule: %d < %d",
  240. ul, r->data.amount );
  241. return res;
  242. case RULE_XPOSTS_EQ:
  243. if ( newsgroups == NULL )
  244. return FALSE;
  245. ul = countGroups( newsgroups );
  246. res = ( ul == r->data.amount );
  247. if ( res )
  248. Log_dbg( LOG_DBG_FILTER,
  249. "Number of cross-posts rule: %d = %d",
  250. ul, r->data.amount );
  251. return res;
  252. case RULE_XPOSTS_GT:
  253. if ( newsgroups == NULL )
  254. return FALSE;
  255. ul = countGroups( newsgroups );
  256. res = ( ul > r->data.amount );
  257. if ( res )
  258. Log_dbg( LOG_DBG_FILTER,
  259. "Number of cross-posts rule: %d > %d",
  260. ul, r->data.amount );
  261. return res;
  262. case RULE_POST_STATUS:
  263. if ( Grp_postAllow( thisGrp ) == r->data.postAllow )
  264. {
  265. Log_dbg( LOG_DBG_FILTER,
  266. "Post status rule: group status matches %c",
  267. r->data.postAllow );
  268. return TRUE;
  269. }
  270. return FALSE;
  271. }
  272. ASSERT( FALSE ); /* Shouldn't get here */
  273. return 0; /* Keep compiler quiet */
  274. }
  275. /* Check a single filter to see if it fires. */
  276. static Bool
  277. checkFilter( const char *thisGrp, const char *newsgroups,
  278. const Over *ov, const Filter *f )
  279. {
  280. int i;
  281. for ( i = 0; i < f->nRules; i++ )
  282. if ( ! checkRule( thisGrp, newsgroups, ov, &f->rules[i] ) )
  283. return FALSE;
  284. return TRUE;
  285. }
  286. /* Add a filter to the list of filters. */
  287. void
  288. Flt_addFilter( const Filter *f )
  289. {
  290. ASSERT( f != NULL );
  291. if ( ( filter.nFilters + 1 ) > filter.maxFilters )
  292. {
  293. filter.filters =
  294. ( const Filter ** ) realloc( filter.filters,
  295. ( filter.maxFilters + 5 )
  296. * sizeof( Filter * ) );
  297. if ( filter.filters == NULL )
  298. Log_fatal( "Could not realloc filter list" );
  299. filter.maxFilters += 5;
  300. }
  301. filter.filters[ filter.nFilters++ ] = f;
  302. }
  303. /*
  304. * Called by Fetch_init().
  305. * Must be called before
  306. * Fetch_getNewGrps(), Client_getNewgrps(), client.c:processGrps()
  307. * because processGrps() sets the stampfile needed by lastupdate.
  308. */
  309. void
  310. Flt_init( const char * server )
  311. {
  312. int index1, index2;
  313. time_t now, lastupdate;
  314. FilterRule * thisRule ;
  315. Str filename;
  316. time ( &now );
  317. lastupdate = (time_t) 0; /* defaults to start of epoch */
  318. snprintf( filename, MAXCHAR, "%s/lastupdate.%s",
  319. Cfg_spoolDir(), server );
  320. if ( !Utl_getStamp( &lastupdate , filename ) )
  321. /* There's no stamp file if server has never been queried.
  322. *
  323. */
  324. Log_dbg( LOG_DBG_FILTER,
  325. "Filter unable to get stamp file %s . Please query server.", filename );
  326. /* traverse all rules of all filters */
  327. for ( index1 = 0; index1 < filter.nFilters; index1++ )
  328. {
  329. for ( index2 = 0; index2 < filter.filters[ index1 ] -> nRules; index2++ )
  330. {
  331. thisRule = & ( filter.filters[ index1 ] -> rules[ index2 ] );
  332. switch ( thisRule -> type )
  333. {
  334. /* evaluate variable date specs */
  335. case RULE_DATE_LT:
  336. case RULE_DATE_EQ:
  337. case RULE_DATE_GT:
  338. thisRule -> data.reftime.calctime =
  339. thisRule ->data.reftime.timeoffset;
  340. switch ( thisRule ->data.reftime.vartime )
  341. {
  342. case NOW:
  343. thisRule -> data.reftime.calctime += now;
  344. break;
  345. case LASTUPDATE:
  346. thisRule -> data.reftime.calctime += lastupdate;
  347. break;
  348. default:
  349. break;
  350. } /* end switch( ... vartime) */
  351. /* Silently fix absolute dates before the epoch.
  352. * This is not the place to mock about strange dates.
  353. */
  354. if ( thisRule -> data.reftime.calctime < (time_t) 0 )
  355. thisRule -> data.reftime.calctime = (time_t) 0 ;
  356. #if 0
  357. Log_dbg( LOG_DBG_FILTER, "%d: %dl = %dl + %d",
  358. thisRule -> type,
  359. (long) thisRule -> data.reftime.calctime,
  360. (long) thisRule ->data.reftime.timeoffset,
  361. (int) thisRule ->data.reftime.vartime == NOW
  362. ? now
  363. : thisRule ->data.reftime.vartime == LASTUPDATE
  364. ? lastupdate
  365. : thisRule ->data.reftime.vartime );
  366. #endif
  367. break;
  368. default:
  369. break;
  370. } /* end switch( ... -> type) */
  371. } /* end for() */
  372. } /* end for() */
  373. return ;
  374. }
  375. /*
  376. * Run the rules over the supplied overview. If a specific rule fires,
  377. * returns its action. If no rule fires, or a rule specifying the default
  378. * action fires, return the default read mode.
  379. */
  380. FilterAction
  381. Flt_checkFilters( const char *thisGrp, const char *newsgroups,
  382. const Over *ov, FetchMode mode )
  383. {
  384. int i;
  385. for ( i = 0; i < filter.nFilters; i++ )
  386. if ( checkFilter( thisGrp, newsgroups, ov, filter.filters[ i ] ) )
  387. {
  388. FilterAction action = filter.filters[ i ]->action;
  389. Log_dbg( LOG_DBG_FILTER,
  390. "Filter %d fired on message %s",
  391. i, Ov_msgId( ov ) );
  392. if ( action == FILTER_DEFAULT )
  393. break;
  394. else
  395. return action;
  396. }
  397. switch( mode )
  398. {
  399. case FULL: return FILTER_FULL;
  400. case THREAD: return FILTER_THREAD;
  401. case OVER: return FILTER_XOVER;
  402. }
  403. ASSERT( FALSE ); /* Shouldn't get here */
  404. return FILTER_FULL; /* Keep compiler quiet */
  405. }
  406. Filter *
  407. new_Filter( void )
  408. {
  409. Filter *f;
  410. if ( ! ( f = ( Filter * ) malloc( sizeof( Filter ) ) ) )
  411. Log_fatal( "Cannot allocate Filter" );
  412. f->nRules = 0;
  413. f->maxRules = 0;
  414. f->rules = NULL;
  415. f->action = FILTER_DEFAULT;
  416. return f;
  417. }
  418. void
  419. del_Filter( Filter *f )
  420. {
  421. if ( f == NULL )
  422. return;
  423. if ( f->rules != NULL )
  424. free( f->rules );
  425. free( f );
  426. }
  427. FilterAction
  428. Flt_action( const Filter *f )
  429. {
  430. return f->action;
  431. }
  432. int
  433. Flt_nRules( const Filter *f )
  434. {
  435. return f->nRules;
  436. }
  437. /*
  438. * Do we have a rule requiring us to fetch the Newsgroups: headers of
  439. * articles?
  440. */
  441. Bool
  442. Flt_getNewsgroups( void )
  443. {
  444. return filter.needGroups;
  445. }
  446. FilterRule
  447. Flt_rule( const Filter *f, int ruleNo )
  448. {
  449. ASSERT( ruleNo < f->nRules );
  450. return f->rules[ ruleNo ];
  451. }
  452. void
  453. Flt_setAction( Filter *f, FilterAction action )
  454. {
  455. f->action = action;
  456. }
  457. void
  458. Flt_addRule( Filter *f, FilterRule rule )
  459. {
  460. /* Does the rule require Newsgroups: headers to be fetched? */
  461. if ( rule.type == RULE_NEWSGROUP ||
  462. ( rule.type >= RULE_XPOSTS_LT && rule.type <= RULE_XPOSTS_GT ) )
  463. filter.needGroups = TRUE;
  464. if ( f->nRules + 1 > f->maxRules )
  465. {
  466. f->rules =
  467. ( FilterRule * ) realloc( f->rules,
  468. ( f->maxRules + 5 )
  469. * sizeof( FilterRule ) );
  470. if ( f->rules == NULL )
  471. Log_fatal( "Could not realloc rule list" );
  472. f->maxRules += 5;
  473. }
  474. f->rules[ f->nRules++ ] = rule;
  475. }