* Update to version 2.19.5
[alpine.git] / pith / pattern.c
blob3a258e7cf4a8180d4f43f45dffd4d33d9c0f699d
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: pattern.c 1204 2009-02-02 19:54:23Z hubert@u.washington.edu $";
3 #endif
4 /*
5 * ========================================================================
6 * Copyright 2006-2009 University of Washington
7 * Copyright 2013-2014 Eduardo Chappa
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * ========================================================================
18 #include "../pith/headers.h"
19 #include "../pith/pattern.h"
20 #include "../pith/state.h"
21 #include "../pith/conf.h"
22 #include "../pith/string.h"
23 #include "../pith/msgno.h"
24 #include "../pith/status.h"
25 #include "../pith/list.h"
26 #include "../pith/flag.h"
27 #include "../pith/tempfile.h"
28 #include "../pith/addrstring.h"
29 #include "../pith/search.h"
30 #include "../pith/mailcmd.h"
31 #include "../pith/filter.h"
32 #include "../pith/save.h"
33 #include "../pith/mimedesc.h"
34 #include "../pith/reply.h"
35 #include "../pith/folder.h"
36 #include "../pith/maillist.h"
37 #include "../pith/sort.h"
38 #include "../pith/copyaddr.h"
39 #include "../pith/pipe.h"
40 #include "../pith/list.h"
41 #include "../pith/news.h"
42 #include "../pith/util.h"
43 #include "../pith/sequence.h"
44 #include "../pith/detoken.h"
45 #include "../pith/busy.h"
46 #include "../pith/indxtype.h"
47 #include "../pith/mailindx.h"
48 #include "../pith/send.h"
49 #include "../pith/icache.h"
50 #include "../pith/ablookup.h"
51 #include "../pith/keyword.h"
55 * Internal prototypes
57 void open_any_patterns(long);
58 void sub_open_any_patterns(long);
59 void sub_close_patterns(long);
60 int sub_any_patterns(long, PAT_STATE *);
61 PAT_LINE_S *parse_pat_lit(char *);
62 PAT_LINE_S *parse_pat_inherit(void);
63 PAT_S *parse_pat(char *);
64 void parse_patgrp_slash(char *, PATGRP_S *);
65 void parse_action_slash(char *, ACTION_S *);
66 ARBHDR_S *parse_arbhdr(char *);
67 char *next_arb(char *);
68 PAT_S *first_any_pattern(PAT_STATE *);
69 PAT_S *last_any_pattern(PAT_STATE *);
70 PAT_S *prev_any_pattern(PAT_STATE *);
71 PAT_S *next_any_pattern(PAT_STATE *);
72 int sub_write_patterns(long);
73 int write_pattern_file(char **, PAT_LINE_S *);
74 int write_pattern_lit(char **, PAT_LINE_S *);
75 int write_pattern_inherit(char **, PAT_LINE_S *);
76 char *data_for_patline(PAT_S *);
77 int charsets_present_in_msg(MAILSTREAM *, unsigned long, STRLIST_S *);
78 void collect_charsets_from_subj(ENVELOPE *, STRLIST_S **);
79 void collect_charsets_from_body(BODY *, STRLIST_S **);
80 SEARCHPGM *next_not(SEARCHPGM *);
81 SEARCHOR *next_or(SEARCHOR **);
82 void set_up_search_pgm(char *, PATTERN_S *, SEARCHPGM *);
83 void add_type_to_pgm(char *, PATTERN_S *, SEARCHPGM *);
84 void set_srch(char *, char *, SEARCHPGM *);
85 void set_srch_hdr(char *, char *, SEARCHPGM *);
86 void set_search_by_age(INTVL_S *, SEARCHPGM *, int);
87 void set_search_by_size(INTVL_S *, SEARCHPGM *);
88 int non_eh(char *);
89 void add_eh(char **, char **, char *, int *);
90 void set_extra_hdrs(char *);
91 int is_ascii_string(char *);
92 ACTION_S *combine_inherited_role_guts(ACTION_S *);
93 int move_filtered_msgs(MAILSTREAM *, MSGNO_S *, char *, int, char *);
94 void set_some_flags(MAILSTREAM *, MSGNO_S *, long, char **, char **, int, char *);
98 * optional hook for external-program filter test
100 void (*pith_opt_filter_pattern_cmd)(char **, SEARCHSET *, MAILSTREAM *, long, INTVL_S *);
103 void
104 role_process_filters(void)
106 int i;
107 MAILSTREAM *stream;
108 MSGNO_S *msgmap;
110 for(i = 0; i < ps_global->s_pool.nstream; i++){
111 stream = ps_global->s_pool.streams[i];
112 if(stream && pine_mail_ping(stream)){
113 msgmap = sp_msgmap(stream);
114 if(msgmap)
115 reprocess_filter_patterns(stream, msgmap, MI_REFILTERING);
122 add_to_pattern(PAT_S *pat, long int rflags)
124 PAT_LINE_S *new_patline, *patline;
125 PAT_S *new_pat;
126 PAT_STATE dummy;
128 if(!any_patterns(rflags, &dummy))
129 return(0);
131 /* need a new patline */
132 new_patline = (PAT_LINE_S *)fs_get(sizeof(*new_patline));
133 memset((void *)new_patline, 0, sizeof(*new_patline));
134 new_patline->type = Literal;
135 (*cur_pat_h)->dirtypinerc = 1;
137 /* and a copy of pat */
138 new_pat = copy_pat(pat);
140 /* tie together */
141 new_patline->first = new_patline->last = new_pat;
142 new_pat->patline = new_patline;
146 * Manipulate bits directly in pattern list.
147 * Cur_pat_h is set by any_patterns.
151 /* find last patline */
152 for(patline = (*cur_pat_h)->patlinehead;
153 patline && patline->next;
154 patline = patline->next)
157 /* add new patline to end of list */
158 if(patline){
159 patline->next = new_patline;
160 new_patline->prev = patline;
162 else
163 (*cur_pat_h)->patlinehead = new_patline;
165 return(1);
170 * Does pattern quoting. Takes the string that the user sees and converts
171 * it to the config file string.
173 * Args: src -- The source string.
175 * The last arg to add_escapes causes \, and \\ to be replaced with hex
176 * versions of comma and backslash. That's so we can embed commas in
177 * list variables without having them act as separators. If the user wants
178 * a literal comma, they type backslash comma.
179 * If /, \, or " appear (other than the special cases in previous sentence)
180 * they are backslash-escaped like \/, \\, or \".
182 * Returns: An allocated string with quoting added.
184 * The caller is responsible for freeing the memory allocated for the answer.
186 char *
187 add_pat_escapes(char *src)
189 return(add_escapes(src, "/\\\"", '\\', "", ",\\"));
194 * Undoes the escape quoting done by add_pat_escapes.
196 * Args: src -- The source string.
198 * Returns: A string with backslash quoting removed or NULL. The string starts
199 * at src and goes until the end of src or until a / is reached. The
200 * / is not included in the string. /'s may be quoted by preceding
201 * them with a backslash (\) and \'s may also be quoted by
202 * preceding them with a \. In fact, \ quotes any character.
203 * Not quite, \nnn is octal escape, \xXX is hex escape.
204 * Hex escapes are undone but left with a backslash in front.
206 * The caller is responsible for freeing the memory allocated for the answer.
208 char *
209 remove_pat_escapes(char *src)
211 char *ans = NULL, *q, *p;
212 int done = 0;
214 if(src){
215 p = q = (char *)fs_get(strlen(src) + 1);
217 while(!done){
218 switch(*src){
219 case '\\':
220 src++;
221 if(*src){
222 if(isdigit((unsigned char)*src)){ /* octal escape */
223 *p++ = '\\';
224 *p++ = (char)read_octal(&src);
226 else if((*src == 'x' || *src == 'X') &&
227 *(src+1) && *(src+2) && isxpair(src+1)){
228 *p++ = '\\';
229 *p++ = (char)read_hex(src+1);
230 src += 3;
232 else
233 *p++ = *src++;
236 break;
238 case '\0':
239 case '/':
240 done++;
241 break;
243 default:
244 *p++ = *src++;
245 break;
249 *p = '\0';
251 ans = cpystr(q);
252 fs_give((void **)&q);
255 return(ans);
260 * This takes envelope data and adds the backslash escapes that the user
261 * would have been responsible for adding if editing manually.
262 * It just escapes commas and backslashes.
264 * Caller must free result.
266 char *
267 add_roletake_escapes(char *src)
269 return(add_escapes(src, ",\\", '\\', "", ""));
273 * This function only escapes commas.
275 char *
276 add_comma_escapes(char *src)
278 return(add_escapes(src, ",", '\\', "", ""));
283 * These are the global pattern handles which all of the pattern routines
284 * use. Once we open one of these we usually leave it open until exiting
285 * pine. The _any versions are only used if we are altering our configuration,
286 * the _ne (NonEmpty) versions are used routinely. We open the patterns by
287 * calling either nonempty_patterns (normal use) or any_patterns (config).
289 * There are eight different pinerc variables which contain patterns. They are
290 * patterns-filters2, patterns-roles, patterns-scores2, patterns-indexcolors,
291 * patterns-other, and the old patterns, patterns-filters, and patterns-scores.
292 * The first five are the active patterns variables and the old variable are
293 * kept around so that we can convert old patterns to new. The reason we
294 * split it into five separate variables is so that each can independently
295 * be controlled by the main pinerc or by the exception pinerc. The reason
296 * for the change to filters2 and scores2 was so we could change the semantics
297 * of how rules work when there are pieces in the rule that we don't
298 * understand. We added a rule to detect 8bitSubjects. So a user might have
299 * a filter that deletes messages with 8bitSubjects. The problem was that
300 * that same filter in a old patterns-filters pine would match because it
301 * would ignore the 8bitSubject part of the pattern and match on the rest.
302 * So we changed the semantics so that rules with unknown pieces would be
303 * ignored instead of used. We had to change variable names at the same time
304 * because we were adding the 8bit thing and the old pines are still out
305 * there. Filters and Scores can both be dangerous. Roles, Colors, and Other
306 * seem less dangerous so not worth adding a new variable for them.
308 * Each of the eight variables has its own handle and status variables below.
309 * That means that they operate independently.
311 * Looking at just a single one of those variables, it has four possible
312 * values. In normal use, we use the current_val of the variable to set
313 * up the patterns. We do that by calling nonempty_patterns() with the
314 * appropriate rflags. When editing configurations, we have the other two
315 * variables to deal with: main_user_val and post_user_val.
316 * We only ever deal with one of those at a time, so we re-use the variables.
317 * However, we do sometimes want to deal with one of those and at the same
318 * time refer to the current current_val. For example, if we are editing
319 * the post or main user_val for the filters variable, we still want
320 * to check for new mail. If we find new mail we'll want to call
321 * process_filter_patterns which uses the current_val for filter patterns.
322 * That means we have to provide for the case where we are using current_val
323 * at the same time as we're using one of the user_vals. That's why we have
324 * both the _ne variables (NonEmpty) and the _any variables.
326 * In any_patterns (and first_pattern...) use_flags may only be set to
327 * one value at a time, whereas rflags may be more than one value OR'd together.
329 PAT_HANDLE **cur_pat_h;
330 static PAT_HANDLE *pattern_h_roles_ne, *pattern_h_roles_any,
331 *pattern_h_scores_ne, *pattern_h_scores_any,
332 *pattern_h_filts_ne, *pattern_h_filts_any,
333 *pattern_h_filts_cfg,
334 *pattern_h_filts_ne, *pattern_h_filts_any,
335 *pattern_h_incol_ne, *pattern_h_incol_any,
336 *pattern_h_other_ne, *pattern_h_other_any,
337 *pattern_h_srch_ne, *pattern_h_srch_any,
338 *pattern_h_oldpat_ne, *pattern_h_oldpat_any;
341 * These contain the PAT_OPEN_MASK open status and the PAT_USE_MASK use status.
343 static long *cur_pat_status;
344 static long pat_status_roles_ne, pat_status_roles_any,
345 pat_status_scores_ne, pat_status_scores_any,
346 pat_status_filts_ne, pat_status_filts_any,
347 pat_status_incol_ne, pat_status_incol_any,
348 pat_status_other_ne, pat_status_other_any,
349 pat_status_srch_ne, pat_status_srch_any,
350 pat_status_oldpat_ne, pat_status_oldpat_any,
351 pat_status_oldfilt_ne, pat_status_oldfilt_any,
352 pat_status_oldscore_ne, pat_status_oldscore_any;
354 #define SET_PATTYPE(rflags) \
355 set_pathandle(rflags); \
356 cur_pat_status = \
357 ((rflags) & PAT_USE_CURRENT) \
358 ? (((rflags) & ROLE_DO_INCOLS) ? &pat_status_incol_ne : \
359 ((rflags) & ROLE_DO_OTHER) ? &pat_status_other_ne : \
360 ((rflags) & ROLE_DO_FILTER) ? &pat_status_filts_ne : \
361 ((rflags) & ROLE_DO_SCORES) ? &pat_status_scores_ne : \
362 ((rflags) & ROLE_DO_ROLES) ? &pat_status_roles_ne : \
363 ((rflags) & ROLE_DO_SRCH) ? &pat_status_srch_ne : \
364 ((rflags) & ROLE_OLD_FILT) ? &pat_status_oldfilt_ne : \
365 ((rflags) & ROLE_OLD_SCORE) ? &pat_status_oldscore_ne :\
366 &pat_status_oldpat_ne) \
367 : (((rflags) & ROLE_DO_INCOLS) ? &pat_status_incol_any : \
368 ((rflags) & ROLE_DO_OTHER) ? &pat_status_other_any : \
369 ((rflags) & ROLE_DO_FILTER) ? &pat_status_filts_any : \
370 ((rflags) & ROLE_DO_SCORES) ? &pat_status_scores_any : \
371 ((rflags) & ROLE_DO_ROLES) ? &pat_status_roles_any : \
372 ((rflags) & ROLE_DO_SRCH) ? &pat_status_srch_any : \
373 ((rflags) & ROLE_OLD_FILT) ? &pat_status_oldfilt_any :\
374 ((rflags) & ROLE_OLD_SCORE) ? &pat_status_oldscore_any:\
375 &pat_status_oldpat_any);
376 #define CANONICAL_RFLAGS(rflags) \
377 ((((rflags) & (ROLE_DO_ROLES | ROLE_REPLY | ROLE_FORWARD | ROLE_COMPOSE)) \
378 ? ROLE_DO_ROLES : 0) | \
379 (((rflags) & (ROLE_DO_INCOLS | ROLE_INCOL)) \
380 ? ROLE_DO_INCOLS : 0) | \
381 (((rflags) & (ROLE_DO_SCORES | ROLE_SCORE)) \
382 ? ROLE_DO_SCORES : 0) | \
383 (((rflags) & (ROLE_DO_FILTER)) \
384 ? ROLE_DO_FILTER : 0) | \
385 (((rflags) & (ROLE_DO_OTHER)) \
386 ? ROLE_DO_OTHER : 0) | \
387 (((rflags) & (ROLE_DO_SRCH)) \
388 ? ROLE_DO_SRCH : 0) | \
389 (((rflags) & (ROLE_OLD_FILT)) \
390 ? ROLE_OLD_FILT : 0) | \
391 (((rflags) & (ROLE_OLD_SCORE)) \
392 ? ROLE_OLD_SCORE : 0) | \
393 (((rflags) & (ROLE_OLD_PAT)) \
394 ? ROLE_OLD_PAT : 0))
396 #define SETPGMSTATUS(val,yes,no) \
397 switch(val){ \
398 case PAT_STAT_YES: \
399 (yes) = 1; \
400 break; \
401 case PAT_STAT_NO: \
402 (no) = 1; \
403 break; \
404 case PAT_STAT_EITHER: \
405 default: \
406 break; \
409 #define SET_STATUS(srchin,srchfor,assignto) \
410 {char *qq, *pp; \
411 int ii; \
412 NAMEVAL_S *vv; \
413 if((qq = srchstr(srchin, srchfor)) != NULL){ \
414 if((pp = remove_pat_escapes(qq+strlen(srchfor))) != NULL){ \
415 for(ii = 0; (vv = role_status_types(ii)); ii++) \
416 if(!strucmp(pp, vv->shortname)){ \
417 assignto = vv->value; \
418 break; \
421 fs_give((void **)&pp); \
426 #define SET_MSGSTATE(srchin,srchfor,assignto) \
427 {char *qq, *pp; \
428 int ii; \
429 NAMEVAL_S *vv; \
430 if((qq = srchstr(srchin, srchfor)) != NULL){ \
431 if((pp = remove_pat_escapes(qq+strlen(srchfor))) != NULL){ \
432 for(ii = 0; (vv = msg_state_types(ii)); ii++) \
433 if(!strucmp(pp, vv->shortname)){ \
434 assignto = vv->value; \
435 break; \
438 fs_give((void **)&pp); \
443 #define PATTERN_N (9)
446 void
447 set_pathandle(long int rflags)
449 cur_pat_h = (rflags & PAT_USE_CURRENT)
450 ? ((rflags & ROLE_DO_INCOLS) ? &pattern_h_incol_ne :
451 (rflags & ROLE_DO_OTHER) ? &pattern_h_other_ne :
452 (rflags & ROLE_DO_FILTER) ? &pattern_h_filts_ne :
453 (rflags & ROLE_DO_SCORES) ? &pattern_h_scores_ne :
454 (rflags & ROLE_DO_ROLES) ? &pattern_h_roles_ne :
455 (rflags & ROLE_DO_SRCH) ? &pattern_h_srch_ne :
456 &pattern_h_oldpat_ne)
457 : ((rflags & PAT_USE_CHANGED)
458 ? &pattern_h_filts_cfg
459 : ((rflags & ROLE_DO_INCOLS) ? &pattern_h_incol_any :
460 (rflags & ROLE_DO_OTHER) ? &pattern_h_other_any :
461 (rflags & ROLE_DO_FILTER) ? &pattern_h_filts_any :
462 (rflags & ROLE_DO_SCORES) ? &pattern_h_scores_any :
463 (rflags & ROLE_DO_ROLES) ? &pattern_h_roles_any :
464 (rflags & ROLE_DO_SRCH) ? &pattern_h_srch_any :
465 &pattern_h_oldpat_any));
470 * Rflags may be more than one pattern type OR'd together. It also contains
471 * the "use" parameter.
473 void
474 open_any_patterns(long int rflags)
476 long canon_rflags;
478 dprint((7, "open_any_patterns(0x%x)\n", rflags));
480 canon_rflags = CANONICAL_RFLAGS(rflags);
482 if(canon_rflags & ROLE_DO_INCOLS)
483 sub_open_any_patterns(ROLE_DO_INCOLS | (rflags & PAT_USE_MASK));
484 if(canon_rflags & ROLE_DO_FILTER)
485 sub_open_any_patterns(ROLE_DO_FILTER | (rflags & PAT_USE_MASK));
486 if(canon_rflags & ROLE_DO_OTHER)
487 sub_open_any_patterns(ROLE_DO_OTHER | (rflags & PAT_USE_MASK));
488 if(canon_rflags & ROLE_DO_SCORES)
489 sub_open_any_patterns(ROLE_DO_SCORES | (rflags & PAT_USE_MASK));
490 if(canon_rflags & ROLE_DO_ROLES)
491 sub_open_any_patterns(ROLE_DO_ROLES | (rflags & PAT_USE_MASK));
492 if(canon_rflags & ROLE_DO_SRCH)
493 sub_open_any_patterns(ROLE_DO_SRCH | (rflags & PAT_USE_MASK));
494 if(canon_rflags & ROLE_OLD_FILT)
495 sub_open_any_patterns(ROLE_OLD_FILT | (rflags & PAT_USE_MASK));
496 if(canon_rflags & ROLE_OLD_SCORE)
497 sub_open_any_patterns(ROLE_OLD_SCORE | (rflags & PAT_USE_MASK));
498 if(canon_rflags & ROLE_OLD_PAT)
499 sub_open_any_patterns(ROLE_OLD_PAT | (rflags & PAT_USE_MASK));
504 * This should only be called with a single pattern type (plus use flags).
505 * We assume that patterns of this type are closed before this is called.
506 * This always succeeds unless we run out of memory, in which case fs_get
507 * never returns.
509 void
510 sub_open_any_patterns(long int rflags)
512 PAT_LINE_S *patline = NULL, *pl = NULL;
513 char **t = NULL;
514 struct variable *var;
516 SET_PATTYPE(rflags);
518 *cur_pat_h = (PAT_HANDLE *)fs_get(sizeof(**cur_pat_h));
519 memset((void *)*cur_pat_h, 0, sizeof(**cur_pat_h));
521 if(rflags & ROLE_DO_ROLES)
522 var = &ps_global->vars[V_PAT_ROLES];
523 else if(rflags & ROLE_DO_FILTER)
524 var = &ps_global->vars[V_PAT_FILTS];
525 else if(rflags & ROLE_DO_OTHER)
526 var = &ps_global->vars[V_PAT_OTHER];
527 else if(rflags & ROLE_DO_SCORES)
528 var = &ps_global->vars[V_PAT_SCORES];
529 else if(rflags & ROLE_DO_INCOLS)
530 var = &ps_global->vars[V_PAT_INCOLS];
531 else if(rflags & ROLE_DO_SRCH)
532 var = &ps_global->vars[V_PAT_SRCH];
533 else if(rflags & ROLE_OLD_FILT)
534 var = &ps_global->vars[V_PAT_FILTS_OLD];
535 else if(rflags & ROLE_OLD_SCORE)
536 var = &ps_global->vars[V_PAT_SCORES_OLD];
537 else if(rflags & ROLE_OLD_PAT)
538 var = &ps_global->vars[V_PATTERNS];
540 switch(rflags & PAT_USE_MASK){
541 case PAT_USE_CURRENT:
542 t = var->current_val.l;
543 break;
544 case PAT_USE_CHANGED:
546 * some trickery to only use changed if actually changed.
547 * otherwise, use current_val
549 t = var->is_changed_val ? var->changed_val.l : var->current_val.l;
550 break;
551 case PAT_USE_MAIN:
552 t = var->main_user_val.l;
553 break;
554 case PAT_USE_POST:
555 t = var->post_user_val.l;
556 break;
559 if(t){
560 for(; t[0] && t[0][0]; t++){
561 if(*t && !strncmp("LIT:", *t, 4))
562 patline = parse_pat_lit(*t + 4);
563 else if(*t && !strncmp("FILE:", *t, 5))
564 patline = parse_pat_file(*t + 5);
565 else if(rflags & (PAT_USE_MAIN | PAT_USE_POST) &&
566 patline == NULL && *t && !strcmp(INHERIT, *t))
567 patline = parse_pat_inherit();
568 else
569 patline = NULL;
571 if(patline){
572 if(pl){
573 pl->next = patline;
574 patline->prev = pl;
575 pl = pl->next;
577 else{
578 (*cur_pat_h)->patlinehead = patline;
579 pl = patline;
582 else
583 q_status_message1(SM_ORDER, 0, 3,
584 "Invalid patterns line \"%.200s\"", *t);
588 *cur_pat_status = PAT_OPENED | (rflags & PAT_USE_MASK);
592 void
593 close_every_pattern(void)
595 close_patterns(ROLE_DO_INCOLS | ROLE_DO_FILTER | ROLE_DO_SCORES
596 | ROLE_DO_OTHER | ROLE_DO_ROLES | ROLE_DO_SRCH
597 | ROLE_OLD_FILT | ROLE_OLD_SCORE | ROLE_OLD_PAT
598 | PAT_USE_CURRENT);
600 * Since there is only one set of variables for the other three uses
601 * we can just close any one of them. There can only be one open at
602 * a time.
604 close_patterns(ROLE_DO_INCOLS | ROLE_DO_FILTER | ROLE_DO_SCORES
605 | ROLE_DO_OTHER | ROLE_DO_ROLES | ROLE_DO_SRCH
606 | ROLE_OLD_FILT | ROLE_OLD_SCORE | ROLE_OLD_PAT
607 | PAT_USE_MAIN);
612 * Can be called with more than one pattern type.
614 void
615 close_patterns(long int rflags)
617 long canon_rflags;
619 dprint((7, "close_patterns(0x%x)\n", rflags));
621 canon_rflags = CANONICAL_RFLAGS(rflags);
623 if(canon_rflags & ROLE_DO_INCOLS)
624 sub_close_patterns(ROLE_DO_INCOLS | (rflags & PAT_USE_MASK));
625 if(canon_rflags & ROLE_DO_OTHER)
626 sub_close_patterns(ROLE_DO_OTHER | (rflags & PAT_USE_MASK));
627 if(canon_rflags & ROLE_DO_FILTER)
628 sub_close_patterns(ROLE_DO_FILTER | (rflags & PAT_USE_MASK));
629 if(canon_rflags & ROLE_DO_SCORES)
630 sub_close_patterns(ROLE_DO_SCORES | (rflags & PAT_USE_MASK));
631 if(canon_rflags & ROLE_DO_ROLES)
632 sub_close_patterns(ROLE_DO_ROLES | (rflags & PAT_USE_MASK));
633 if(canon_rflags & ROLE_DO_SRCH)
634 sub_close_patterns(ROLE_DO_SRCH | (rflags & PAT_USE_MASK));
635 if(canon_rflags & ROLE_OLD_FILT)
636 sub_close_patterns(ROLE_OLD_FILT | (rflags & PAT_USE_MASK));
637 if(canon_rflags & ROLE_OLD_SCORE)
638 sub_close_patterns(ROLE_OLD_SCORE | (rflags & PAT_USE_MASK));
639 if(canon_rflags & ROLE_OLD_PAT)
640 sub_close_patterns(ROLE_OLD_PAT | (rflags & PAT_USE_MASK));
645 * Can be called with only a single pattern type.
647 void
648 sub_close_patterns(long int rflags)
650 SET_PATTYPE(rflags);
652 if(*cur_pat_h != NULL){
653 free_patline(&(*cur_pat_h)->patlinehead);
654 fs_give((void **)cur_pat_h);
657 *cur_pat_status = PAT_CLOSED;
659 scores_are_used(SCOREUSE_INVALID);
664 * Can be called with more than one pattern type.
665 * Nonempty always uses PAT_USE_CURRENT (the current_val).
668 nonempty_patterns(long int rflags, PAT_STATE *pstate)
670 return(any_patterns((rflags & ROLE_MASK) | PAT_USE_CURRENT, pstate));
675 * Initializes pstate and parses and sets up appropriate pattern variables.
676 * May be called with more than one pattern type OR'd together in rflags.
677 * Pstate will keep track of that and next_pattern et. al. will increment
678 * through all of those pattern types.
681 any_patterns(long int rflags, PAT_STATE *pstate)
683 int ret = 0;
684 long canon_rflags;
686 dprint((7, "any_patterns(0x%x)\n", rflags));
688 memset((void *)pstate, 0, sizeof(*pstate));
689 pstate->rflags = rflags;
691 canon_rflags = CANONICAL_RFLAGS(pstate->rflags);
693 if(canon_rflags & ROLE_DO_INCOLS)
694 ret += sub_any_patterns(ROLE_DO_INCOLS, pstate);
695 if(canon_rflags & ROLE_DO_OTHER)
696 ret += sub_any_patterns(ROLE_DO_OTHER, pstate);
697 if(canon_rflags & ROLE_DO_FILTER)
698 ret += sub_any_patterns(ROLE_DO_FILTER, pstate);
699 if(canon_rflags & ROLE_DO_SCORES)
700 ret += sub_any_patterns(ROLE_DO_SCORES, pstate);
701 if(canon_rflags & ROLE_DO_ROLES)
702 ret += sub_any_patterns(ROLE_DO_ROLES, pstate);
703 if(canon_rflags & ROLE_DO_SRCH)
704 ret += sub_any_patterns(ROLE_DO_SRCH, pstate);
705 if(canon_rflags & ROLE_OLD_FILT)
706 ret += sub_any_patterns(ROLE_OLD_FILT, pstate);
707 if(canon_rflags & ROLE_OLD_SCORE)
708 ret += sub_any_patterns(ROLE_OLD_SCORE, pstate);
709 if(canon_rflags & ROLE_OLD_PAT)
710 ret += sub_any_patterns(ROLE_OLD_PAT, pstate);
712 return(ret);
717 sub_any_patterns(long int rflags, PAT_STATE *pstate)
719 SET_PATTYPE(rflags | (pstate->rflags & PAT_USE_MASK));
721 if(*cur_pat_h &&
722 (((pstate->rflags & PAT_USE_MASK) == PAT_USE_CURRENT &&
723 (*cur_pat_status & PAT_USE_MASK) != PAT_USE_CURRENT) ||
724 ((pstate->rflags & PAT_USE_MASK) != PAT_USE_CURRENT &&
725 ((*cur_pat_status & PAT_OPEN_MASK) != PAT_OPENED ||
726 (*cur_pat_status & PAT_USE_MASK) !=
727 (pstate->rflags & PAT_USE_MASK)))))
728 close_patterns(rflags | (pstate->rflags & PAT_USE_MASK));
730 /* open_any always succeeds */
731 if(!*cur_pat_h && ((*cur_pat_status & PAT_OPEN_MASK) == PAT_CLOSED))
732 open_any_patterns(rflags | (pstate->rflags & PAT_USE_MASK));
734 if(!*cur_pat_h){ /* impossible */
735 *cur_pat_status = PAT_CLOSED;
736 return(0);
740 * Opening nonempty can fail. That just means there aren't any
741 * patterns of that type.
743 if((pstate->rflags & PAT_USE_MASK) == PAT_USE_CURRENT &&
744 !(*cur_pat_h)->patlinehead)
745 *cur_pat_status = (PAT_OPEN_FAILED | PAT_USE_CURRENT);
747 return(((*cur_pat_status & PAT_OPEN_MASK) == PAT_OPENED) ? 1 : 0);
752 edit_pattern(PAT_S *newpat, int pos, long int rflags)
754 PAT_S *oldpat;
755 PAT_LINE_S *tpatline;
756 int i;
757 PAT_STATE pstate;
759 if(!any_patterns(rflags, &pstate)) return(1);
761 for(i = 0, tpatline = (*cur_pat_h)->patlinehead;
762 i < pos && tpatline; tpatline = tpatline->next, i++);
763 if(i != pos) return(1);
764 oldpat = tpatline->first;
765 free_pat(&oldpat);
766 tpatline->first = tpatline->last = newpat;
767 newpat->patline = tpatline;
768 tpatline->dirty = 1;
770 (*cur_pat_h)->dirtypinerc = 1;
771 write_patterns(rflags);
773 return(0);
777 add_pattern(PAT_S *newpat, long int rflags)
779 PAT_LINE_S *tpatline, *newpatline;
780 PAT_STATE pstate;
782 any_patterns(rflags, &pstate);
784 for(tpatline = (*cur_pat_h)->patlinehead;
785 tpatline && tpatline->next ; tpatline = tpatline->next);
786 newpatline = (PAT_LINE_S *)fs_get(sizeof(PAT_LINE_S));
787 if(tpatline)
788 tpatline->next = newpatline;
789 else
790 (*cur_pat_h)->patlinehead = newpatline;
791 memset((void *)newpatline, 0, sizeof(PAT_LINE_S));
792 newpatline->prev = tpatline;
793 newpatline->first = newpatline->last = newpat;
794 newpatline->type = Literal;
795 newpat->patline = newpatline;
796 newpatline->dirty = 1;
798 (*cur_pat_h)->dirtypinerc = 1;
799 write_patterns(rflags);
801 return(0);
805 delete_pattern(int pos, long int rflags)
807 PAT_LINE_S *tpatline;
808 int i;
809 PAT_STATE pstate;
811 if(!any_patterns(rflags, &pstate)) return(1);
813 for(i = 0, tpatline = (*cur_pat_h)->patlinehead;
814 i < pos && tpatline; tpatline = tpatline->next, i++);
815 if(i != pos) return(1);
817 if(tpatline == (*cur_pat_h)->patlinehead)
818 (*cur_pat_h)->patlinehead = tpatline->next;
819 if(tpatline->prev) tpatline->prev->next = tpatline->next;
820 if(tpatline->next) tpatline->next->prev = tpatline->prev;
821 tpatline->prev = NULL;
822 tpatline->next = NULL;
824 free_patline(&tpatline);
826 (*cur_pat_h)->dirtypinerc = 1;
827 write_patterns(rflags);
829 return(0);
833 shuffle_pattern(int pos, int up, long int rflags)
835 PAT_LINE_S *tpatline, *shufpatline;
836 int i;
837 PAT_STATE pstate;
839 if(!any_patterns(rflags, &pstate)) return(1);
841 for(i = 0, tpatline = (*cur_pat_h)->patlinehead;
842 i < pos && tpatline; tpatline = tpatline->next, i++);
843 if(i != pos) return(1);
845 if(up == 1){
846 if(tpatline->prev == NULL) return(1);
847 shufpatline = tpatline->prev;
848 tpatline->prev = shufpatline->prev;
849 if(shufpatline->prev)
850 shufpatline->prev->next = tpatline;
851 if(tpatline->next)
852 tpatline->next->prev = shufpatline;
853 shufpatline->next = tpatline->next;
854 shufpatline->prev = tpatline;
855 tpatline->next = shufpatline;
856 if(shufpatline == (*cur_pat_h)->patlinehead)
857 (*cur_pat_h)->patlinehead = tpatline;
859 else if(up == -1){
860 if(tpatline->next == NULL) return(1);
861 shufpatline = tpatline->next;
862 tpatline->next = shufpatline->next;
863 if(shufpatline->next)
864 shufpatline->next->prev = tpatline;
865 if(tpatline->prev)
866 tpatline->prev->next = shufpatline;
867 shufpatline->prev = tpatline->prev;
868 shufpatline->next = tpatline;
869 tpatline->prev = shufpatline;
870 if(tpatline == (*cur_pat_h)->patlinehead)
871 (*cur_pat_h)->patlinehead = shufpatline;
873 else return(1);
875 shufpatline->dirty = 1;
876 tpatline->dirty = 1;
878 (*cur_pat_h)->dirtypinerc = 1;
879 write_patterns(rflags);
881 return(0);
884 PAT_LINE_S *
885 parse_pat_lit(char *litpat)
887 PAT_LINE_S *patline;
888 PAT_S *pat;
890 patline = (PAT_LINE_S *)fs_get(sizeof(*patline));
891 memset((void *)patline, 0, sizeof(*patline));
892 patline->type = Literal;
895 if((pat = parse_pat(litpat)) != NULL){
896 pat->patline = patline;
897 patline->first = pat;
898 patline->last = pat;
901 return(patline);
906 * This always returns a patline even if we can't read the file. The patline
907 * returned will say readonly in the worst case and there will be no patterns.
908 * If the file doesn't exist, this creates it if possible.
910 PAT_LINE_S *
911 parse_pat_file(char *filename)
913 #define BUF_SIZE 5000
914 PAT_LINE_S *patline;
915 PAT_S *pat, *p;
916 char path[MAXPATH+1], buf[BUF_SIZE];
917 char *dir, *q;
918 FILE *fp;
919 int ok = 0, some_pats = 0;
920 struct variable *vars = ps_global->vars;
922 signature_path(filename, path, MAXPATH);
924 if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, path)){
925 q_status_message1(SM_ORDER | SM_DING, 3, 4,
926 "Can't use Roles file outside of %.200s",
927 VAR_OPER_DIR);
928 return(NULL);
931 patline = (PAT_LINE_S *)fs_get(sizeof(*patline));
932 memset((void *)patline, 0, sizeof(*patline));
933 patline->type = File;
934 patline->filename = cpystr(filename);
935 patline->filepath = cpystr(path);
937 if((q = last_cmpnt(path)) != NULL){
938 int save;
940 save = *--q;
941 *q = '\0';
942 dir = cpystr(*path ? path : "/");
943 *q = save;
945 else
946 dir = cpystr(".");
948 #if defined(DOS) || defined(OS2)
950 * If the dir has become a drive letter and : (e.g. "c:")
951 * then append a "\". The library function access() in the
952 * win 16 version of MSC seems to require this.
954 if(isalpha((unsigned char) *dir)
955 && *(dir+1) == ':' && *(dir+2) == '\0'){
956 *(dir+2) = '\\';
957 *(dir+3) = '\0';
959 #endif /* DOS || OS2 */
962 * Even if we can edit the file itself, we aren't going
963 * to be able to change it unless we can also write in
964 * the directory that contains it (because we write into a
965 * temp file and then rename).
967 if(can_access(dir, EDIT_ACCESS) != 0)
968 patline->readonly = 1;
970 if(can_access(path, EDIT_ACCESS) == 0){
971 if(patline->readonly)
972 q_status_message1(SM_ORDER, 0, 3,
973 "Pattern file directory (%.200s) is ReadOnly", dir);
975 else if(can_access(path, READ_ACCESS) == 0)
976 patline->readonly = 1;
978 if(can_access(path, ACCESS_EXISTS) == 0){
979 if((fp = our_fopen(path, "rb")) != NULL){
980 /* Check to see if this is a valid patterns file */
981 if(fp_file_size(fp) <= 0L)
982 ok++;
983 else{
984 size_t len;
986 len = strlen(PATTERN_MAGIC);
987 if(fread(buf, sizeof(char), len+3, fp) == len+3){
988 buf[len+3] = '\0';
989 buf[len] = '\0';
990 if(strcmp(buf, PATTERN_MAGIC) == 0){
991 if(atoi(PATTERN_FILE_VERS) < atoi(buf + len + 1))
992 q_status_message1(SM_ORDER, 0, 4,
993 "Pattern file \"%.200s\" is made by newer Alpine, will try to use it anyway",
994 filename);
996 ok++;
997 some_pats++;
998 /* toss rest of first line */
999 (void)fgets(buf, BUF_SIZE, fp);
1004 if(!ok){
1005 patline->readonly = 1;
1006 q_status_message1(SM_ORDER | SM_DING, 3, 4,
1007 "\"%.200s\" is not a Pattern file", path);
1010 p = NULL;
1011 while(some_pats && fgets(buf, BUF_SIZE, fp) != NULL){
1012 if((pat = parse_pat(buf)) != NULL){
1013 pat->patline = patline;
1014 if(!patline->first)
1015 patline->first = pat;
1017 patline->last = pat;
1019 if(p){
1020 p->next = pat;
1021 pat->prev = p;
1022 p = p->next;
1024 else
1025 p = pat;
1029 (void)fclose(fp);
1031 else{
1032 patline->readonly = 1;
1033 q_status_message2(SM_ORDER | SM_DING, 3, 4,
1034 "Error \"%.200s\" reading pattern file \"%.200s\"",
1035 error_description(errno), path);
1038 else{ /* doesn't exist yet, try to create it */
1039 if(patline->readonly)
1040 q_status_message1(SM_ORDER, 0, 3,
1041 "Pattern file directory (%.200s) is ReadOnly", dir);
1042 else{
1044 * We try to create it by making up an empty patline and calling
1045 * write_pattern_file.
1047 patline->dirty = 1;
1048 if(write_pattern_file(NULL, patline) != 0){
1049 patline->readonly = 1;
1050 patline->dirty = 0;
1051 q_status_message1(SM_ORDER | SM_DING, 3, 4,
1052 "Error creating pattern file \"%.200s\"",
1053 path);
1058 if(dir)
1059 fs_give((void **)&dir);
1061 return(patline);
1065 PAT_LINE_S *
1066 parse_pat_inherit(void)
1068 PAT_LINE_S *patline;
1069 PAT_S *pat;
1071 patline = (PAT_LINE_S *)fs_get(sizeof(*patline));
1072 memset((void *)patline, 0, sizeof(*patline));
1073 patline->type = Inherit;
1075 pat = (PAT_S *)fs_get(sizeof(*pat));
1076 memset((void *)pat, 0, sizeof(*pat));
1077 pat->inherit = 1;
1079 pat->patline = patline;
1080 patline->first = pat;
1081 patline->last = pat;
1083 return(patline);
1088 * There are three forms that a PATTERN_S has at various times. There is
1089 * the actual PATTERN_S struct which is used internally and is used whenever
1090 * we are actually doing something with the pattern, like filtering or
1091 * something. There is the version that goes in the config file. And there
1092 * is the version the user edits.
1094 * To go between these three forms we have the helper routines
1096 * pattern_to_config
1097 * config_to_pattern
1098 * pattern_to_editlist
1099 * editlist_to_pattern
1101 * Here's what is supposed to be happening. A PATTERN_S is a linked list
1102 * of strings with nothing escaped. That is, a backslash or a comma is
1103 * just in there as a backslash or comma.
1105 * The version the user edits is very similar. Because we have historically
1106 * used commas as separators the user has always had to enter a \, in order
1107 * to put a real comma in one of the items. That is the only difference
1108 * between a PATTERN_S string and the editlist strings. Note that backslashes
1109 * themselves are not escaped. A backslash which is not followed by a comma
1110 * is a backslash. It doesn't escape the following character. That's a bit
1111 * odd, it is that way because most people will never know about this
1112 * backslash stuff but PC-Pine users may have backslashes in folder names.
1114 * The version that goes in the config file has a few transformations made.
1115 * PATTERN_S intermediate_form Config string
1116 * , \, \x2C
1117 * \ \\ \x5C
1118 * / \/
1119 * " \"
1121 * The commas are turned into hex commas so that we can tell the separators
1122 * in the comma-separated lists from those commas.
1123 * The backslashes are escaped because they escape commas.
1124 * The /'s are escaped because they separate pattern pieces.
1125 * The "'s are escaped because they are significant to parse_list when
1126 * parsing the config file.
1127 * hubert - 2004-04-01
1128 * (date is only coincidental!)
1130 * Addendum. The not's are handled separately from all the strings. Not sure
1131 * why that is or if there is a good reason. Nevertheless, now is not the
1132 * time to figure it out so leave it that way.
1133 * hubert - 2004-07-14
1135 PAT_S *
1136 parse_pat(char *str)
1138 PAT_S *pat = NULL;
1139 char *p, *q, *astr, *pstr;
1140 int backslashed;
1141 #define PTRN "pattern="
1142 #define PTRNLEN 8
1143 #define ACTN "action="
1144 #define ACTNLEN 7
1146 if(str)
1147 removing_trailing_white_space(str);
1149 if(!str || !*str || *str == '#')
1150 return(pat);
1152 pat = (PAT_S *)fs_get(sizeof(*pat));
1153 memset((void *)pat, 0, sizeof(*pat));
1155 if((p = srchstr(str, PTRN)) != NULL){
1156 pat->patgrp = (PATGRP_S *)fs_get(sizeof(*pat->patgrp));
1157 memset((void *)pat->patgrp, 0, sizeof(*pat->patgrp));
1158 pat->patgrp->fldr_type = FLDR_DEFL;
1159 pat->patgrp->inabook = IAB_DEFL;
1160 pat->patgrp->cat_lim = -1L;
1162 if((pstr = copy_quoted_string_asis(p+PTRNLEN)) != NULL){
1163 /* move to next slash */
1164 for(q=pstr, backslashed=0; *q; q++){
1165 switch(*q){
1166 case '\\':
1167 backslashed = !backslashed;
1168 break;
1170 case '/':
1171 if(!backslashed){
1172 parse_patgrp_slash(q, pat->patgrp);
1173 if(pat->patgrp->bogus && !pat->raw)
1174 pat->raw = cpystr(str);
1177 /* fall through */
1179 default:
1180 backslashed = 0;
1181 break;
1185 /* we always force a nickname */
1186 if(!pat->patgrp->nick)
1187 pat->patgrp->nick = cpystr("Alternate Role");
1189 fs_give((void **)&pstr);
1193 if((p = srchstr(str, ACTN)) != NULL){
1194 pat->action = (ACTION_S *)fs_get(sizeof(*pat->action));
1195 memset((void *)pat->action, 0, sizeof(*pat->action));
1196 pat->action->startup_rule = IS_NOTSET;
1197 pat->action->repl_type = ROLE_REPL_DEFL;
1198 pat->action->forw_type = ROLE_FORW_DEFL;
1199 pat->action->comp_type = ROLE_COMP_DEFL;
1200 pat->action->nick = cpystr((pat->patgrp && pat->patgrp->nick
1201 && pat->patgrp->nick[0])
1202 ? pat->patgrp->nick : "Alternate Role");
1204 if((astr = copy_quoted_string_asis(p+ACTNLEN)) != NULL){
1205 /* move to next slash */
1206 for(q=astr, backslashed=0; *q; q++){
1207 switch(*q){
1208 case '\\':
1209 backslashed = !backslashed;
1210 break;
1212 case '/':
1213 if(!backslashed){
1214 parse_action_slash(q, pat->action);
1215 if(pat->action->bogus && !pat->raw)
1216 pat->raw = cpystr(str);
1219 /* fall through */
1221 default:
1222 backslashed = 0;
1223 break;
1227 fs_give((void **)&astr);
1229 if(!pat->action->is_a_score)
1230 pat->action->scoreval = 0L;
1232 if(pat->action->is_a_filter)
1233 pat->action->kill = (pat->action->folder
1234 || pat->action->kill == -1) ? 0 : 1;
1235 else{
1236 if(pat->action->folder)
1237 free_pattern(&pat->action->folder);
1240 if(!pat->action->is_a_role){
1241 pat->action->repl_type = ROLE_NOTAROLE_DEFL;
1242 pat->action->forw_type = ROLE_NOTAROLE_DEFL;
1243 pat->action->comp_type = ROLE_NOTAROLE_DEFL;
1244 if(pat->action->from)
1245 mail_free_address(&pat->action->from);
1246 if(pat->action->replyto)
1247 mail_free_address(&pat->action->replyto);
1248 if(pat->action->fcc)
1249 fs_give((void **)&pat->action->fcc);
1250 if(pat->action->litsig)
1251 fs_give((void **)&pat->action->litsig);
1252 if(pat->action->sig)
1253 fs_give((void **)&pat->action->sig);
1254 if(pat->action->template)
1255 fs_give((void **)&pat->action->template);
1256 if(pat->action->cstm)
1257 free_list_array(&pat->action->cstm);
1258 if(pat->action->smtp)
1259 free_list_array(&pat->action->smtp);
1260 if(pat->action->nntp)
1261 free_list_array(&pat->action->nntp);
1262 if(pat->action->inherit_nick)
1263 fs_give((void **)&pat->action->inherit_nick);
1266 if(!pat->action->is_a_incol){
1267 if(pat->action->incol)
1268 free_color_pair(&pat->action->incol);
1271 if(!pat->action->is_a_other){
1272 pat->action->sort_is_set = 0;
1273 pat->action->sortorder = 0;
1274 pat->action->revsort = 0;
1275 pat->action->startup_rule = IS_NOTSET;
1276 if(pat->action->index_format)
1277 fs_give((void **)&pat->action->index_format);
1282 return(pat);
1287 * Fill in one member of patgrp from str.
1289 * The multiple constant strings are lame but it evolved this way from
1290 * previous versions and isn't worth fixing.
1292 void
1293 parse_patgrp_slash(char *str, PATGRP_S *patgrp)
1295 char *p;
1297 if(!patgrp)
1298 panic("NULL patgrp to parse_patgrp_slash");
1299 else if(!(str && *str)){
1300 panic("NULL or empty string to parse_patgrp_slash");
1301 patgrp->bogus = 1;
1303 else if(!strncmp(str, "/NICK=", 6))
1304 patgrp->nick = remove_pat_escapes(str+6);
1305 else if(!strncmp(str, "/COMM=", 6))
1306 patgrp->comment = remove_pat_escapes(str+6);
1307 else if(!strncmp(str, "/TO=", 4) || !strncmp(str, "/!TO=", 5))
1308 patgrp->to = parse_pattern("TO", str, 1);
1309 else if(!strncmp(str, "/CC=", 4) || !strncmp(str, "/!CC=", 5))
1310 patgrp->cc = parse_pattern("CC", str, 1);
1311 else if(!strncmp(str, "/RECIP=", 7) || !strncmp(str, "/!RECIP=", 8))
1312 patgrp->recip = parse_pattern("RECIP", str, 1);
1313 else if(!strncmp(str, "/PARTIC=", 8) || !strncmp(str, "/!PARTIC=", 9))
1314 patgrp->partic = parse_pattern("PARTIC", str, 1);
1315 else if(!strncmp(str, "/FROM=", 6) || !strncmp(str, "/!FROM=", 7))
1316 patgrp->from = parse_pattern("FROM", str, 1);
1317 else if(!strncmp(str, "/SENDER=", 8) || !strncmp(str, "/!SENDER=", 9))
1318 patgrp->sender = parse_pattern("SENDER", str, 1);
1319 else if(!strncmp(str, "/NEWS=", 6) || !strncmp(str, "/!NEWS=", 7))
1320 patgrp->news = parse_pattern("NEWS", str, 1);
1321 else if(!strncmp(str, "/SUBJ=", 6) || !strncmp(str, "/!SUBJ=", 7))
1322 patgrp->subj = parse_pattern("SUBJ", str, 1);
1323 else if(!strncmp(str, "/ALL=", 5) || !strncmp(str, "/!ALL=", 6))
1324 patgrp->alltext = parse_pattern("ALL", str, 1);
1325 else if(!strncmp(str, "/BODY=", 6) || !strncmp(str, "/!BODY=", 7))
1326 patgrp->bodytext = parse_pattern("BODY", str, 1);
1327 else if(!strncmp(str, "/KEY=", 5) || !strncmp(str, "/!KEY=", 6))
1328 patgrp->keyword = parse_pattern("KEY", str, 1);
1329 else if(!strncmp(str, "/CHAR=", 6) || !strncmp(str, "/!CHAR=", 7))
1330 patgrp->charsets = parse_pattern("CHAR", str, 1);
1331 else if(!strncmp(str, "/FOLDER=", 8) || !strncmp(str, "/!FOLDER=", 9))
1332 patgrp->folder = parse_pattern("FOLDER", str, 1);
1333 else if(!strncmp(str, "/ABOOKS=", 8) || !strncmp(str, "/!ABOOKS=", 9))
1334 patgrp->abooks = parse_pattern("ABOOKS", str, 1);
1336 * A problem with arbhdrs is that more than one of them can appear in
1337 * the string. We come back here the second time, but we already took
1338 * care of the whole thing on the first pass. Hence the check for
1339 * arbhdr already set.
1341 else if(!strncmp(str, "/ARB", 4) || !strncmp(str, "/!ARB", 5)
1342 || !strncmp(str, "/EARB", 5) || !strncmp(str, "/!EARB", 6)){
1343 if(!patgrp->arbhdr)
1344 patgrp->arbhdr = parse_arbhdr(str);
1345 /* else do nothing */
1347 else if(!strncmp(str, "/SENTDATE=", 10))
1348 patgrp->age_uses_sentdate = 1;
1349 else if(!strncmp(str, "/SCOREI=", 8)){
1350 if((p = remove_pat_escapes(str+8)) != NULL){
1351 if((patgrp->score = parse_intvl(p)) != NULL)
1352 patgrp->do_score = 1;
1354 fs_give((void **)&p);
1357 else if(!strncmp(str, "/AGE=", 5)){
1358 if((p = remove_pat_escapes(str+5)) != NULL){
1359 if((patgrp->age = parse_intvl(p)) != NULL)
1360 patgrp->do_age = 1;
1362 fs_give((void **)&p);
1365 else if(!strncmp(str, "/SIZE=", 6)){
1366 if((p = remove_pat_escapes(str+6)) != NULL){
1367 if((patgrp->size = parse_intvl(p)) != NULL)
1368 patgrp->do_size = 1;
1370 fs_give((void **)&p);
1373 else if(!strncmp(str, "/CATCMD=", 8)){
1374 if((p = remove_pat_escapes(str+8)) != NULL){
1375 int commas = 0;
1376 char *q;
1378 /* count elements in list */
1379 for(q = p; q && *q; q++)
1380 if(*q == ',')
1381 commas++;
1383 patgrp->category_cmd = parse_list(p, commas+1, PL_REMSURRQUOT,NULL);
1384 fs_give((void **)&p);
1387 else if(!strncmp(str, "/CATVAL=", 8)){
1388 if((p = remove_pat_escapes(str+8)) != NULL){
1389 if((patgrp->cat = parse_intvl(p)) != NULL)
1390 patgrp->do_cat = 1;
1392 fs_give((void **)&p);
1395 else if(!strncmp(str, "/CATLIM=", 8)){
1396 if((p = remove_pat_escapes(str+8)) != NULL){
1397 long i;
1399 i = atol(p);
1400 patgrp->cat_lim = i;
1401 fs_give((void **)&p);
1404 else if(!strncmp(str, "/FLDTYPE=", 9)){
1405 if((p = remove_pat_escapes(str+9)) != NULL){
1406 int i;
1407 NAMEVAL_S *v;
1409 for(i = 0; (v = pat_fldr_types(i)); i++)
1410 if(!strucmp(p, v->shortname)){
1411 patgrp->fldr_type = v->value;
1412 break;
1415 fs_give((void **)&p);
1418 else if(!strncmp(str, "/AFROM=", 7)){
1419 if((p = remove_pat_escapes(str+7)) != NULL){
1420 int i;
1421 NAMEVAL_S *v;
1423 for(i = 0; (v = inabook_fldr_types(i)); i++)
1424 if(!strucmp(p, v->shortname)){
1425 patgrp->inabook |= v->value;
1426 break;
1429 /* to match old semantics */
1430 patgrp->inabook |= IAB_FROM;
1431 patgrp->inabook |= IAB_REPLYTO;
1433 fs_give((void **)&p);
1436 else if(!strncmp(str, "/AFROMA=", 8)){
1437 if((p = remove_pat_escapes(str+8)) != NULL){
1439 /* make sure AFROMA comes after AFROM in config lines */
1440 patgrp->inabook &= ~IAB_ADDR_MASK;
1442 if(strchr(p, 'F'))
1443 patgrp->inabook |= IAB_FROM;
1444 if(strchr(p, 'R'))
1445 patgrp->inabook |= IAB_REPLYTO;
1446 if(strchr(p, 'S'))
1447 patgrp->inabook |= IAB_SENDER;
1448 if(strchr(p, 'T'))
1449 patgrp->inabook |= IAB_TO;
1450 if(strchr(p, 'C'))
1451 patgrp->inabook |= IAB_CC;
1453 fs_give((void **)&p);
1456 else if(!strncmp(str, "/STATN=", 7)){
1457 SET_STATUS(str,"/STATN=",patgrp->stat_new);
1459 else if(!strncmp(str, "/STATR=", 7)){
1460 SET_STATUS(str,"/STATR=",patgrp->stat_rec);
1462 else if(!strncmp(str, "/STATI=", 7)){
1463 SET_STATUS(str,"/STATI=",patgrp->stat_imp);
1465 else if(!strncmp(str, "/STATA=", 7)){
1466 SET_STATUS(str,"/STATA=",patgrp->stat_ans);
1468 else if(!strncmp(str, "/STATD=", 7)){
1469 SET_STATUS(str,"/STATD=",patgrp->stat_del);
1471 else if(!strncmp(str, "/8BITS=", 7)){
1472 SET_STATUS(str,"/8BITS=",patgrp->stat_8bitsubj);
1474 else if(!strncmp(str, "/BOM=", 5)){
1475 SET_STATUS(str,"/BOM=",patgrp->stat_bom);
1477 else if(!strncmp(str, "/BOY=", 5)){
1478 SET_STATUS(str,"/BOY=",patgrp->stat_boy);
1480 else{
1481 char save;
1483 patgrp->bogus = 1;
1485 if((p = strindex(str, '=')) != NULL){
1486 save = *(p+1);
1487 *(p+1) = '\0';
1490 dprint((1,
1491 "parse_patgrp_slash(%.20s): unrecognized in \"%s\"\n",
1492 str ? str : "?",
1493 (patgrp && patgrp->nick) ? patgrp->nick : ""));
1494 q_status_message4(SM_ORDER, 1, 3,
1495 "Warning: unrecognized pattern element \"%.20s\"%.20s%.20s%.20s",
1496 str, patgrp->nick ? " in rule \"" : "",
1497 patgrp->nick ? patgrp->nick : "", patgrp->nick ? "\"" : "");
1499 if(p)
1500 *(p+1) = save;
1506 * Fill in one member of action struct from str.
1508 * The multiple constant strings are lame but it evolved this way from
1509 * previous versions and isn't worth fixing.
1511 void
1512 parse_action_slash(char *str, ACTION_S *action)
1514 char *p;
1515 int stateval, i;
1516 NAMEVAL_S *v;
1518 if(!action)
1519 panic("NULL action to parse_action_slash");
1520 else if(!(str && *str))
1521 panic("NULL or empty string to parse_action_slash");
1522 else if(!strncmp(str, "/ROLE=1", 7))
1523 action->is_a_role = 1;
1524 else if(!strncmp(str, "/OTHER=1", 8))
1525 action->is_a_other = 1;
1526 else if(!strncmp(str, "/ISINCOL=1", 10))
1527 action->is_a_incol = 1;
1528 else if(!strncmp(str, "/ISSRCH=1", 9))
1529 action->is_a_srch = 1;
1531 * This is unfortunate. If a new filter is set to only set
1532 * state bits it will be interpreted by an older pine which
1533 * doesn't have that feature like a filter that is set to Delete.
1534 * So we change the filter indicator to FILTER=2 to disable the
1535 * filter for older versions.
1537 else if(!strncmp(str, "/FILTER=1", 9) || !strncmp(str, "/FILTER=2", 9))
1538 action->is_a_filter = 1;
1539 else if(!strncmp(str, "/ISSCORE=1", 10))
1540 action->is_a_score = 1;
1541 else if(!strncmp(str, "/SCORE=", 7)){
1542 if((p = remove_pat_escapes(str+7)) != NULL){
1543 long i;
1545 i = atol(p);
1546 if(i >= SCORE_MIN && i <= SCORE_MAX)
1547 action->scoreval = i;
1549 fs_give((void **)&p);
1552 else if(!strncmp(str, "/SCOREHDRTOK=", 13))
1553 action->scorevalhdrtok = config_to_hdrtok(str+13);
1554 else if(!strncmp(str, "/FOLDER=", 8))
1555 action->folder = parse_pattern("FOLDER", str, 1);
1556 else if(!strncmp(str, "/KEYSET=", 8))
1557 action->keyword_set = parse_pattern("KEYSET", str, 1);
1558 else if(!strncmp(str, "/KEYCLR=", 8))
1559 action->keyword_clr = parse_pattern("KEYCLR", str, 1);
1560 else if(!strncmp(str, "/NOKILL=", 8))
1561 action->kill = -1;
1562 else if(!strncmp(str, "/NOTDEL=", 8))
1563 action->move_only_if_not_deleted = 1;
1564 else if(!strncmp(str, "/NONTERM=", 9))
1565 action->non_terminating = 1;
1566 else if(!strncmp(str, "/STATI=", 7)){
1567 stateval = ACT_STAT_LEAVE;
1568 SET_MSGSTATE(str,"/STATI=",stateval);
1569 switch(stateval){
1570 case ACT_STAT_LEAVE:
1571 break;
1572 case ACT_STAT_SET:
1573 action->state_setting_bits |= F_FLAG;
1574 break;
1575 case ACT_STAT_CLEAR:
1576 action->state_setting_bits |= F_UNFLAG;
1577 break;
1580 else if(!strncmp(str, "/STATD=", 7)){
1581 stateval = ACT_STAT_LEAVE;
1582 SET_MSGSTATE(str,"/STATD=",stateval);
1583 switch(stateval){
1584 case ACT_STAT_LEAVE:
1585 break;
1586 case ACT_STAT_SET:
1587 action->state_setting_bits |= F_DEL;
1588 break;
1589 case ACT_STAT_CLEAR:
1590 action->state_setting_bits |= F_UNDEL;
1591 break;
1594 else if(!strncmp(str, "/STATA=", 7)){
1595 stateval = ACT_STAT_LEAVE;
1596 SET_MSGSTATE(str,"/STATA=",stateval);
1597 switch(stateval){
1598 case ACT_STAT_LEAVE:
1599 break;
1600 case ACT_STAT_SET:
1601 action->state_setting_bits |= F_ANS;
1602 break;
1603 case ACT_STAT_CLEAR:
1604 action->state_setting_bits |= F_UNANS;
1605 break;
1608 else if(!strncmp(str, "/STATN=", 7)){
1609 stateval = ACT_STAT_LEAVE;
1610 SET_MSGSTATE(str,"/STATN=",stateval);
1611 switch(stateval){
1612 case ACT_STAT_LEAVE:
1613 break;
1614 case ACT_STAT_SET:
1615 action->state_setting_bits |= F_UNSEEN;
1616 break;
1617 case ACT_STAT_CLEAR:
1618 action->state_setting_bits |= F_SEEN;
1619 break;
1622 else if(!strncmp(str, "/RTYPE=", 7)){
1623 /* reply type */
1624 action->repl_type = ROLE_REPL_DEFL;
1625 if((p = remove_pat_escapes(str+7)) != NULL){
1626 for(i = 0; (v = role_repl_types(i)); i++)
1627 if(!strucmp(p, v->shortname)){
1628 action->repl_type = v->value;
1629 break;
1632 fs_give((void **)&p);
1635 else if(!strncmp(str, "/FTYPE=", 7)){
1636 /* forward type */
1637 action->forw_type = ROLE_FORW_DEFL;
1638 if((p = remove_pat_escapes(str+7)) != NULL){
1639 for(i = 0; (v = role_forw_types(i)); i++)
1640 if(!strucmp(p, v->shortname)){
1641 action->forw_type = v->value;
1642 break;
1645 fs_give((void **)&p);
1648 else if(!strncmp(str, "/CTYPE=", 7)){
1649 /* compose type */
1650 action->comp_type = ROLE_COMP_DEFL;
1651 if((p = remove_pat_escapes(str+7)) != NULL){
1652 for(i = 0; (v = role_comp_types(i)); i++)
1653 if(!strucmp(p, v->shortname)){
1654 action->comp_type = v->value;
1655 break;
1658 fs_give((void **)&p);
1661 else if(!strncmp(str, "/FROM=", 6)){
1662 /* get the from */
1663 if((p = remove_pat_escapes(str+6)) != NULL){
1664 rfc822_parse_adrlist(&action->from, p,
1665 ps_global->maildomain);
1666 fs_give((void **)&p);
1669 else if(!strncmp(str, "/REPL=", 6)){
1670 /* get the reply-to */
1671 if((p = remove_pat_escapes(str+6)) != NULL){
1672 rfc822_parse_adrlist(&action->replyto, p,
1673 ps_global->maildomain);
1674 fs_give((void **)&p);
1677 else if(!strncmp(str, "/FCC=", 5))
1678 action->fcc = remove_pat_escapes(str+5);
1679 else if(!strncmp(str, "/LSIG=", 6))
1680 action->litsig = remove_pat_escapes(str+6);
1681 else if(!strncmp(str, "/SIG=", 5))
1682 action->sig = remove_pat_escapes(str+5);
1683 else if(!strncmp(str, "/TEMPLATE=", 10))
1684 action->template = remove_pat_escapes(str+10);
1685 /* get the custom headers */
1686 else if(!strncmp(str, "/CSTM=", 6)){
1687 if((p = remove_pat_escapes(str+6)) != NULL){
1688 int commas = 0;
1689 char *q;
1691 /* count elements in list */
1692 for(q = p; q && *q; q++)
1693 if(*q == ',')
1694 commas++;
1696 action->cstm = parse_list(p, commas+1, 0, NULL);
1697 fs_give((void **)&p);
1700 else if(!strncmp(str, "/SMTP=", 6)){
1701 if((p = remove_pat_escapes(str+6)) != NULL){
1702 int commas = 0;
1703 char *q;
1705 /* count elements in list */
1706 for(q = p; q && *q; q++)
1707 if(*q == ',')
1708 commas++;
1710 action->smtp = parse_list(p, commas+1, PL_REMSURRQUOT, NULL);
1711 fs_give((void **)&p);
1714 else if(!strncmp(str, "/NNTP=", 6)){
1715 if((p = remove_pat_escapes(str+6)) != NULL){
1716 int commas = 0;
1717 char *q;
1719 /* count elements in list */
1720 for(q = p; q && *q; q++)
1721 if(*q == ',')
1722 commas++;
1724 action->nntp = parse_list(p, commas+1, PL_REMSURRQUOT, NULL);
1725 fs_give((void **)&p);
1728 else if(!strncmp(str, "/INICK=", 7))
1729 action->inherit_nick = remove_pat_escapes(str+7);
1730 else if(!strncmp(str, "/INCOL=", 7)){
1731 if((p = remove_pat_escapes(str+7)) != NULL){
1732 char *fg = NULL, *bg = NULL, *z;
1735 * Color should look like
1736 * /FG=white/BG=red
1738 if((z = srchstr(p, "/FG=")) != NULL)
1739 fg = remove_pat_escapes(z+4);
1740 if((z = srchstr(p, "/BG=")) != NULL)
1741 bg = remove_pat_escapes(z+4);
1743 if(fg && *fg && bg && *bg)
1744 action->incol = new_color_pair(fg, bg);
1746 if(fg)
1747 fs_give((void **)&fg);
1748 if(bg)
1749 fs_give((void **)&bg);
1750 fs_give((void **)&p);
1753 /* per-folder sort */
1754 else if(!strncmp(str, "/SORT=", 6)){
1755 if((p = remove_pat_escapes(str+6)) != NULL){
1756 SortOrder def_sort;
1757 int def_sort_rev;
1759 if(decode_sort(p, &def_sort, &def_sort_rev) != -1){
1760 action->sort_is_set = 1;
1761 action->sortorder = def_sort;
1762 action->revsort = (def_sort_rev ? 1 : 0);
1765 fs_give((void **)&p);
1768 /* per-folder index-format */
1769 else if(!strncmp(str, "/IFORM=", 7))
1770 action->index_format = remove_pat_escapes(str+7);
1771 /* per-folder startup-rule */
1772 else if(!strncmp(str, "/START=", 7)){
1773 if((p = remove_pat_escapes(str+7)) != NULL){
1774 for(i = 0; (v = startup_rules(i)); i++)
1775 if(!strucmp(p, S_OR_L(v))){
1776 action->startup_rule = v->value;
1777 break;
1780 fs_give((void **)&p);
1783 else{
1784 char save;
1786 action->bogus = 1;
1788 if((p = strindex(str, '=')) != NULL){
1789 save = *(p+1);
1790 *(p+1) = '\0';
1793 dprint((1,
1794 "parse_action_slash(%.20s): unrecognized in \"%s\"\n",
1795 str ? str : "?",
1796 (action && action->nick) ? action->nick : ""));
1797 q_status_message4(SM_ORDER, 1, 3,
1798 "Warning: unrecognized pattern action \"%.20s\"%.20s%.20s%.20s",
1799 str, action->nick ? " in rule \"" : "",
1800 action->nick ? action->nick : "", action->nick ? "\"" : "");
1802 if(p)
1803 *(p+1) = save;
1809 * Str looks like (min,max) or a comma-separated list of these.
1811 * Parens are optional if unambiguous, whitespace is ignored.
1812 * If min is left out it is -INF. If max is left out it is INF.
1813 * If only one number and no comma number is min and max is INF.
1815 * Returns the INTVL_S list.
1817 INTVL_S *
1818 parse_intvl(char *str)
1820 char *q;
1821 long left, right;
1822 INTVL_S *ret = NULL, **next;
1824 if(!str)
1825 return(ret);
1827 q = str;
1829 for(;;){
1830 left = right = INTVL_UNDEF;
1832 /* skip to first number */
1833 while(isspace((unsigned char) *q) || *q == LPAREN)
1834 q++;
1836 /* min number */
1837 if(*q == COMMA || !struncmp(q, "-INF", 4))
1838 left = - INTVL_INF;
1839 else if(*q == '-' || isdigit((unsigned char) *q))
1840 left = atol(q);
1842 if(left != INTVL_UNDEF){
1843 /* skip to second number */
1844 while(*q && *q != COMMA && *q != RPAREN)
1845 q++;
1846 if(*q == COMMA)
1847 q++;
1848 while(isspace((unsigned char) *q))
1849 q++;
1851 /* max number */
1852 if(*q == '\0' || *q == RPAREN || !struncmp(q, "INF", 3))
1853 right = INTVL_INF;
1854 else if(*q == '-' || isdigit((unsigned char) *q))
1855 right = atol(q);
1858 if(left == INTVL_UNDEF || right == INTVL_UNDEF
1859 || left > right){
1860 if(left != INTVL_UNDEF || right != INTVL_UNDEF || *q){
1861 if(left != INTVL_UNDEF && right != INTVL_UNDEF
1862 && left > right)
1863 q_status_message1(SM_ORDER, 3, 5,
1864 _("Error: Interval \"%s\", min > max"), str);
1865 else
1866 q_status_message1(SM_ORDER, 3, 5,
1867 _("Error: Interval \"%s\": syntax is (min,max)"), str);
1869 if(ret)
1870 free_intvl(&ret);
1872 ret = NULL;
1875 break;
1877 else{
1878 if(!ret){
1879 ret = (INTVL_S *) fs_get(sizeof(*ret));
1880 memset((void *) ret, 0, sizeof(*ret));
1881 ret->imin = left;
1882 ret->imax = right;
1883 next = &ret->next;
1885 else{
1886 *next = (INTVL_S *) fs_get(sizeof(*ret));
1887 memset((void *) *next, 0, sizeof(*ret));
1888 (*next)->imin = left;
1889 (*next)->imax = right;
1890 next = &(*next)->next;
1893 /* skip to next interval in list */
1894 while(*q && *q != COMMA && *q != RPAREN)
1895 q++;
1896 if(*q == RPAREN)
1897 q++;
1898 while(*q && *q != COMMA)
1899 q++;
1900 if(*q == COMMA)
1901 q++;
1905 return(ret);
1910 * Returns string that looks like "(left,right),(left2,right2)".
1911 * Caller is responsible for freeing memory.
1913 char *
1914 stringform_of_intvl(INTVL_S *intvl)
1916 char *res = NULL;
1918 if(intvl && intvl->imin != INTVL_UNDEF && intvl->imax != INTVL_UNDEF
1919 && intvl->imin <= intvl->imax){
1920 char lbuf[20], rbuf[20], buf[45], *p;
1921 INTVL_S *iv;
1922 int count = 0;
1923 size_t reslen;
1925 /* find a max size and allocate it for the result */
1926 for(iv = intvl;
1927 (iv && iv->imin != INTVL_UNDEF && iv->imax != INTVL_UNDEF
1928 && iv->imin <= iv->imax);
1929 iv = iv->next)
1930 count++;
1932 reslen = count * 50 * sizeof(char);
1933 res = (char *) fs_get(reslen+1);
1934 memset((void *) res, 0, reslen+1);
1935 p = res;
1937 for(iv = intvl;
1938 (iv && iv->imin != INTVL_UNDEF && iv->imax != INTVL_UNDEF
1939 && iv->imin <= iv->imax);
1940 iv = iv->next){
1942 if(iv->imin == - INTVL_INF){
1943 strncpy(lbuf, "-INF", sizeof(lbuf));
1944 lbuf[sizeof(lbuf)-1] = '\0';
1946 else
1947 snprintf(lbuf, sizeof(lbuf), "%ld", iv->imin);
1949 if(iv->imax == INTVL_INF){
1950 strncpy(rbuf, "INF", sizeof(rbuf));
1951 rbuf[sizeof(rbuf)-1] = '\0';
1953 else
1954 snprintf(rbuf, sizeof(rbuf), "%ld", iv->imax);
1956 snprintf(buf, sizeof(buf), "%.1s(%.20s,%.20s)", (p == res) ? "" : ",",
1957 lbuf, rbuf);
1959 sstrncpy(&p, buf, reslen+1 -(p-res));
1962 res[reslen] = '\0';
1965 return(res);
1969 char *
1970 hdrtok_to_stringform(HEADER_TOK_S *hdrtok)
1972 char buf[1024], nbuf[10];
1973 char *res = NULL;
1974 char *p, *ptr;
1976 if(hdrtok){
1977 snprintf(nbuf, sizeof(nbuf), "%d", hdrtok->fieldnum);
1978 ptr = buf;
1979 sstrncpy(&ptr, hdrtok->hdrname ? hdrtok->hdrname : "", sizeof(buf)-(ptr-buf));
1980 sstrncpy(&ptr, "(", sizeof(buf)-(ptr-buf));
1981 sstrncpy(&ptr, nbuf, sizeof(buf)-(ptr-buf));
1982 sstrncpy(&ptr, ",\"", sizeof(buf)-(ptr-buf));
1983 p = hdrtok->fieldseps;
1984 while(p && *p){
1985 if((*p == '\"' || *p == '\\') && ptr-buf < sizeof(buf))
1986 *ptr++ = '\\';
1988 if(ptr-buf < sizeof(buf))
1989 *ptr++ = *p++;
1992 sstrncpy(&ptr, "\")", sizeof(buf)-(ptr-buf));
1994 if(ptr-buf < sizeof(buf))
1995 *ptr = '\0';
1996 else
1997 buf[sizeof(buf)-1] = '\0';
1999 res = cpystr(buf);
2002 return(res);
2006 HEADER_TOK_S *
2007 stringform_to_hdrtok(char *str)
2009 char *p, *q, *w, hdrname[200];
2010 HEADER_TOK_S *hdrtok = NULL;
2012 if(str && *str){
2013 p = str;
2014 hdrname[0] = '\0';
2015 w = hdrname;
2017 if(*p == '\"'){ /* quoted name */
2018 p++;
2019 while(w < hdrname + sizeof(hdrname)-1 && *p != '\"'){
2020 if(*p == '\\')
2021 p++;
2023 *w++ = *p++;
2026 *w = '\0';
2027 if(*p == '\"')
2028 p++;
2030 else{
2031 while(w < hdrname + sizeof(hdrname)-1 &&
2032 !(!(*p & 0x80) && isspace((unsigned char)*p)) &&
2033 *p != '(')
2034 *w++ = *p++;
2036 *w = '\0';
2039 if(hdrname[0])
2040 hdrtok = new_hdrtok(hdrname);
2042 if(hdrtok){
2043 if(*p == '('){
2044 p++;
2046 if(*p && isdigit((unsigned char) *p)){
2047 q = p;
2048 while(*p && isdigit((unsigned char) *p))
2049 p++;
2051 hdrtok->fieldnum = atoi(q);
2053 if(*p == ','){
2054 int j;
2056 p++;
2057 /* don't use default */
2058 if(*p && *p != ')' && hdrtok->fieldseps){
2059 hdrtok->fieldseps[0] = '\0';
2060 hdrtok->fieldsepcnt = 0;
2063 j = 0;
2064 if(*p == '\"' && strchr(p+1, '\"')){ /* quoted */
2065 p++;
2066 while(*p && *p != '\"'){
2067 if(hdrtok->fieldseps)
2068 fs_resize((void **) &hdrtok->fieldseps, j+2);
2070 if(*p == '\\' && *(p+1))
2071 p++;
2073 if(hdrtok->fieldseps){
2074 hdrtok->fieldseps[j++] = *p++;
2075 hdrtok->fieldseps[j] = '\0';
2076 hdrtok->fieldsepcnt = j;
2080 else{
2081 while(*p && *p != ')'){
2082 if(hdrtok->fieldseps)
2083 fs_resize((void **) &hdrtok->fieldseps, j+2);
2085 if(*p == '\\' && *(p+1))
2086 p++;
2088 if(hdrtok->fieldseps){
2089 hdrtok->fieldseps[j++] = *p++;
2090 hdrtok->fieldseps[j] = '\0';
2091 hdrtok->fieldsepcnt = j;
2096 else{
2097 q_status_message(SM_ORDER | SM_DING, 3, 3, "Missing 2nd argument should be field number, a non-negative digit");
2100 else{
2101 q_status_message(SM_ORDER | SM_DING, 3, 3, "1st argument should be field number, a non-negative digit");
2104 else{
2105 q_status_message1(SM_ORDER | SM_DING, 3, 3, "Missing left parenthesis in %s", str);
2108 else{
2109 q_status_message1(SM_ORDER | SM_DING, 3, 3, "Missing header name in %s", str);
2113 return(hdrtok);
2117 char *
2118 hdrtok_to_config(HEADER_TOK_S *hdrtok)
2120 char *ptr, buf[1024], nbuf[10], *p1, *p2, *p3;
2121 char *res = NULL;
2123 if(hdrtok){
2124 snprintf(nbuf, sizeof(nbuf), "%d", hdrtok->fieldnum);
2125 memset(buf, 0, sizeof(buf));
2126 ptr = buf;
2127 sstrncpy(&ptr, "/HN=", sizeof(buf)-(ptr-buf));
2128 sstrncpy(&ptr, (p1=add_pat_escapes(hdrtok->hdrname ? hdrtok->hdrname : "")), sizeof(buf)-(ptr-buf));
2129 sstrncpy(&ptr, "/FN=", sizeof(buf)-(ptr-buf));
2130 sstrncpy(&ptr, (p2=add_pat_escapes(nbuf)), sizeof(buf)-(ptr-buf));
2131 sstrncpy(&ptr, "/FS=", sizeof(buf)-(ptr-buf));
2132 sstrncpy(&ptr, (p3=add_pat_escapes(hdrtok->fieldseps ? hdrtok->fieldseps : "")), sizeof(buf)-(ptr-buf));
2134 buf[sizeof(buf)-1] = '\0';
2135 res = add_pat_escapes(buf);
2137 if(p1)
2138 fs_give((void **)&p1);
2140 if(p2)
2141 fs_give((void **)&p2);
2143 if(p3)
2144 fs_give((void **)&p3);
2147 return(res);
2151 HEADER_TOK_S *
2152 config_to_hdrtok(char *str)
2154 HEADER_TOK_S *hdrtok = NULL;
2155 char *p, *q;
2156 int j;
2158 if(str && *str){
2159 if((q = remove_pat_escapes(str)) != NULL){
2160 char *hn = NULL, *fn = NULL, *fs = NULL, *z;
2162 if((z = srchstr(q, "/HN=")) != NULL)
2163 hn = remove_pat_escapes(z+4);
2164 if((z = srchstr(q, "/FN=")) != NULL)
2165 fn = remove_pat_escapes(z+4);
2166 if((z = srchstr(q, "/FS=")) != NULL)
2167 fs = remove_pat_escapes(z+4);
2169 hdrtok = new_hdrtok(hn);
2170 if(fn)
2171 hdrtok->fieldnum = atoi(fn);
2173 if(fs && *fs){
2174 if(hdrtok->fieldseps){
2175 hdrtok->fieldseps[0] = '\0';
2176 hdrtok->fieldsepcnt = 0;
2179 p = fs;
2180 j = 0;
2181 if(*p == '\"' && strchr(p+1, '\"')){
2182 p++;
2183 while(*p && *p != '\"'){
2184 if(hdrtok->fieldseps)
2185 fs_resize((void **) &hdrtok->fieldseps, j+2);
2187 if(*p == '\\' && *(p+1))
2188 p++;
2190 if(hdrtok->fieldseps){
2191 hdrtok->fieldseps[j++] = *p++;
2192 hdrtok->fieldseps[j] = '\0';
2193 hdrtok->fieldsepcnt = j;
2197 else{
2198 while(*p){
2199 if(hdrtok->fieldseps)
2200 fs_resize((void **) &hdrtok->fieldseps, j+2);
2202 if(*p == '\\' && *(p+1))
2203 p++;
2205 if(hdrtok->fieldseps){
2206 hdrtok->fieldseps[j++] = *p++;
2207 hdrtok->fieldseps[j] = '\0';
2208 hdrtok->fieldsepcnt = j;
2214 if(hn)
2215 fs_give((void **)&hn);
2216 if(fn)
2217 fs_give((void **)&fn);
2218 if(fs)
2219 fs_give((void **)&fs);
2221 fs_give((void **)&q);
2225 return(hdrtok);
2230 * Args -- flags - SCOREUSE_INVALID Mark scores_in_use invalid so that we'll
2231 * recalculate if we want to use it again.
2232 * - SCOREUSE_GET Return whether scores are being used or not.
2234 * Returns -- 0 - Scores not being used at all.
2235 * >0 - Scores are used. The return value consists of flag values
2236 * OR'd together. Possible values are:
2238 * SCOREUSE_INCOLS - scores needed for index line colors
2239 * SCOREUSE_ROLES - scores needed for roles
2240 * SCOREUSE_FILTERS - scores needed for filters
2241 * SCOREUSE_OTHER - scores needed for other stuff
2242 * SCOREUSE_INDEX - scores needed for index drawing
2244 * SCOREUSE_STATEDEP - scores depend on message state
2247 scores_are_used(int flags)
2249 static int scores_in_use = -1;
2250 long type1, type2;
2251 int scores_are_defined, scores_are_used_somewhere = 0;
2252 PAT_STATE pstate1, pstate2;
2254 if(flags & SCOREUSE_INVALID) /* mark invalid so we recalculate next time */
2255 scores_in_use = -1;
2256 else if(scores_in_use == -1){
2259 * Check the patterns to see if scores are potentially
2260 * being used.
2261 * The first_pattern() in the if checks whether there are any
2262 * non-zero scorevals. The loop checks whether any patterns
2263 * use those non-zero scorevals.
2265 type1 = ROLE_SCORE;
2266 type2 = (ROLE_REPLY | ROLE_FORWARD | ROLE_COMPOSE |
2267 ROLE_INCOL | ROLE_DO_FILTER);
2268 scores_are_defined = nonempty_patterns(type1, &pstate1)
2269 && first_pattern(&pstate1);
2270 if(scores_are_defined)
2271 scores_are_used_somewhere =
2272 ((nonempty_patterns(type2, &pstate2) && first_pattern(&pstate2))
2273 || ps_global->a_format_contains_score
2274 || mn_get_sort(ps_global->msgmap) == SortScore);
2276 if(scores_are_used_somewhere){
2277 PAT_S *pat;
2280 * Careful. nonempty_patterns() may call close_pattern()
2281 * which will set scores_in_use to -1! So we have to be
2282 * sure to reset it after we call nonempty_patterns().
2284 scores_in_use = 0;
2285 if(ps_global->a_format_contains_score
2286 || mn_get_sort(ps_global->msgmap) == SortScore)
2287 scores_in_use |= SCOREUSE_INDEX;
2289 if(nonempty_patterns(type2, &pstate2))
2290 for(pat = first_pattern(&pstate2);
2291 pat;
2292 pat = next_pattern(&pstate2))
2293 if(pat->patgrp && !pat->patgrp->bogus && pat->patgrp->do_score){
2294 if(pat->action && pat->action->is_a_incol)
2295 scores_in_use |= SCOREUSE_INCOLS;
2296 if(pat->action && pat->action->is_a_role)
2297 scores_in_use |= SCOREUSE_ROLES;
2298 if(pat->action && pat->action->is_a_filter)
2299 scores_in_use |= SCOREUSE_FILTERS;
2300 if(pat->action && pat->action->is_a_other)
2301 scores_in_use |= SCOREUSE_OTHER;
2305 * Note whether scores depend on message state or not.
2307 if(scores_in_use)
2308 for(pat = first_pattern(&pstate1);
2309 pat;
2310 pat = next_pattern(&pstate1))
2311 if(patgrp_depends_on_active_state(pat->patgrp)){
2312 scores_in_use |= SCOREUSE_STATEDEP;
2313 break;
2317 else
2318 scores_in_use = 0;
2321 return((scores_in_use == -1) ? 0 : scores_in_use);
2326 patgrp_depends_on_state(PATGRP_S *patgrp)
2328 return(patgrp && (patgrp_depends_on_active_state(patgrp)
2329 || patgrp->stat_rec != PAT_STAT_EITHER));
2334 * Recent doesn't count for this function because it doesn't change while
2335 * the mailbox is open.
2338 patgrp_depends_on_active_state(PATGRP_S *patgrp)
2340 return(patgrp && !patgrp->bogus
2341 && (patgrp->stat_new != PAT_STAT_EITHER ||
2342 patgrp->stat_del != PAT_STAT_EITHER ||
2343 patgrp->stat_imp != PAT_STAT_EITHER ||
2344 patgrp->stat_ans != PAT_STAT_EITHER ||
2345 patgrp->keyword));
2350 * Look for label in str and return a pointer to parsed string.
2351 * Actually, we look for "label=" or "!label=", the second means NOT.
2352 * Converts from string from patterns file which looks like
2353 * /NEWS=comp.mail.,comp.mail.pine/TO=...
2354 * This is the string that came from pattern="string" with the pattern=
2355 * and outer quotes removed.
2356 * This converts the string to a PATTERN_S list and returns
2357 * an allocated copy.
2359 PATTERN_S *
2360 parse_pattern(char *label, char *str, int hex_to_backslashed)
2362 char copy[50]; /* local copy of label */
2363 char copynot[50]; /* local copy of label, NOT'ed */
2364 char *q, *labeled_str;
2365 PATTERN_S *head = NULL;
2367 if(!label || !str)
2368 return(NULL);
2370 q = copy;
2371 sstrncpy(&q, "/", sizeof(copy));
2372 sstrncpy(&q, label, sizeof(copy) - (q-copy));
2373 sstrncpy(&q, "=", sizeof(copy) - (q-copy));
2374 copy[sizeof(copy)-1] = '\0';
2375 q = copynot;
2376 sstrncpy(&q, "/!", sizeof(copynot));
2377 sstrncpy(&q, label, sizeof(copynot) - (q-copynot));
2378 sstrncpy(&q, "=", sizeof(copynot) - (q-copynot));
2379 copynot[sizeof(copynot)-1] = '\0';
2381 if(hex_to_backslashed){
2382 if((q = srchstr(str, copy)) != NULL){
2383 head = config_to_pattern(q+strlen(copy));
2385 else if((q = srchstr(str, copynot)) != NULL){
2386 head = config_to_pattern(q+strlen(copynot));
2387 head->not = 1;
2390 else{
2391 if((q = srchstr(str, copy)) != NULL){
2392 if((labeled_str =
2393 remove_backslash_escapes(q+strlen(copy))) != NULL){
2394 head = string_to_pattern(labeled_str);
2395 fs_give((void **)&labeled_str);
2398 else if((q = srchstr(str, copynot)) != NULL){
2399 if((labeled_str =
2400 remove_backslash_escapes(q+strlen(copynot))) != NULL){
2401 head = string_to_pattern(labeled_str);
2402 head->not = 1;
2403 fs_give((void **)&labeled_str);
2408 return(head);
2413 * Look for /ARB's in str and return a pointer to parsed ARBHDR_S.
2414 * Actually, we look for /!ARB and /!EARB as well. Those mean NOT.
2415 * Converts from string from patterns file which looks like
2416 * /ARB<fieldname1>=pattern/.../ARB<fieldname2>=pattern...
2417 * This is the string that came from pattern="string" with the pattern=
2418 * and outer quotes removed.
2419 * This converts the string to a ARBHDR_S list and returns
2420 * an allocated copy.
2422 ARBHDR_S *
2423 parse_arbhdr(char *str)
2425 char *q, *s, *equals, *noesc;
2426 int not, empty, skip;
2427 ARBHDR_S *ahdr = NULL, *a, *aa;
2428 PATTERN_S *p = NULL;
2430 if(!str)
2431 return(NULL);
2433 aa = NULL;
2434 for(s = str; (q = next_arb(s)); s = q+1){
2435 not = (q[1] == '!') ? 1 : 0;
2436 empty = (q[not+1] == 'E') ? 1 : 0;
2437 skip = 4 + not + empty;
2438 if((noesc = remove_pat_escapes(q+skip)) != NULL){
2439 if(*noesc != '=' && (equals = strindex(noesc, '=')) != NULL){
2440 a = (ARBHDR_S *)fs_get(sizeof(*a));
2441 memset((void *)a, 0, sizeof(*a));
2442 *equals = '\0';
2443 a->isemptyval = empty;
2444 a->field = cpystr(noesc);
2445 if(empty)
2446 a->p = string_to_pattern("");
2447 else if(*(equals+1) &&
2448 (p = string_to_pattern(equals+1)) != NULL)
2449 a->p = p;
2451 if(not && a->p)
2452 a->p->not = 1;
2454 /* keep them in the same order */
2455 if(aa){
2456 aa->next = a;
2457 aa = aa->next;
2459 else{
2460 ahdr = a;
2461 aa = ahdr;
2465 fs_give((void **)&noesc);
2469 return(ahdr);
2473 char *
2474 next_arb(char *start)
2476 char *q1, *q2, *q3, *q4, *p;
2478 q1 = srchstr(start, "/ARB");
2479 q2 = srchstr(start, "/!ARB");
2480 q3 = srchstr(start, "/EARB");
2481 q4 = srchstr(start, "/!EARB");
2483 p = q1;
2484 if(!p || (q2 && q2 < p))
2485 p = q2;
2486 if(!p || (q3 && q3 < p))
2487 p = q3;
2488 if(!p || (q4 && q4 < p))
2489 p = q4;
2491 return(p);
2496 * Converts a string to a PATTERN_S list and returns an
2497 * allocated copy. The source string looks like
2498 * string1,string2,...
2499 * Commas and backslashes may be backslash-escaped in the original string
2500 * in order to include actual commas and backslashes in the pattern.
2501 * So \, is an actual comma and , is the separator character.
2503 PATTERN_S *
2504 string_to_pattern(char *str)
2506 char *q, *s, *workspace;
2507 PATTERN_S *p, *head = NULL, **nextp;
2509 if(!str)
2510 return(head);
2513 * We want an empty string to cause an empty substring in the pattern
2514 * instead of returning a NULL pattern. That can be used as a way to
2515 * match any header. For example, if all the patterns but the news
2516 * pattern were null and the news pattern was a substring of "" then
2517 * we use that to match any message with a newsgroups header.
2519 if(!*str){
2520 head = (PATTERN_S *)fs_get(sizeof(*p));
2521 memset((void *)head, 0, sizeof(*head));
2522 head->substring = cpystr("");
2524 else{
2525 nextp = &head;
2526 workspace = (char *)fs_get((strlen(str)+1) * sizeof(char));
2527 s = workspace;
2528 *s = '\0';
2529 q = str;
2530 do {
2531 switch(*q){
2532 case COMMA:
2533 case '\0':
2534 *s = '\0';
2535 removing_leading_and_trailing_white_space(workspace);
2536 p = (PATTERN_S *)fs_get(sizeof(*p));
2537 memset((void *)p, 0, sizeof(*p));
2538 p->substring = cpystr(workspace);
2540 convert_possibly_encoded_str_to_utf8(&p->substring);
2542 *nextp = p;
2543 nextp = &p->next;
2544 s = workspace;
2545 *s = '\0';
2546 break;
2548 case BSLASH:
2549 if(*(q+1) == COMMA || *(q+1) == BSLASH)
2550 *s++ = *(++q);
2551 else
2552 *s++ = *q;
2554 break;
2556 default:
2557 *s++ = *q;
2558 break;
2560 } while(*q++);
2562 fs_give((void **)&workspace);
2565 return(head);
2570 * Converts a PATTERN_S list to a string.
2571 * The resulting string is allocated here and looks like
2572 * string1,string2,...
2573 * Commas and backslashes in the original pattern
2574 * end up backslash-escaped in the string.
2576 char *
2577 pattern_to_string(PATTERN_S *pattern)
2579 PATTERN_S *p;
2580 char *result = NULL, *q, *s;
2581 size_t n;
2583 if(!pattern)
2584 return(result);
2586 /* how much space is needed? */
2587 n = 0;
2588 for(p = pattern; p; p = p->next){
2589 n += (p == pattern) ? 0 : 1;
2590 for(s = p->substring; s && *s; s++){
2591 if(*s == COMMA || *s == BSLASH)
2592 n++;
2594 n++;
2598 q = result = (char *)fs_get(++n);
2599 for(p = pattern; p; p = p->next){
2600 if(p != pattern)
2601 *q++ = COMMA;
2603 for(s = p->substring; s && *s; s++){
2604 if(*s == COMMA || *s == BSLASH)
2605 *q++ = '\\';
2607 *q++ = *s;
2611 *q = '\0';
2613 return(result);
2618 * Do the escaping necessary to take a string for a pattern into a comma-
2619 * separated string with escapes suitable for the config file.
2620 * Returns an allocated copy of that string.
2621 * In particular
2622 * , -> \, -> \x2C
2623 * \ -> \\ -> \x5C
2624 * " -> \"
2625 * / -> \/
2627 char *
2628 pattern_to_config(PATTERN_S *pat)
2630 char *s, *res = NULL;
2632 s = pattern_to_string(pat);
2633 if(s){
2634 res = add_pat_escapes(s);
2635 fs_give((void **) &s);
2638 return(res);
2642 * Opposite of pattern_to_config.
2644 PATTERN_S *
2645 config_to_pattern(char *str)
2647 char *s;
2648 PATTERN_S *pat = NULL;
2650 s = remove_pat_escapes(str);
2651 if(s){
2652 pat = string_to_pattern(s);
2653 fs_give((void **) &s);
2656 return(pat);
2661 * Converts an array of strings to a PATTERN_S list and returns an
2662 * allocated copy.
2663 * The list strings may not contain commas directly, because the UI turns
2664 * those into separate list members. Instead, the user types \, and
2665 * that backslash comma is converted to a comma here.
2666 * It is a bit odd. Backslash itself is not escaped. A backslash which is
2667 * not followed by a comma is a literal backslash, a backslash followed by
2668 * a comma is a comma.
2670 PATTERN_S *
2671 editlist_to_pattern(char **list)
2673 PATTERN_S *head = NULL;
2675 if(!(list && *list))
2676 return(head);
2679 * We want an empty string to cause an empty substring in the pattern
2680 * instead of returning a NULL pattern. That can be used as a way to
2681 * match any header. For example, if all the patterns but the news
2682 * pattern were null and the news pattern was a substring of "" then
2683 * we use that to match any message with a newsgroups header.
2685 if(!list[0][0]){
2686 head = (PATTERN_S *) fs_get(sizeof(*head));
2687 memset((void *) head, 0, sizeof(*head));
2688 head->substring = cpystr("");
2690 else{
2691 char *str, *s, *q, *workspace = NULL;
2692 size_t l = 0;
2693 PATTERN_S *p, **nextp;
2694 int i;
2696 nextp = &head;
2697 for(i = 0; (str = list[i]); i++){
2698 if(str[0]){
2699 if(!workspace){
2700 l = strlen(str) + 1;
2701 workspace = (char *) fs_get(l * sizeof(char));
2703 else if(strlen(str) + 1 > l){
2704 l = strlen(str) + 1;
2705 fs_give((void **) &workspace);
2706 workspace = (char *) fs_get(l * sizeof(char));
2709 s = workspace;
2710 *s = '\0';
2711 q = str;
2712 do {
2713 switch(*q){
2714 case '\0':
2715 *s = '\0';
2716 removing_leading_and_trailing_white_space(workspace);
2717 p = (PATTERN_S *) fs_get(sizeof(*p));
2718 memset((void *) p, 0, sizeof(*p));
2719 p->substring = cpystr(workspace);
2720 *nextp = p;
2721 nextp = &p->next;
2722 s = workspace;
2723 *s = '\0';
2724 break;
2726 case BSLASH:
2727 if(*(q+1) == COMMA)
2728 *s++ = *(++q);
2729 else
2730 *s++ = *q;
2732 break;
2734 default:
2735 *s++ = *q;
2736 break;
2738 } while(*q++);
2743 return(head);
2748 * Converts a PATTERN_S to an array of strings and returns an allocated copy.
2749 * Commas are converted to backslash-comma, because the text_tool UI uses
2750 * commas to separate items.
2751 * It is a bit odd. Backslash itself is not escaped. A backslash which is
2752 * not followed by a comma is a literal backslash, a backslash followed by
2753 * a comma is a comma.
2755 char **
2756 pattern_to_editlist(PATTERN_S *pat)
2758 int cnt, i;
2759 PATTERN_S *p;
2760 char **list = NULL;
2762 if(!pat)
2763 return(list);
2765 /* how many in list? */
2766 for(cnt = 0, p = pat; p; p = p->next)
2767 cnt++;
2769 list = (char **) fs_get((cnt + 1) * sizeof(*list));
2770 memset((void *) list, 0, (cnt + 1) * sizeof(*list));
2772 for(i = 0, p = pat; p; p = p->next, i++)
2773 list[i] = add_comma_escapes(p->substring);
2775 return(list);
2779 PATGRP_S *
2780 nick_to_patgrp(char *nick, int rflags)
2782 PAT_S *pat;
2783 PAT_STATE pstate;
2784 PATGRP_S *patgrp = NULL;
2786 if(!(nick && *nick
2787 && nonempty_patterns(rflags, &pstate) && first_pattern(&pstate)))
2788 return(patgrp);
2790 for(pat = first_pattern(&pstate);
2791 !patgrp && pat;
2792 pat = next_pattern(&pstate))
2793 if(pat->patgrp && pat->patgrp->nick && !strcmp(pat->patgrp->nick, nick))
2794 patgrp = copy_patgrp(pat->patgrp);
2796 return(patgrp);
2801 * Must be called with a pstate, we don't check for it.
2802 * It respects the cur_rflag_num in pstate. That is, it doesn't start over
2803 * at i=1, it starts at cur_rflag_num.
2805 PAT_S *
2806 first_any_pattern(PAT_STATE *pstate)
2808 PAT_LINE_S *patline = NULL;
2809 int i;
2810 long local_rflag;
2813 * The rest of pstate should be set before coming here.
2814 * In particular, the rflags should be set by a call to nonempty_patterns
2815 * or any_patterns, and cur_rflag_num should be set.
2817 pstate->patlinecurrent = NULL;
2818 pstate->patcurrent = NULL;
2821 * The order of these is important. It is the same as the order
2822 * used for next_any_pattern and opposite of the order used by
2823 * last and prev. For next_any's benefit, we allow cur_rflag_num to
2824 * start us out past the first set.
2826 for(i = pstate->cur_rflag_num; i <= PATTERN_N; i++){
2828 local_rflag = 0L;
2830 switch(i){
2831 case 1:
2832 local_rflag = ROLE_DO_SRCH & CANONICAL_RFLAGS(pstate->rflags);
2833 break;
2835 case 2:
2836 local_rflag = ROLE_DO_INCOLS & CANONICAL_RFLAGS(pstate->rflags);
2837 break;
2839 case 3:
2840 local_rflag = ROLE_DO_ROLES & CANONICAL_RFLAGS(pstate->rflags);
2841 break;
2843 case 4:
2844 local_rflag = ROLE_DO_FILTER & CANONICAL_RFLAGS(pstate->rflags);
2845 break;
2847 case 5:
2848 local_rflag = ROLE_DO_SCORES & CANONICAL_RFLAGS(pstate->rflags);
2849 break;
2851 case 6:
2852 local_rflag = ROLE_DO_OTHER & CANONICAL_RFLAGS(pstate->rflags);
2853 break;
2855 case 7:
2856 local_rflag = ROLE_OLD_FILT & CANONICAL_RFLAGS(pstate->rflags);
2857 break;
2859 case 8:
2860 local_rflag = ROLE_OLD_SCORE & CANONICAL_RFLAGS(pstate->rflags);
2861 break;
2863 case PATTERN_N:
2864 local_rflag = ROLE_OLD_PAT & CANONICAL_RFLAGS(pstate->rflags);
2865 break;
2868 if(local_rflag){
2869 SET_PATTYPE(local_rflag | (pstate->rflags & PAT_USE_MASK));
2871 if(*cur_pat_h){
2872 /* Find first patline with a pat */
2873 for(patline = (*cur_pat_h)->patlinehead;
2874 patline && !patline->first;
2875 patline = patline->next)
2879 if(patline){
2880 pstate->cur_rflag_num = i;
2881 pstate->patlinecurrent = patline;
2882 pstate->patcurrent = patline->first;
2886 if(pstate->patcurrent)
2887 break;
2890 return(pstate->patcurrent);
2895 * Return first pattern of the specified types. These types were set by a
2896 * previous call to any_patterns or nonempty_patterns.
2898 * Args -- pstate pattern state. This is set here and passed back for
2899 * use by next_pattern. Must be non-null.
2900 * It must have been initialized previously by a call to
2901 * nonempty_patterns or any_patterns.
2903 PAT_S *
2904 first_pattern(PAT_STATE *pstate)
2906 PAT_S *pat;
2907 long rflags;
2909 pstate->cur_rflag_num = 1;
2911 rflags = pstate->rflags;
2913 for(pat = first_any_pattern(pstate);
2914 pat && !((pat->action &&
2915 ((rflags & ROLE_DO_ROLES && pat->action->is_a_role) ||
2916 (rflags & (ROLE_DO_INCOLS|ROLE_INCOL) &&
2917 pat->action->is_a_incol) ||
2918 (rflags & ROLE_DO_OTHER && pat->action->is_a_other) ||
2919 (rflags & ROLE_DO_SRCH && pat->action->is_a_srch) ||
2920 (rflags & ROLE_DO_SCORES && pat->action->is_a_score) ||
2921 (rflags & ROLE_SCORE && (pat->action->scoreval
2922 || pat->action->scorevalhdrtok)) ||
2923 (rflags & ROLE_DO_FILTER && pat->action->is_a_filter) ||
2924 (rflags & ROLE_REPLY &&
2925 (pat->action->repl_type == ROLE_REPL_YES ||
2926 pat->action->repl_type == ROLE_REPL_NOCONF)) ||
2927 (rflags & ROLE_FORWARD &&
2928 (pat->action->forw_type == ROLE_FORW_YES ||
2929 pat->action->forw_type == ROLE_FORW_NOCONF)) ||
2930 (rflags & ROLE_COMPOSE &&
2931 (pat->action->comp_type == ROLE_COMP_YES ||
2932 pat->action->comp_type == ROLE_COMP_NOCONF)) ||
2933 (rflags & ROLE_OLD_FILT) ||
2934 (rflags & ROLE_OLD_SCORE) ||
2935 (rflags & ROLE_OLD_PAT)))
2937 pat->inherit);
2938 pat = next_any_pattern(pstate))
2941 return(pat);
2946 * Just like first_any_pattern.
2948 PAT_S *
2949 last_any_pattern(PAT_STATE *pstate)
2951 PAT_LINE_S *patline = NULL;
2952 int i;
2953 long local_rflag;
2956 * The rest of pstate should be set before coming here.
2957 * In particular, the rflags should be set by a call to nonempty_patterns
2958 * or any_patterns, and cur_rflag_num should be set.
2960 pstate->patlinecurrent = NULL;
2961 pstate->patcurrent = NULL;
2963 for(i = pstate->cur_rflag_num; i >= 1; i--){
2965 local_rflag = 0L;
2967 switch(i){
2968 case 1:
2969 local_rflag = ROLE_DO_SRCH & CANONICAL_RFLAGS(pstate->rflags);
2970 break;
2972 case 2:
2973 local_rflag = ROLE_DO_INCOLS & CANONICAL_RFLAGS(pstate->rflags);
2974 break;
2976 case 3:
2977 local_rflag = ROLE_DO_ROLES & CANONICAL_RFLAGS(pstate->rflags);
2978 break;
2980 case 4:
2981 local_rflag = ROLE_DO_FILTER & CANONICAL_RFLAGS(pstate->rflags);
2982 break;
2984 case 5:
2985 local_rflag = ROLE_DO_SCORES & CANONICAL_RFLAGS(pstate->rflags);
2986 break;
2988 case 6:
2989 local_rflag = ROLE_DO_OTHER & CANONICAL_RFLAGS(pstate->rflags);
2990 break;
2992 case 7:
2993 local_rflag = ROLE_OLD_FILT & CANONICAL_RFLAGS(pstate->rflags);
2994 break;
2996 case 8:
2997 local_rflag = ROLE_OLD_SCORE & CANONICAL_RFLAGS(pstate->rflags);
2998 break;
3000 case PATTERN_N:
3001 local_rflag = ROLE_OLD_PAT & CANONICAL_RFLAGS(pstate->rflags);
3002 break;
3005 if(local_rflag){
3006 SET_PATTYPE(local_rflag | (pstate->rflags & PAT_USE_MASK));
3008 pstate->patlinecurrent = NULL;
3009 pstate->patcurrent = NULL;
3011 if(*cur_pat_h){
3012 /* Find last patline with a pat */
3013 for(patline = (*cur_pat_h)->patlinehead;
3014 patline;
3015 patline = patline->next)
3016 if(patline->last)
3017 pstate->patlinecurrent = patline;
3019 if(pstate->patlinecurrent)
3020 pstate->patcurrent = pstate->patlinecurrent->last;
3023 if(pstate->patcurrent)
3024 pstate->cur_rflag_num = i;
3026 if(pstate->patcurrent)
3027 break;
3031 return(pstate->patcurrent);
3036 * Return last pattern of the specified types. These types were set by a
3037 * previous call to any_patterns or nonempty_patterns.
3039 * Args -- pstate pattern state. This is set here and passed back for
3040 * use by prev_pattern. Must be non-null.
3041 * It must have been initialized previously by a call to
3042 * nonempty_patterns or any_patterns.
3044 PAT_S *
3045 last_pattern(PAT_STATE *pstate)
3047 PAT_S *pat;
3048 long rflags;
3050 pstate->cur_rflag_num = PATTERN_N;
3052 rflags = pstate->rflags;
3054 for(pat = last_any_pattern(pstate);
3055 pat && !((pat->action &&
3056 ((rflags & ROLE_DO_ROLES && pat->action->is_a_role) ||
3057 (rflags & (ROLE_DO_INCOLS|ROLE_INCOL) &&
3058 pat->action->is_a_incol) ||
3059 (rflags & ROLE_DO_OTHER && pat->action->is_a_other) ||
3060 (rflags & ROLE_DO_SRCH && pat->action->is_a_srch) ||
3061 (rflags & ROLE_DO_SCORES && pat->action->is_a_score) ||
3062 (rflags & ROLE_SCORE && (pat->action->scoreval
3063 || pat->action->scorevalhdrtok)) ||
3064 (rflags & ROLE_DO_FILTER && pat->action->is_a_filter) ||
3065 (rflags & ROLE_REPLY &&
3066 (pat->action->repl_type == ROLE_REPL_YES ||
3067 pat->action->repl_type == ROLE_REPL_NOCONF)) ||
3068 (rflags & ROLE_FORWARD &&
3069 (pat->action->forw_type == ROLE_FORW_YES ||
3070 pat->action->forw_type == ROLE_FORW_NOCONF)) ||
3071 (rflags & ROLE_COMPOSE &&
3072 (pat->action->comp_type == ROLE_COMP_YES ||
3073 pat->action->comp_type == ROLE_COMP_NOCONF)) ||
3074 (rflags & ROLE_OLD_FILT) ||
3075 (rflags & ROLE_OLD_SCORE) ||
3076 (rflags & ROLE_OLD_PAT)))
3078 pat->inherit);
3079 pat = prev_any_pattern(pstate))
3082 return(pat);
3087 * This assumes that pstate is valid.
3089 PAT_S *
3090 next_any_pattern(PAT_STATE *pstate)
3092 PAT_LINE_S *patline;
3094 if(pstate->patlinecurrent){
3095 if(pstate->patcurrent && pstate->patcurrent->next)
3096 pstate->patcurrent = pstate->patcurrent->next;
3097 else{
3098 /* Find next patline with a pat */
3099 for(patline = pstate->patlinecurrent->next;
3100 patline && !patline->first;
3101 patline = patline->next)
3104 if(patline){
3105 pstate->patlinecurrent = patline;
3106 pstate->patcurrent = patline->first;
3108 else{
3109 pstate->patlinecurrent = NULL;
3110 pstate->patcurrent = NULL;
3115 /* we've reached the last, try the next rflag_num (the next pattern type) */
3116 if(!pstate->patcurrent){
3117 pstate->cur_rflag_num++;
3118 pstate->patcurrent = first_any_pattern(pstate);
3121 return(pstate->patcurrent);
3126 * Return next pattern of the specified types. These types were set by a
3127 * previous call to any_patterns or nonempty_patterns.
3129 * Args -- pstate pattern state. This is set by first_pattern or last_pattern.
3131 PAT_S *
3132 next_pattern(PAT_STATE *pstate)
3134 PAT_S *pat;
3135 long rflags;
3137 rflags = pstate->rflags;
3139 for(pat = next_any_pattern(pstate);
3140 pat && !((pat->action &&
3141 ((rflags & ROLE_DO_ROLES && pat->action->is_a_role) ||
3142 (rflags & (ROLE_DO_INCOLS|ROLE_INCOL) &&
3143 pat->action->is_a_incol) ||
3144 (rflags & ROLE_DO_OTHER && pat->action->is_a_other) ||
3145 (rflags & ROLE_DO_SRCH && pat->action->is_a_srch) ||
3146 (rflags & ROLE_DO_SCORES && pat->action->is_a_score) ||
3147 (rflags & ROLE_SCORE && (pat->action->scoreval
3148 || pat->action->scorevalhdrtok)) ||
3149 (rflags & ROLE_DO_FILTER && pat->action->is_a_filter) ||
3150 (rflags & ROLE_REPLY &&
3151 (pat->action->repl_type == ROLE_REPL_YES ||
3152 pat->action->repl_type == ROLE_REPL_NOCONF)) ||
3153 (rflags & ROLE_FORWARD &&
3154 (pat->action->forw_type == ROLE_FORW_YES ||
3155 pat->action->forw_type == ROLE_FORW_NOCONF)) ||
3156 (rflags & ROLE_COMPOSE &&
3157 (pat->action->comp_type == ROLE_COMP_YES ||
3158 pat->action->comp_type == ROLE_COMP_NOCONF)) ||
3159 (rflags & ROLE_OLD_FILT) ||
3160 (rflags & ROLE_OLD_SCORE) ||
3161 (rflags & ROLE_OLD_PAT)))
3163 pat->inherit);
3164 pat = next_any_pattern(pstate))
3167 return(pat);
3172 * This assumes that pstate is valid.
3174 PAT_S *
3175 prev_any_pattern(PAT_STATE *pstate)
3177 PAT_LINE_S *patline;
3179 if(pstate->patlinecurrent){
3180 if(pstate->patcurrent && pstate->patcurrent->prev)
3181 pstate->patcurrent = pstate->patcurrent->prev;
3182 else{
3183 /* Find prev patline with a pat */
3184 for(patline = pstate->patlinecurrent->prev;
3185 patline && !patline->last;
3186 patline = patline->prev)
3189 if(patline){
3190 pstate->patlinecurrent = patline;
3191 pstate->patcurrent = patline->last;
3193 else{
3194 pstate->patlinecurrent = NULL;
3195 pstate->patcurrent = NULL;
3200 if(!pstate->patcurrent){
3201 pstate->cur_rflag_num--;
3202 pstate->patcurrent = last_any_pattern(pstate);
3205 return(pstate->patcurrent);
3210 * Return prev pattern of the specified types. These types were set by a
3211 * previous call to any_patterns or nonempty_patterns.
3213 * Args -- pstate pattern state. This is set by first_pattern or last_pattern.
3215 PAT_S *
3216 prev_pattern(PAT_STATE *pstate)
3218 PAT_S *pat;
3219 long rflags;
3221 rflags = pstate->rflags;
3223 for(pat = prev_any_pattern(pstate);
3224 pat && !((pat->action &&
3225 ((rflags & ROLE_DO_ROLES && pat->action->is_a_role) ||
3226 (rflags & (ROLE_DO_INCOLS|ROLE_INCOL) &&
3227 pat->action->is_a_incol) ||
3228 (rflags & ROLE_DO_OTHER && pat->action->is_a_other) ||
3229 (rflags & ROLE_DO_SRCH && pat->action->is_a_srch) ||
3230 (rflags & ROLE_DO_SCORES && pat->action->is_a_score) ||
3231 (rflags & ROLE_SCORE && (pat->action->scoreval
3232 || pat->action->scorevalhdrtok)) ||
3233 (rflags & ROLE_DO_FILTER && pat->action->is_a_filter) ||
3234 (rflags & ROLE_REPLY &&
3235 (pat->action->repl_type == ROLE_REPL_YES ||
3236 pat->action->repl_type == ROLE_REPL_NOCONF)) ||
3237 (rflags & ROLE_FORWARD &&
3238 (pat->action->forw_type == ROLE_FORW_YES ||
3239 pat->action->forw_type == ROLE_FORW_NOCONF)) ||
3240 (rflags & ROLE_COMPOSE &&
3241 (pat->action->comp_type == ROLE_COMP_YES ||
3242 pat->action->comp_type == ROLE_COMP_NOCONF)) ||
3243 (rflags & ROLE_OLD_FILT) ||
3244 (rflags & ROLE_OLD_SCORE) ||
3245 (rflags & ROLE_OLD_PAT)))
3247 pat->inherit);
3248 pat = prev_any_pattern(pstate))
3251 return(pat);
3256 * Rflags may be more than one pattern type OR'd together.
3259 write_patterns(long int rflags)
3261 int canon_rflags;
3262 int err = 0;
3264 dprint((7, "write_patterns(0x%x)\n", rflags));
3266 canon_rflags = CANONICAL_RFLAGS(rflags);
3268 if(canon_rflags & ROLE_DO_INCOLS)
3269 err += sub_write_patterns(ROLE_DO_INCOLS | (rflags & PAT_USE_MASK));
3270 if(!err && canon_rflags & ROLE_DO_OTHER)
3271 err += sub_write_patterns(ROLE_DO_OTHER | (rflags & PAT_USE_MASK));
3272 if(!err && canon_rflags & ROLE_DO_FILTER)
3273 err += sub_write_patterns(ROLE_DO_FILTER | (rflags & PAT_USE_MASK));
3274 if(!err && canon_rflags & ROLE_DO_SCORES)
3275 err += sub_write_patterns(ROLE_DO_SCORES | (rflags & PAT_USE_MASK));
3276 if(!err && canon_rflags & ROLE_DO_ROLES)
3277 err += sub_write_patterns(ROLE_DO_ROLES | (rflags & PAT_USE_MASK));
3278 if(!err && canon_rflags & ROLE_DO_SRCH)
3279 err += sub_write_patterns(ROLE_DO_SRCH | (rflags & PAT_USE_MASK));
3281 if(!err && !(rflags & PAT_USE_CHANGED))
3282 write_pinerc(ps_global, (rflags & PAT_USE_MAIN) ? Main : Post, WRP_NONE);
3284 return(err);
3289 sub_write_patterns(long int rflags)
3291 int err = 0, lineno = 0;
3292 char **lvalue = NULL;
3293 PAT_LINE_S *patline;
3295 SET_PATTYPE(rflags);
3297 if(!(*cur_pat_h)){
3298 q_status_message(SM_ORDER | SM_DING, 3, 4,
3299 "Unknown error saving patterns");
3300 return(-1);
3303 if((*cur_pat_h)->dirtypinerc){
3304 /* Count how many lines will be in patterns variable */
3305 for(patline = (*cur_pat_h)->patlinehead;
3306 patline;
3307 patline = patline->next)
3308 lineno++;
3310 lvalue = (char **)fs_get((lineno+1)*sizeof(char *));
3311 memset(lvalue, 0, (lineno+1) * sizeof(char *));
3314 for(patline = (*cur_pat_h)->patlinehead, lineno = 0;
3315 !err && patline;
3316 patline = patline->next, lineno++){
3317 if(patline->type == File)
3318 err = write_pattern_file((*cur_pat_h)->dirtypinerc
3319 ? &lvalue[lineno] : NULL, patline);
3320 else if(patline->type == Literal && (*cur_pat_h)->dirtypinerc)
3321 err = write_pattern_lit(&lvalue[lineno], patline);
3322 else if(patline->type == Inherit)
3323 err = write_pattern_inherit((*cur_pat_h)->dirtypinerc
3324 ? &lvalue[lineno] : NULL, patline);
3327 if((*cur_pat_h)->dirtypinerc){
3328 if(err)
3329 free_list_array(&lvalue);
3330 else{
3331 char ***alval;
3332 struct variable *var;
3334 if(rflags & ROLE_DO_ROLES)
3335 var = &ps_global->vars[V_PAT_ROLES];
3336 else if(rflags & ROLE_DO_OTHER)
3337 var = &ps_global->vars[V_PAT_OTHER];
3338 else if(rflags & ROLE_DO_FILTER)
3339 var = &ps_global->vars[V_PAT_FILTS];
3340 else if(rflags & ROLE_DO_SCORES)
3341 var = &ps_global->vars[V_PAT_SCORES];
3342 else if(rflags & ROLE_DO_INCOLS)
3343 var = &ps_global->vars[V_PAT_INCOLS];
3344 else if(rflags & ROLE_DO_SRCH)
3345 var = &ps_global->vars[V_PAT_SRCH];
3347 alval = (rflags & PAT_USE_CHANGED) ? &(var->changed_val.l)
3348 : ALVAL(var, (rflags & PAT_USE_MAIN) ? Main : Post);
3349 if(*alval)
3350 free_list_array(alval);
3352 if(rflags & PAT_USE_CHANGED) var->is_changed_val = 1;
3354 *alval = lvalue;
3356 if(!(rflags & PAT_USE_CHANGED))
3357 set_current_val(var, TRUE, TRUE);
3361 if(!err)
3362 (*cur_pat_h)->dirtypinerc = 0;
3364 return(err);
3369 * Write pattern lines into a file.
3371 * Args lvalue -- Pointer to char * to fill in variable value
3372 * patline --
3374 * Returns 0 -- all is ok, lvalue has been filled in, file has been written
3375 * else -- error, lvalue untouched, file not written
3378 write_pattern_file(char **lvalue, PAT_LINE_S *patline)
3380 char *p, *tfile;
3381 int fd = -1, err = 0;
3382 FILE *fp_new;
3383 PAT_S *pat;
3385 dprint((7, "write_pattern_file(%s)\n",
3386 (patline && patline->filepath) ? patline->filepath : "?"));
3388 if(lvalue){
3389 size_t l;
3391 l = strlen(patline->filename) + 5;
3392 p = (char *) fs_get((l+1) * sizeof(char));
3393 strncpy(p, "FILE:", l+1);
3394 p[l] = '\0';
3395 strncat(p, patline->filename, l+1-1-strlen(p));
3396 p[l] = '\0';
3397 *lvalue = p;
3400 if(patline->readonly || !patline->dirty) /* doesn't need writing */
3401 return(err);
3403 /* Get a tempfile to write the patterns into */
3404 if(((tfile = tempfile_in_same_dir(patline->filepath, ".pt", NULL)) == NULL)
3405 || ((fd = our_open(tfile, O_TRUNC|O_WRONLY|O_CREAT|O_BINARY, 0600)) < 0)
3406 || ((fp_new = fdopen(fd, "w")) == NULL)){
3407 q_status_message1(SM_ORDER | SM_DING, 3, 4,
3408 "Can't write in directory containing file \"%.200s\"",
3409 patline->filepath);
3410 if(tfile){
3411 our_unlink(tfile);
3412 fs_give((void **)&tfile);
3415 if(fd >= 0)
3416 close(fd);
3418 return(-1);
3421 dprint((9, "write_pattern_file: writing into %s\n",
3422 tfile ? tfile : "?"));
3424 if(fprintf(fp_new, "%s %s\n", PATTERN_MAGIC, PATTERN_FILE_VERS) == EOF)
3425 err--;
3427 for(pat = patline->first; !err && pat; pat = pat->next){
3428 if((p = data_for_patline(pat)) != NULL){
3429 if(fprintf(fp_new, "%s\n", p) == EOF)
3430 err--;
3432 fs_give((void **)&p);
3436 if(err || fclose(fp_new) == EOF){
3437 if(err)
3438 (void)fclose(fp_new);
3440 err--;
3441 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3442 "I/O error: \"%.200s\": %.200s",
3443 tfile, error_description(errno));
3446 if(!err && rename_file(tfile, patline->filepath) < 0){
3447 err--;
3448 q_status_message3(SM_ORDER | SM_DING, 3, 4,
3449 _("Error renaming \"%s\" to \"%s\": %s"),
3450 tfile, patline->filepath, error_description(errno));
3451 dprint((2,
3452 "write_pattern_file: Error renaming (%s,%s): %s\n",
3453 tfile ? tfile : "?",
3454 (patline && patline->filepath) ? patline->filepath : "?",
3455 error_description(errno)));
3458 if(tfile){
3459 our_unlink(tfile);
3460 fs_give((void **)&tfile);
3463 if(!err)
3464 patline->dirty = 0;
3466 return(err);
3471 * Write literal pattern lines into lvalue (pinerc variable).
3473 * Args lvalue -- Pointer to char * to fill in variable value
3474 * patline --
3476 * Returns 0 -- all is ok, lvalue has been filled in, file has been written
3477 * else -- error, lvalue untouched, file not written
3480 write_pattern_lit(char **lvalue, PAT_LINE_S *patline)
3482 char *p = NULL;
3483 int err = 0;
3484 PAT_S *pat;
3486 pat = patline ? patline->first : NULL;
3488 if(pat && lvalue && (p = data_for_patline(pat)) != NULL){
3489 size_t l;
3491 l = strlen(p) + 4;
3492 *lvalue = (char *) fs_get((l+1) * sizeof(char));
3493 strncpy(*lvalue, "LIT:", l+1);
3494 (*lvalue)[l] = '\0';
3495 strncat(*lvalue, p, l+1-1-strlen(*lvalue));
3496 (*lvalue)[l] = '\0';
3498 else{
3499 q_status_message(SM_ORDER | SM_DING, 3, 4,
3500 _("Unknown error saving pattern variable"));
3501 err--;
3504 if(p)
3505 fs_give((void **)&p);
3507 return(err);
3512 write_pattern_inherit(char **lvalue, PAT_LINE_S *patline)
3514 int err = 0;
3516 if(patline && patline->type == Inherit && lvalue)
3517 *lvalue = cpystr(INHERIT);
3518 else
3519 err--;
3521 return(err);
3526 char *
3527 data_for_patline(PAT_S *pat)
3529 char *p = NULL, *q, *to_pat = NULL,
3530 *news_pat = NULL, *from_pat = NULL,
3531 *sender_pat = NULL, *cc_pat = NULL, *subj_pat = NULL,
3532 *arb_pat = NULL, *fldr_type_pat = NULL, *fldr_pat = NULL,
3533 *afrom_type_pat = NULL, *abooks_pat = NULL,
3534 *alltext_pat = NULL, *scorei_pat = NULL, *recip_pat = NULL,
3535 *keyword_pat = NULL, *charset_pat = NULL,
3536 *bodytext_pat = NULL, *age_pat = NULL, *sentdate = NULL,
3537 *size_pat = NULL,
3538 *category_cmd = NULL, *category_pat = NULL,
3539 *category_lim = NULL,
3540 *partic_pat = NULL, *stat_new_val = NULL,
3541 *stat_rec_val = NULL,
3542 *stat_imp_val = NULL, *stat_del_val = NULL,
3543 *stat_ans_val = NULL, *stat_8bit_val = NULL,
3544 *stat_bom_val = NULL, *stat_boy_val = NULL,
3545 *from_act = NULL, *replyto_act = NULL, *fcc_act = NULL,
3546 *sig_act = NULL, *nick = NULL, *templ_act = NULL,
3547 *litsig_act = NULL, *cstm_act = NULL, *smtp_act = NULL,
3548 *nntp_act = NULL, *comment = NULL,
3549 *repl_val = NULL, *forw_val = NULL, *comp_val = NULL,
3550 *incol_act = NULL, *inherit_nick = NULL,
3551 *score_act = NULL, *hdrtok_act = NULL,
3552 *sort_act = NULL, *iform_act = NULL, *start_act = NULL,
3553 *folder_act = NULL, *filt_ifnotdel = NULL,
3554 *filt_nokill = NULL, *filt_del_val = NULL,
3555 *filt_imp_val = NULL, *filt_ans_val = NULL,
3556 *filt_new_val = NULL, *filt_nonterm = NULL,
3557 *keyword_set = NULL, *keyword_clr = NULL;
3558 int to_not = 0, news_not = 0, from_not = 0,
3559 sender_not = 0, cc_not = 0, subj_not = 0,
3560 partic_not = 0, recip_not = 0, alltext_not, bodytext_not,
3561 keyword_not = 0, charset_not = 0;
3562 size_t l;
3563 ACTION_S *action = NULL;
3564 NAMEVAL_S *f;
3566 if(!pat)
3567 return(p);
3569 if((pat->patgrp && pat->patgrp->bogus)
3570 || (pat->action && pat->action->bogus)){
3571 if(pat->raw)
3572 p = cpystr(pat->raw);
3574 return(p);
3577 if(pat->patgrp){
3578 if(pat->patgrp->nick)
3579 if((nick = add_pat_escapes(pat->patgrp->nick)) && !*nick)
3580 fs_give((void **) &nick);
3582 if(pat->patgrp->comment)
3583 if((comment = add_pat_escapes(pat->patgrp->comment)) && !*comment)
3584 fs_give((void **) &comment);
3586 if(pat->patgrp->to){
3587 to_pat = pattern_to_config(pat->patgrp->to);
3588 to_not = pat->patgrp->to->not;
3591 if(pat->patgrp->from){
3592 from_pat = pattern_to_config(pat->patgrp->from);
3593 from_not = pat->patgrp->from->not;
3596 if(pat->patgrp->sender){
3597 sender_pat = pattern_to_config(pat->patgrp->sender);
3598 sender_not = pat->patgrp->sender->not;
3601 if(pat->patgrp->cc){
3602 cc_pat = pattern_to_config(pat->patgrp->cc);
3603 cc_not = pat->patgrp->cc->not;
3606 if(pat->patgrp->recip){
3607 recip_pat = pattern_to_config(pat->patgrp->recip);
3608 recip_not = pat->patgrp->recip->not;
3611 if(pat->patgrp->partic){
3612 partic_pat = pattern_to_config(pat->patgrp->partic);
3613 partic_not = pat->patgrp->partic->not;
3616 if(pat->patgrp->news){
3617 news_pat = pattern_to_config(pat->patgrp->news);
3618 news_not = pat->patgrp->news->not;
3621 if(pat->patgrp->subj){
3622 subj_pat = pattern_to_config(pat->patgrp->subj);
3623 subj_not = pat->patgrp->subj->not;
3626 if(pat->patgrp->alltext){
3627 alltext_pat = pattern_to_config(pat->patgrp->alltext);
3628 alltext_not = pat->patgrp->alltext->not;
3631 if(pat->patgrp->bodytext){
3632 bodytext_pat = pattern_to_config(pat->patgrp->bodytext);
3633 bodytext_not = pat->patgrp->bodytext->not;
3636 if(pat->patgrp->keyword){
3637 keyword_pat = pattern_to_config(pat->patgrp->keyword);
3638 keyword_not = pat->patgrp->keyword->not;
3641 if(pat->patgrp->charsets){
3642 charset_pat = pattern_to_config(pat->patgrp->charsets);
3643 charset_not = pat->patgrp->charsets->not;
3646 if(pat->patgrp->arbhdr){
3647 ARBHDR_S *a;
3648 char *p1 = NULL, *p2 = NULL, *p3 = NULL, *p4 = NULL;
3649 int len = 0;
3651 /* This is brute force dumb, but who cares? */
3652 for(a = pat->patgrp->arbhdr; a; a = a->next){
3653 if(a->field && a->field[0]){
3654 p1 = pattern_to_string(a->p);
3655 p1 = p1 ? p1 : cpystr("");
3656 l = strlen(a->field)+strlen(p1)+1;
3657 p2 = (char *) fs_get((l+1) * sizeof(char));
3658 snprintf(p2, l+1, "%s=%s", a->field, p1);
3659 p3 = add_pat_escapes(p2);
3660 l = strlen(p3)+6;
3661 p4 = (char *) fs_get((l+1) * sizeof(char));
3662 snprintf(p4, l+1, "/%s%sARB%s",
3663 (a->p && a->p->not) ? "!" : "",
3664 a->isemptyval ? "E" : "", p3);
3665 len += strlen(p4);
3667 if(p1)
3668 fs_give((void **)&p1);
3669 if(p2)
3670 fs_give((void **)&p2);
3671 if(p3)
3672 fs_give((void **)&p3);
3673 if(p4)
3674 fs_give((void **)&p4);
3678 p = arb_pat = (char *)fs_get((len + 1) * sizeof(char));
3680 for(a = pat->patgrp->arbhdr; a; a = a->next){
3681 if(a->field && a->field[0]){
3682 p1 = pattern_to_string(a->p);
3683 p1 = p1 ? p1 : cpystr("");
3684 l = strlen(a->field)+strlen(p1)+1;
3685 p2 = (char *) fs_get((l+1) * sizeof(char));
3686 snprintf(p2, l+1, "%s=%s", a->field, p1);
3687 p3 = add_pat_escapes(p2);
3688 l = strlen(p3)+6;
3689 p4 = (char *) fs_get((l+1) * sizeof(char));
3690 snprintf(p4, l+1, "/%s%sARB%s",
3691 (a->p && a->p->not) ? "!" : "",
3692 a->isemptyval ? "E" : "", p3);
3693 sstrncpy(&p, p4, len+1-(p-arb_pat));
3695 if(p1)
3696 fs_give((void **)&p1);
3697 if(p2)
3698 fs_give((void **)&p2);
3699 if(p3)
3700 fs_give((void **)&p3);
3701 if(p4)
3702 fs_give((void **)&p4);
3706 arb_pat[len] = '\0';
3709 if(pat->patgrp->age_uses_sentdate)
3710 sentdate = cpystr("/SENTDATE=1");
3712 if(pat->patgrp->do_score){
3713 p = stringform_of_intvl(pat->patgrp->score);
3714 if(p){
3715 scorei_pat = add_pat_escapes(p);
3716 fs_give((void **)&p);
3720 if(pat->patgrp->do_age){
3721 p = stringform_of_intvl(pat->patgrp->age);
3722 if(p){
3723 age_pat = add_pat_escapes(p);
3724 fs_give((void **)&p);
3728 if(pat->patgrp->do_size){
3729 p = stringform_of_intvl(pat->patgrp->size);
3730 if(p){
3731 size_pat = add_pat_escapes(p);
3732 fs_give((void **)&p);
3736 if(pat->patgrp->category_cmd && pat->patgrp->category_cmd[0]){
3737 size_t sz;
3738 char **l, *q;
3740 /* concatenate into string with commas first */
3741 sz = 0;
3742 for(l = pat->patgrp->category_cmd; l[0] && l[0][0]; l++)
3743 sz += strlen(l[0]) + 1;
3745 if(sz){
3746 char *p;
3747 int first_one = 1;
3749 q = (char *)fs_get(sz);
3750 memset(q, 0, sz);
3751 p = q;
3752 for(l = pat->patgrp->category_cmd; l[0] && l[0][0]; l++){
3753 if(!first_one)
3754 sstrncpy(&p, ",", sz-(p-q));
3756 first_one = 0;
3757 sstrncpy(&p, l[0], sz-(p-q));
3760 q[sz-1] = '\0';
3762 category_cmd = add_pat_escapes(q);
3763 fs_give((void **)&q);
3767 if(pat->patgrp->do_cat){
3768 p = stringform_of_intvl(pat->patgrp->cat);
3769 if(p){
3770 category_pat = add_pat_escapes(p);
3771 fs_give((void **)&p);
3775 if(pat->patgrp->cat_lim != -1L){
3776 category_lim = (char *) fs_get(20 * sizeof(char));
3777 snprintf(category_lim, 20, "%ld", pat->patgrp->cat_lim);
3780 if((f = pat_fldr_types(pat->patgrp->fldr_type)) != NULL)
3781 fldr_type_pat = f->shortname;
3783 if(pat->patgrp->folder)
3784 fldr_pat = pattern_to_config(pat->patgrp->folder);
3786 if((f = inabook_fldr_types(pat->patgrp->inabook)) != NULL
3787 && f->value != IAB_DEFL)
3788 afrom_type_pat = f->shortname;
3790 if(pat->patgrp->abooks)
3791 abooks_pat = pattern_to_config(pat->patgrp->abooks);
3793 if(pat->patgrp->stat_new != PAT_STAT_EITHER &&
3794 (f = role_status_types(pat->patgrp->stat_new)) != NULL)
3795 stat_new_val = f->shortname;
3797 if(pat->patgrp->stat_rec != PAT_STAT_EITHER &&
3798 (f = role_status_types(pat->patgrp->stat_rec)) != NULL)
3799 stat_rec_val = f->shortname;
3801 if(pat->patgrp->stat_del != PAT_STAT_EITHER &&
3802 (f = role_status_types(pat->patgrp->stat_del)) != NULL)
3803 stat_del_val = f->shortname;
3805 if(pat->patgrp->stat_ans != PAT_STAT_EITHER &&
3806 (f = role_status_types(pat->patgrp->stat_ans)) != NULL)
3807 stat_ans_val = f->shortname;
3809 if(pat->patgrp->stat_imp != PAT_STAT_EITHER &&
3810 (f = role_status_types(pat->patgrp->stat_imp)) != NULL)
3811 stat_imp_val = f->shortname;
3813 if(pat->patgrp->stat_8bitsubj != PAT_STAT_EITHER &&
3814 (f = role_status_types(pat->patgrp->stat_8bitsubj)) != NULL)
3815 stat_8bit_val = f->shortname;
3817 if(pat->patgrp->stat_bom != PAT_STAT_EITHER &&
3818 (f = role_status_types(pat->patgrp->stat_bom)) != NULL)
3819 stat_bom_val = f->shortname;
3821 if(pat->patgrp->stat_boy != PAT_STAT_EITHER &&
3822 (f = role_status_types(pat->patgrp->stat_boy)) != NULL)
3823 stat_boy_val = f->shortname;
3826 if(pat->action){
3827 action = pat->action;
3829 if(action->is_a_score){
3830 if(action->scoreval != 0L &&
3831 action->scoreval >= SCORE_MIN && action->scoreval <= SCORE_MAX){
3832 score_act = (char *) fs_get(5 * sizeof(char));
3833 snprintf(score_act, 5, "%ld", pat->action->scoreval);
3836 if(action->scorevalhdrtok)
3837 hdrtok_act = hdrtok_to_config(action->scorevalhdrtok);
3840 if(action->is_a_role){
3841 if(action->inherit_nick)
3842 inherit_nick = add_pat_escapes(action->inherit_nick);
3843 if(action->fcc)
3844 fcc_act = add_pat_escapes(action->fcc);
3845 if(action->litsig)
3846 litsig_act = add_pat_escapes(action->litsig);
3847 if(action->sig)
3848 sig_act = add_pat_escapes(action->sig);
3849 if(action->template)
3850 templ_act = add_pat_escapes(action->template);
3852 if(action->cstm){
3853 size_t sz;
3854 char **l, *q;
3856 /* concatenate into string with commas first */
3857 sz = 0;
3858 for(l = action->cstm; l[0] && l[0][0]; l++)
3859 sz += strlen(l[0]) + 1;
3861 if(sz){
3862 char *p;
3863 int first_one = 1;
3865 q = (char *)fs_get(sz);
3866 memset(q, 0, sz);
3867 p = q;
3868 for(l = action->cstm; l[0] && l[0][0]; l++){
3869 if((!struncmp(l[0], "from", 4) &&
3870 (l[0][4] == ':' || l[0][4] == '\0')) ||
3871 (!struncmp(l[0], "reply-to", 8) &&
3872 (l[0][8] == ':' || l[0][8] == '\0')))
3873 continue;
3875 if(!first_one)
3876 sstrncpy(&p, ",", sz-(p-q));
3878 first_one = 0;
3879 sstrncpy(&p, l[0], sz-(p-q));
3882 q[sz-1] = '\0';
3884 cstm_act = add_pat_escapes(q);
3885 fs_give((void **)&q);
3889 if(action->smtp){
3890 size_t sz;
3891 char **l, *q;
3893 /* concatenate into string with commas first */
3894 sz = 0;
3895 for(l = action->smtp; l[0] && l[0][0]; l++)
3896 sz += strlen(l[0]) + 1;
3898 if(sz){
3899 char *p;
3900 int first_one = 1;
3902 q = (char *)fs_get(sz);
3903 memset(q, 0, sz);
3904 p = q;
3905 for(l = action->smtp; l[0] && l[0][0]; l++){
3906 if(!first_one)
3907 sstrncpy(&p, ",", sz-(p-q));
3909 first_one = 0;
3910 sstrncpy(&p, l[0], sz-(p-q));
3913 q[sz-1] = '\0';
3915 smtp_act = add_pat_escapes(q);
3916 fs_give((void **)&q);
3920 if(action->nntp){
3921 size_t sz;
3922 char **l, *q;
3924 /* concatenate into string with commas first */
3925 sz = 0;
3926 for(l = action->nntp; l[0] && l[0][0]; l++)
3927 sz += strlen(l[0]) + 1;
3929 if(sz){
3930 char *p;
3931 int first_one = 1;
3933 q = (char *)fs_get(sz);
3934 memset(q, 0, sz);
3935 p = q;
3936 for(l = action->nntp; l[0] && l[0][0]; l++){
3937 if(!first_one)
3938 sstrncpy(&p, ",", sz-(p-q));
3940 first_one = 0;
3941 sstrncpy(&p, l[0], sz-(p-q));
3944 q[sz-1] = '\0';
3946 nntp_act = add_pat_escapes(q);
3947 fs_give((void **)&q);
3951 if((f = role_repl_types(action->repl_type)) != NULL)
3952 repl_val = f->shortname;
3954 if((f = role_forw_types(action->forw_type)) != NULL)
3955 forw_val = f->shortname;
3957 if((f = role_comp_types(action->comp_type)) != NULL)
3958 comp_val = f->shortname;
3961 if(action->is_a_incol && action->incol){
3962 char *ptr, buf[256], *p1, *p2;
3964 ptr = buf;
3965 memset(buf, 0, sizeof(buf));
3966 sstrncpy(&ptr, "/FG=", sizeof(buf)-(ptr-buf));
3967 sstrncpy(&ptr, (p1=add_pat_escapes(action->incol->fg)), sizeof(buf)-(ptr-buf));
3968 sstrncpy(&ptr, "/BG=", sizeof(buf)-(ptr-buf));
3969 sstrncpy(&ptr, (p2=add_pat_escapes(action->incol->bg)), sizeof(buf)-(ptr-buf));
3970 buf[sizeof(buf)-1] = '\0';
3971 /* the colors will be doubly escaped */
3972 incol_act = add_pat_escapes(buf);
3973 if(p1)
3974 fs_give((void **)&p1);
3976 if(p2)
3977 fs_give((void **)&p2);
3980 if(action->is_a_other){
3981 char buf[256];
3983 if(action->sort_is_set){
3984 snprintf(buf, sizeof(buf), "%.50s%.50s",
3985 sort_name(action->sortorder),
3986 action->revsort ? "/Reverse" : "");
3987 sort_act = add_pat_escapes(buf);
3990 if(action->index_format)
3991 iform_act = add_pat_escapes(action->index_format);
3993 if(action->startup_rule != IS_NOTSET &&
3994 (f = startup_rules(action->startup_rule)) != NULL)
3995 start_act = S_OR_L(f);
3998 if(action->is_a_role && action->from){
3999 char *bufp;
4000 size_t len;
4002 len = est_size(action->from);
4003 bufp = (char *) fs_get(len * sizeof(char));
4004 p = addr_string_mult(action->from, bufp, len);
4005 if(p){
4006 from_act = add_pat_escapes(p);
4007 fs_give((void **)&p);
4011 if(action->is_a_role && action->replyto){
4012 char *bufp;
4013 size_t len;
4015 len = est_size(action->replyto);
4016 bufp = (char *) fs_get(len * sizeof(char));
4017 p = addr_string_mult(action->replyto, bufp, len);
4018 if(p){
4019 replyto_act = add_pat_escapes(p);
4020 fs_give((void **)&p);
4024 if(action->is_a_filter){
4025 if(action->folder){
4026 if((folder_act = pattern_to_config(action->folder)) != NULL){
4027 if(action->move_only_if_not_deleted)
4028 filt_ifnotdel = cpystr("/NOTDEL=1");
4032 if(action->keyword_set)
4033 keyword_set = pattern_to_config(action->keyword_set);
4035 if(action->keyword_clr)
4036 keyword_clr = pattern_to_config(action->keyword_clr);
4038 if(!action->kill)
4039 filt_nokill = cpystr("/NOKILL=1");
4041 if(action->non_terminating)
4042 filt_nonterm = cpystr("/NONTERM=1");
4044 if(action->state_setting_bits){
4045 char buf[256];
4046 int dval, nval, ival, aval;
4048 buf[0] = '\0';
4049 p = buf;
4051 convert_statebits_to_vals(action->state_setting_bits,
4052 &dval, &aval, &ival, &nval);
4053 if(dval != ACT_STAT_LEAVE &&
4054 (f = msg_state_types(dval)) != NULL)
4055 filt_del_val = f->shortname;
4057 if(aval != ACT_STAT_LEAVE &&
4058 (f = msg_state_types(aval)) != NULL)
4059 filt_ans_val = f->shortname;
4061 if(ival != ACT_STAT_LEAVE &&
4062 (f = msg_state_types(ival)) != NULL)
4063 filt_imp_val = f->shortname;
4065 if(nval != ACT_STAT_LEAVE &&
4066 (f = msg_state_types(nval)) != NULL)
4067 filt_new_val = f->shortname;
4072 l = strlen(nick ? nick : "Alternate Role") +
4073 strlen(comment ? comment : "") +
4074 strlen(to_pat ? to_pat : "") +
4075 strlen(from_pat ? from_pat : "") +
4076 strlen(sender_pat ? sender_pat : "") +
4077 strlen(cc_pat ? cc_pat : "") +
4078 strlen(recip_pat ? recip_pat : "") +
4079 strlen(partic_pat ? partic_pat : "") +
4080 strlen(news_pat ? news_pat : "") +
4081 strlen(subj_pat ? subj_pat : "") +
4082 strlen(alltext_pat ? alltext_pat : "") +
4083 strlen(bodytext_pat ? bodytext_pat : "") +
4084 strlen(arb_pat ? arb_pat : "") +
4085 strlen(scorei_pat ? scorei_pat : "") +
4086 strlen(keyword_pat ? keyword_pat : "") +
4087 strlen(charset_pat ? charset_pat : "") +
4088 strlen(age_pat ? age_pat : "") +
4089 strlen(size_pat ? size_pat : "") +
4090 strlen(category_cmd ? category_cmd : "") +
4091 strlen(category_pat ? category_pat : "") +
4092 strlen(category_lim ? category_lim : "") +
4093 strlen(fldr_pat ? fldr_pat : "") +
4094 strlen(abooks_pat ? abooks_pat : "") +
4095 strlen(sentdate ? sentdate : "") +
4096 strlen(inherit_nick ? inherit_nick : "") +
4097 strlen(score_act ? score_act : "") +
4098 strlen(hdrtok_act ? hdrtok_act : "") +
4099 strlen(from_act ? from_act : "") +
4100 strlen(replyto_act ? replyto_act : "") +
4101 strlen(fcc_act ? fcc_act : "") +
4102 strlen(litsig_act ? litsig_act : "") +
4103 strlen(cstm_act ? cstm_act : "") +
4104 strlen(smtp_act ? smtp_act : "") +
4105 strlen(nntp_act ? nntp_act : "") +
4106 strlen(sig_act ? sig_act : "") +
4107 strlen(incol_act ? incol_act : "") +
4108 strlen(sort_act ? sort_act : "") +
4109 strlen(iform_act ? iform_act : "") +
4110 strlen(start_act ? start_act : "") +
4111 strlen(filt_ifnotdel ? filt_ifnotdel : "") +
4112 strlen(filt_nokill ? filt_nokill : "") +
4113 strlen(filt_nonterm ? filt_nonterm : "") +
4114 (folder_act ? (strlen(folder_act) + 8) : 0) +
4115 strlen(keyword_set ? keyword_set : "") +
4116 strlen(keyword_clr ? keyword_clr : "") +
4117 strlen(templ_act ? templ_act : "") + 540;
4119 * The +540 above is larger than needed but not everything is accounted
4120 * for with the strlens.
4122 p = (char *) fs_get(l * sizeof(char));
4124 q = p;
4125 sstrncpy(&q, "pattern=\"/NICK=", l-(q-p));
4127 if(nick){
4128 sstrncpy(&q, nick, l-(q-p));
4129 fs_give((void **) &nick);
4131 else
4132 sstrncpy(&q, "Alternate Role", l-(q-p));
4134 if(comment){
4135 sstrncpy(&q, "/", l-(q-p));
4136 sstrncpy(&q, "COMM=", l-(q-p));
4137 sstrncpy(&q, comment, l-(q-p));
4138 fs_give((void **) &comment);
4141 if(to_pat){
4142 sstrncpy(&q, "/", l-(q-p));
4143 if(to_not)
4144 sstrncpy(&q, "!", l-(q-p));
4146 sstrncpy(&q, "TO=", l-(q-p));
4147 sstrncpy(&q, to_pat, l-(q-p));
4148 fs_give((void **) &to_pat);
4151 if(from_pat){
4152 sstrncpy(&q, "/", l-(q-p));
4153 if(from_not)
4154 sstrncpy(&q, "!", l-(q-p));
4156 sstrncpy(&q, "FROM=", l-(q-p));
4157 sstrncpy(&q, from_pat, l-(q-p));
4158 fs_give((void **) &from_pat);
4161 if(sender_pat){
4162 sstrncpy(&q, "/", l-(q-p));
4163 if(sender_not)
4164 sstrncpy(&q, "!", l-(q-p));
4166 sstrncpy(&q, "SENDER=", l-(q-p));
4167 sstrncpy(&q, sender_pat, l-(q-p));
4168 fs_give((void **) &sender_pat);
4171 if(cc_pat){
4172 sstrncpy(&q,"/", l-(q-p));
4173 if(cc_not)
4174 sstrncpy(&q, "!", l-(q-p));
4176 sstrncpy(&q,"CC=", l-(q-p));
4177 sstrncpy(&q, cc_pat, l-(q-p));
4178 fs_give((void **) &cc_pat);
4181 if(recip_pat){
4182 sstrncpy(&q, "/", l-(q-p));
4183 if(recip_not)
4184 sstrncpy(&q, "!", l-(q-p));
4186 sstrncpy(&q, "RECIP=", l-(q-p));
4187 sstrncpy(&q, recip_pat, l-(q-p));
4188 fs_give((void **) &recip_pat);
4191 if(partic_pat){
4192 sstrncpy(&q, "/", l-(q-p));
4193 if(partic_not)
4194 sstrncpy(&q, "!", l-(q-p));
4196 sstrncpy(&q, "PARTIC=", l-(q-p));
4197 sstrncpy(&q, partic_pat, l-(q-p));
4198 fs_give((void **) &partic_pat);
4201 if(news_pat){
4202 sstrncpy(&q, "/", l-(q-p));
4203 if(news_not)
4204 sstrncpy(&q, "!", l-(q-p));
4206 sstrncpy(&q, "NEWS=", l-(q-p));
4207 sstrncpy(&q, news_pat, l-(q-p));
4208 fs_give((void **) &news_pat);
4211 if(subj_pat){
4212 sstrncpy(&q, "/", l-(q-p));
4213 if(subj_not)
4214 sstrncpy(&q, "!", l-(q-p));
4216 sstrncpy(&q, "SUBJ=", l-(q-p));
4217 sstrncpy(&q, subj_pat, l-(q-p));
4218 fs_give((void **)&subj_pat);
4221 if(alltext_pat){
4222 sstrncpy(&q, "/", l-(q-p));
4223 if(alltext_not)
4224 sstrncpy(&q, "!", l-(q-p));
4226 sstrncpy(&q, "ALL=", l-(q-p));
4227 sstrncpy(&q, alltext_pat, l-(q-p));
4228 fs_give((void **) &alltext_pat);
4231 if(bodytext_pat){
4232 sstrncpy(&q, "/", l-(q-p));
4233 if(bodytext_not)
4234 sstrncpy(&q, "!", l-(q-p));
4236 sstrncpy(&q, "BODY=", l-(q-p));
4237 sstrncpy(&q, bodytext_pat, l-(q-p));
4238 fs_give((void **) &bodytext_pat);
4241 if(keyword_pat){
4242 sstrncpy(&q, "/", l-(q-p));
4243 if(keyword_not)
4244 sstrncpy(&q, "!", l-(q-p));
4246 sstrncpy(&q, "KEY=", l-(q-p));
4247 sstrncpy(&q, keyword_pat, l-(q-p));
4248 fs_give((void **) &keyword_pat);
4251 if(charset_pat){
4252 sstrncpy(&q, "/", l-(q-p));
4253 if(charset_not)
4254 sstrncpy(&q, "!", l-(q-p));
4256 sstrncpy(&q, "CHAR=", l-(q-p));
4257 sstrncpy(&q, charset_pat, l-(q-p));
4258 fs_give((void **) &charset_pat);
4261 if(arb_pat){
4262 sstrncpy(&q, arb_pat, l-(q-p));
4263 fs_give((void **)&arb_pat);
4266 if(scorei_pat){
4267 sstrncpy(&q, "/SCOREI=", l-(q-p));
4268 sstrncpy(&q, scorei_pat, l-(q-p));
4269 fs_give((void **) &scorei_pat);
4272 if(age_pat){
4273 sstrncpy(&q, "/AGE=", l-(q-p));
4274 sstrncpy(&q, age_pat, l-(q-p));
4275 fs_give((void **) &age_pat);
4278 if(size_pat){
4279 sstrncpy(&q, "/SIZE=", l-(q-p));
4280 sstrncpy(&q, size_pat, l-(q-p));
4281 fs_give((void **) &size_pat);
4284 if(category_cmd){
4285 sstrncpy(&q, "/CATCMD=", l-(q-p));
4286 sstrncpy(&q, category_cmd, l-(q-p));
4287 fs_give((void **) &category_cmd);
4290 if(category_pat){
4291 sstrncpy(&q, "/CATVAL=", l-(q-p));
4292 sstrncpy(&q, category_pat, l-(q-p));
4293 fs_give((void **) &category_pat);
4296 if(category_lim){
4297 sstrncpy(&q, "/CATLIM=", l-(q-p));
4298 sstrncpy(&q, category_lim, l-(q-p));
4299 fs_give((void **) &category_lim);
4302 if(sentdate){
4303 sstrncpy(&q, sentdate, l-(q-p));
4304 fs_give((void **) &sentdate);
4307 if(fldr_type_pat){
4308 sstrncpy(&q, "/FLDTYPE=", l-(q-p));
4309 sstrncpy(&q, fldr_type_pat, l-(q-p));
4312 if(fldr_pat){
4313 sstrncpy(&q, "/FOLDER=", l-(q-p));
4314 sstrncpy(&q, fldr_pat, l-(q-p));
4315 fs_give((void **) &fldr_pat);
4318 if(afrom_type_pat){
4319 sstrncpy(&q, "/AFROM=", l-(q-p));
4320 sstrncpy(&q, afrom_type_pat, l-(q-p));
4323 * Add address types. If it is From or Reply-to
4324 * leave this out so it will still work with pine.
4326 if((pat->patgrp->inabook & IAB_FROM
4327 && pat->patgrp->inabook & IAB_REPLYTO
4328 && !(pat->patgrp->inabook & IAB_SENDER)
4329 && !(pat->patgrp->inabook & IAB_TO)
4330 && !(pat->patgrp->inabook & IAB_CC))
4332 (!(pat->patgrp->inabook & IAB_FROM)
4333 && !(pat->patgrp->inabook & IAB_REPLYTO)
4334 && !(pat->patgrp->inabook & IAB_SENDER)
4335 && !(pat->patgrp->inabook & IAB_TO)
4336 && !(pat->patgrp->inabook & IAB_CC))){
4337 ; /* leave it out */
4339 else{
4340 sstrncpy(&q, "/AFROMA=", l-(q-p));
4341 if(pat->patgrp->inabook & IAB_FROM)
4342 sstrncpy(&q, "F", l-(q-p));
4344 if(pat->patgrp->inabook & IAB_REPLYTO)
4345 sstrncpy(&q, "R", l-(q-p));
4347 if(pat->patgrp->inabook & IAB_SENDER)
4348 sstrncpy(&q, "S", l-(q-p));
4350 if(pat->patgrp->inabook & IAB_TO)
4351 sstrncpy(&q, "T", l-(q-p));
4353 if(pat->patgrp->inabook & IAB_CC)
4354 sstrncpy(&q, "C", l-(q-p));
4358 if(abooks_pat){
4359 sstrncpy(&q, "/ABOOKS=", l-(q-p));
4360 sstrncpy(&q, abooks_pat, l-(q-p));
4361 fs_give((void **) &abooks_pat);
4364 if(stat_new_val){
4365 sstrncpy(&q, "/STATN=", l-(q-p));
4366 sstrncpy(&q, stat_new_val, l-(q-p));
4369 if(stat_rec_val){
4370 sstrncpy(&q, "/STATR=", l-(q-p));
4371 sstrncpy(&q, stat_rec_val, l-(q-p));
4374 if(stat_del_val){
4375 sstrncpy(&q, "/STATD=", l-(q-p));
4376 sstrncpy(&q, stat_del_val, l-(q-p));
4379 if(stat_imp_val){
4380 sstrncpy(&q, "/STATI=", l-(q-p));
4381 sstrncpy(&q, stat_imp_val, l-(q-p));
4384 if(stat_ans_val){
4385 sstrncpy(&q, "/STATA=", l-(q-p));
4386 sstrncpy(&q, stat_ans_val, l-(q-p));
4389 if(stat_8bit_val){
4390 sstrncpy(&q, "/8BITS=", l-(q-p));
4391 sstrncpy(&q, stat_8bit_val, l-(q-p));
4394 if(stat_bom_val){
4395 sstrncpy(&q, "/BOM=", l-(q-p));
4396 sstrncpy(&q, stat_bom_val, l-(q-p));
4399 if(stat_boy_val){
4400 sstrncpy(&q, "/BOY=", l-(q-p));
4401 sstrncpy(&q, stat_boy_val, l-(q-p));
4404 sstrncpy(&q, "\" action=\"", l-(q-p));
4406 if(inherit_nick && *inherit_nick){
4407 sstrncpy(&q, "/INICK=", l-(q-p));
4408 sstrncpy(&q, inherit_nick, l-(q-p));
4409 fs_give((void **)&inherit_nick);
4412 if(action){
4413 if(action->is_a_role)
4414 sstrncpy(&q, "/ROLE=1", l-(q-p));
4416 if(action->is_a_incol)
4417 sstrncpy(&q, "/ISINCOL=1", l-(q-p));
4419 if(action->is_a_srch)
4420 sstrncpy(&q, "/ISSRCH=1", l-(q-p));
4422 if(action->is_a_score)
4423 sstrncpy(&q, "/ISSCORE=1", l-(q-p));
4425 if(action->is_a_filter){
4427 * Older pine will interpret a filter that has no folder
4428 * as a Delete, even if we set it up here to be a Just Set
4429 * State filter. Disable the filter for older versions in that
4430 * case. If kill is set then Delete is what is supposed to
4431 * happen, so that's ok. If folder is set then Move is what is
4432 * supposed to happen, so ok.
4434 if(!action->kill && !action->folder)
4435 sstrncpy(&q, "/FILTER=2", l-(q-p));
4436 else
4437 sstrncpy(&q, "/FILTER=1", l-(q-p));
4440 if(action->is_a_other)
4441 sstrncpy(&q, "/OTHER=1", l-(q-p));
4444 if(score_act){
4445 sstrncpy(&q, "/SCORE=", l-(q-p));
4446 sstrncpy(&q, score_act, l-(q-p));
4447 fs_give((void **)&score_act);
4450 if(hdrtok_act){
4451 sstrncpy(&q, "/SCOREHDRTOK=", l-(q-p));
4452 sstrncpy(&q, hdrtok_act, l-(q-p));
4453 fs_give((void **)&hdrtok_act);
4456 if(from_act){
4457 sstrncpy(&q, "/FROM=", l-(q-p));
4458 sstrncpy(&q, from_act, l-(q-p));
4459 fs_give((void **) &from_act);
4462 if(replyto_act){
4463 sstrncpy(&q, "/REPL=", l-(q-p));
4464 sstrncpy(&q, replyto_act, l-(q-p));
4465 fs_give((void **)&replyto_act);
4468 if(fcc_act){
4469 sstrncpy(&q, "/FCC=", l-(q-p));
4470 sstrncpy(&q, fcc_act, l-(q-p));
4471 fs_give((void **)&fcc_act);
4474 if(litsig_act){
4475 sstrncpy(&q, "/LSIG=", l-(q-p));
4476 sstrncpy(&q, litsig_act, l-(q-p));
4477 fs_give((void **)&litsig_act);
4480 if(sig_act){
4481 sstrncpy(&q, "/SIG=", l-(q-p));
4482 sstrncpy(&q, sig_act, l-(q-p));
4483 fs_give((void **)&sig_act);
4486 if(templ_act){
4487 sstrncpy(&q, "/TEMPLATE=", l-(q-p));
4488 sstrncpy(&q, templ_act, l-(q-p));
4489 fs_give((void **)&templ_act);
4492 if(cstm_act){
4493 sstrncpy(&q, "/CSTM=", l-(q-p));
4494 sstrncpy(&q, cstm_act, l-(q-p));
4495 fs_give((void **)&cstm_act);
4498 if(smtp_act){
4499 sstrncpy(&q, "/SMTP=", l-(q-p));
4500 sstrncpy(&q, smtp_act, l-(q-p));
4501 fs_give((void **)&smtp_act);
4504 if(nntp_act){
4505 sstrncpy(&q, "/NNTP=", l-(q-p));
4506 sstrncpy(&q, nntp_act, l-(q-p));
4507 fs_give((void **)&nntp_act);
4510 if(repl_val){
4511 sstrncpy(&q, "/RTYPE=", l-(q-p));
4512 sstrncpy(&q, repl_val, l-(q-p));
4515 if(forw_val){
4516 sstrncpy(&q, "/FTYPE=", l-(q-p));
4517 sstrncpy(&q, forw_val, l-(q-p));
4520 if(comp_val){
4521 sstrncpy(&q, "/CTYPE=", l-(q-p));
4522 sstrncpy(&q, comp_val, l-(q-p));
4525 if(incol_act){
4526 sstrncpy(&q, "/INCOL=", l-(q-p));
4527 sstrncpy(&q, incol_act, l-(q-p));
4528 fs_give((void **)&incol_act);
4531 if(sort_act){
4532 sstrncpy(&q, "/SORT=", l-(q-p));
4533 sstrncpy(&q, sort_act, l-(q-p));
4534 fs_give((void **)&sort_act);
4537 if(iform_act){
4538 sstrncpy(&q, "/IFORM=", l-(q-p));
4539 sstrncpy(&q, iform_act, l-(q-p));
4540 fs_give((void **)&iform_act);
4543 if(start_act){
4544 sstrncpy(&q, "/START=", l-(q-p));
4545 sstrncpy(&q, start_act, l-(q-p));
4548 if(folder_act){
4549 sstrncpy(&q, "/FOLDER=", l-(q-p));
4550 sstrncpy(&q, folder_act, l-(q-p));
4551 fs_give((void **) &folder_act);
4554 if(filt_ifnotdel){
4555 sstrncpy(&q, filt_ifnotdel, l-(q-p));
4556 fs_give((void **) &filt_ifnotdel);
4559 if(filt_nonterm){
4560 sstrncpy(&q, filt_nonterm, l-(q-p));
4561 fs_give((void **) &filt_nonterm);
4564 if(filt_nokill){
4565 sstrncpy(&q, filt_nokill, l-(q-p));
4566 fs_give((void **) &filt_nokill);
4569 if(filt_new_val){
4570 sstrncpy(&q, "/STATN=", l-(q-p));
4571 sstrncpy(&q, filt_new_val, l-(q-p));
4574 if(filt_del_val){
4575 sstrncpy(&q, "/STATD=", l-(q-p));
4576 sstrncpy(&q, filt_del_val, l-(q-p));
4579 if(filt_imp_val){
4580 sstrncpy(&q, "/STATI=", l-(q-p));
4581 sstrncpy(&q, filt_imp_val, l-(q-p));
4584 if(filt_ans_val){
4585 sstrncpy(&q, "/STATA=", l-(q-p));
4586 sstrncpy(&q, filt_ans_val, l-(q-p));
4589 if(keyword_set){
4590 sstrncpy(&q, "/KEYSET=", l-(q-p));
4591 sstrncpy(&q, keyword_set, l-(q-p));
4592 fs_give((void **) &keyword_set);
4595 if(keyword_clr){
4596 sstrncpy(&q, "/KEYCLR=", l-(q-p));
4597 sstrncpy(&q, keyword_clr, l-(q-p));
4598 fs_give((void **) &keyword_clr);
4601 if(q-p < l)
4602 *q++ = '\"';
4604 if(q-p < l)
4605 *q = '\0';
4607 p[l-1] = '\0';
4609 return(p);
4613 void
4614 convert_statebits_to_vals(long int bits, int *dval, int *aval, int *ival, int *nval)
4616 if(dval)
4617 *dval = ACT_STAT_LEAVE;
4618 if(aval)
4619 *aval = ACT_STAT_LEAVE;
4620 if(ival)
4621 *ival = ACT_STAT_LEAVE;
4622 if(nval)
4623 *nval = ACT_STAT_LEAVE;
4625 if(ival){
4626 if(bits & F_FLAG)
4627 *ival = ACT_STAT_SET;
4628 else if(bits & F_UNFLAG)
4629 *ival = ACT_STAT_CLEAR;
4632 if(aval){
4633 if(bits & F_ANS)
4634 *aval = ACT_STAT_SET;
4635 else if(bits & F_UNANS)
4636 *aval = ACT_STAT_CLEAR;
4639 if(dval){
4640 if(bits & F_DEL)
4641 *dval = ACT_STAT_SET;
4642 else if(bits & F_UNDEL)
4643 *dval = ACT_STAT_CLEAR;
4646 if(nval){
4647 if(bits & F_UNSEEN)
4648 *nval = ACT_STAT_SET;
4649 else if(bits & F_SEEN)
4650 *nval = ACT_STAT_CLEAR;
4656 * The "searched" bit will be set for each message which matches.
4658 * Args: patgrp -- Pattern to search with
4659 * stream --
4660 * searchset -- Restrict search to this set
4661 * section -- Searching a section of the message, not the whole thing
4662 * get_score -- Function to return the score for a message
4663 * flags -- Most of these are flags to mail_search_full. However, we
4664 * overload the flags namespace and pass some flags of our
4665 * own in here that we pick off before calling mail_search.
4666 * Danger, danger, don't overlap with flag values defined
4667 * for c-client (that we want to use). Flags that we will
4668 * use here are:
4669 * MP_IN_CCLIENT_CB
4670 * If this is set we are in a callback from c-client
4671 * because some imap data arrived. We don't want to
4672 * call c-client again because it isn't re-entrant safe.
4673 * This is only a problem if we need to get the text of
4674 * a message to do the search, the envelope is cached
4675 * already.
4676 * MP_NOT
4677 * We want a ! of the patgrp in the search.
4678 * We also throw in SE_FREE for free, since we create
4679 * the search program here.
4681 * Returns: 1 if any message in the searchset matches this pattern
4682 * 0 if no matches
4683 * -1 if couldn't perform search because of no_fetch restriction
4686 match_pattern(PATGRP_S *patgrp, MAILSTREAM *stream, SEARCHSET *searchset,
4687 char *section, long int (*get_score)(MAILSTREAM *, long int),
4688 long int flags)
4690 SEARCHPGM *pgm;
4691 SEARCHSET *s;
4692 MESSAGECACHE *mc;
4693 long i, msgno = 0L;
4694 int in_client_callback = 0, not = 0;
4696 dprint((7, "match_pattern\n"));
4699 * Is the current folder the right type and possibly the right specific
4700 * folder for a match?
4702 if(!(patgrp && !patgrp->bogus && match_pattern_folder(patgrp, stream)))
4703 return(0);
4706 * NULL searchset means that there is no message to compare against.
4707 * This is a match if the folder type matches above (that gets
4708 * us here), and there are no patterns to match against.
4710 * It is not totally clear what should be done in the case of an empty
4711 * search set. If there is search criteria, and someone does something
4712 * that is not specific to any messages (composing from scratch,
4713 * forwarding an attachment), then we can't be sure what a user would
4714 * expect. The original way was to just use the role, which we'll
4715 * preserve here.
4717 if(!searchset)
4718 return(1);
4721 * change by sderr : match_pattern_folder will sometimes
4722 * accept NULL streams, but if we are not in a folder-type-only
4723 * match test, we don't
4725 if(!stream)
4726 return(0);
4728 if(flags & MP_IN_CCLIENT_CB){
4729 in_client_callback++;
4730 flags &= ~MP_IN_CCLIENT_CB;
4733 if(flags & MP_NOT){
4734 not++;
4735 flags &= ~MP_NOT;
4738 flags |= SE_FREE;
4740 if(patgrp->stat_bom != PAT_STAT_EITHER){
4741 if(patgrp->stat_bom == PAT_STAT_YES){
4742 if(!ps_global->beginning_of_month){
4743 return(0);
4746 else if(patgrp->stat_bom == PAT_STAT_NO){
4747 if(ps_global->beginning_of_month){
4748 return(0);
4753 if(patgrp->stat_boy != PAT_STAT_EITHER){
4754 if(patgrp->stat_boy == PAT_STAT_YES){
4755 if(!ps_global->beginning_of_year){
4756 return(0);
4759 else if(patgrp->stat_boy == PAT_STAT_NO){
4760 if(ps_global->beginning_of_year){
4761 return(0);
4766 if(in_client_callback && is_imap_stream(stream)
4767 && (patgrp->alltext || patgrp->bodytext))
4768 return(-1);
4770 pgm = match_pattern_srchpgm(patgrp, stream, searchset);
4771 if(not && !(is_imap_stream(stream) && !modern_imap_stream(stream))){
4772 SEARCHPGM *srchpgm;
4774 srchpgm = pgm;
4775 pgm = mail_newsearchpgm();
4776 pgm->not = mail_newsearchpgmlist();
4777 pgm->not->pgm = srchpgm;
4780 if((patgrp->alltext || patgrp->bodytext)
4781 && (!is_imap_stream(stream) || modern_imap_stream(stream)))
4783 * Cache isn't going to work. Search on server.
4784 * Except that is likely to not work on an old imap server because
4785 * the OR criteria won't work and we are likely to have some ORs.
4786 * So turn off the NOSERVER flag (and search on server if remote)
4787 * unless the server is an old server. It doesn't matter if we
4788 * turn if off if it's not an imap stream, but we do it anyway.
4790 flags &= ~SE_NOSERVER;
4792 if(section){
4794 * Mail_search_full only searches the top-level msg. We want to
4795 * search an attached msg instead. First do the stuff
4796 * that mail_search_full would have done before calling
4797 * mail_search_msg, then call mail_search_msg with a section number.
4798 * Mail_search_msg does take a section number even though
4799 * mail_search_full doesn't.
4803 * We'll only ever set section if the searchset is a single message.
4805 if(pgm->msgno->next == NULL && pgm->msgno->first == pgm->msgno->last)
4806 msgno = pgm->msgno->first;
4808 for(i = 1L; i <= stream->nmsgs; i++)
4809 if((mc = mail_elt(stream, i)) != NULL)
4810 mc->searched = NIL;
4812 if(mail_search_msg(stream,msgno,section,pgm)
4813 && msgno > 0L && msgno <= stream->nmsgs
4814 && (mc = mail_elt(stream, msgno)))
4815 mc->searched = T;
4817 if(flags & SE_FREE)
4818 mail_free_searchpgm(&pgm);
4820 else{
4822 * Here we could be checking on the return value to see if
4823 * the search was "successful" or not. It may be the case
4824 * that we'd want to stop trying filtering if we got some
4825 * sort of error, but for now we would just continue on
4826 * to the next filter.
4828 pine_mail_search_full(stream, "UTF-8", pgm, flags);
4831 /* we searched without the not, reverse it */
4832 if(not && is_imap_stream(stream) && !modern_imap_stream(stream)){
4833 for(msgno = 1L; msgno < mn_get_total(sp_msgmap(stream)); msgno++)
4834 if(stream && msgno && msgno <= stream->nmsgs
4835 && (mc=mail_elt(stream,msgno)) && mc->searched)
4836 mc->searched = NIL;
4837 else
4838 mc->searched = T;
4841 /* check scores */
4842 if(get_score && scores_are_used(SCOREUSE_GET) && patgrp->do_score){
4843 char *savebits;
4844 SEARCHSET *ss;
4847 * Get_score may call build_header_line recursively (we may
4848 * be in build_header_line now) so we have to preserve and
4849 * restore the sequence bits.
4851 savebits = (char *)fs_get((stream->nmsgs+1) * sizeof(char));
4853 for(i = 1L; i <= stream->nmsgs; i++){
4854 if((mc = mail_elt(stream, i)) != NULL){
4855 savebits[i] = mc->sequence;
4856 mc->sequence = 0;
4861 * Build a searchset which will get all the scores that we
4862 * need but not more.
4864 for(s = searchset; s; s = s->next)
4865 for(msgno = s->first; msgno <= s->last; msgno++)
4866 if(msgno > 0L && msgno <= stream->nmsgs
4867 && (mc = mail_elt(stream, msgno)) && mc->searched
4868 && get_msg_score(stream, msgno) == SCORE_UNDEF)
4869 mc->sequence = 1;
4871 if((ss = build_searchset(stream)) != NULL){
4872 (void)calculate_some_scores(stream, ss, in_client_callback);
4873 mail_free_searchset(&ss);
4877 * Now check the scores versus the score intervals to see if
4878 * any of the messages which have matched up to this point can
4879 * be tossed because they don't match the score interval.
4881 for(s = searchset; s; s = s->next)
4882 for(msgno = s->first; msgno <= s->last; msgno++)
4883 if(msgno > 0L && msgno <= stream->nmsgs
4884 && (mc = mail_elt(stream, msgno)) && mc->searched){
4885 long score;
4887 score = (*get_score)(stream, msgno);
4890 * If the score is outside all of the intervals,
4891 * turn off the searched bit.
4892 * So that means we check each interval and if
4893 * it is inside any interval we stop and leave
4894 * the bit set. If it is outside we keep checking.
4896 if(score != SCORE_UNDEF){
4897 INTVL_S *iv;
4899 for(iv = patgrp->score; iv; iv = iv->next)
4900 if(score >= iv->imin && score <= iv->imax)
4901 break;
4903 if(!iv)
4904 mc->searched = NIL;
4908 for(i = 1L; i <= stream->nmsgs; i++)
4909 if((mc = mail_elt(stream, i)) != NULL)
4910 mc->sequence = savebits[i];
4912 fs_give((void **)&savebits);
4915 /* if there are still matches, check for 8bit subject match */
4916 if(patgrp->stat_8bitsubj != PAT_STAT_EITHER)
4917 find_8bitsubj_in_messages(stream, searchset, patgrp->stat_8bitsubj, 1);
4919 /* if there are still matches, check for charset matches */
4920 if(patgrp->charsets)
4921 find_charsets_in_messages(stream, searchset, patgrp, 1);
4923 /* Still matches, check addrbook */
4924 if(patgrp->inabook != IAB_EITHER)
4925 address_in_abook(stream, searchset, patgrp->inabook, patgrp->abooks);
4927 /* Still matches? Run the categorization command on each msg. */
4928 if(pith_opt_filter_pattern_cmd)
4929 (*pith_opt_filter_pattern_cmd)(patgrp->category_cmd, searchset, stream, patgrp->cat_lim, patgrp->cat);
4931 for(s = searchset; s; s = s->next)
4932 for(msgno = s->first; msgno > 0L && msgno <= s->last; msgno++)
4933 if(msgno > 0L && msgno <= stream->nmsgs
4934 && (mc = mail_elt(stream, msgno)) && mc->searched)
4935 return(1);
4937 return(0);
4942 * Look through messages in searchset to see if they contain 8bit
4943 * characters in their subjects. All of the messages in
4944 * searchset should initially have the searched bit set. Turn off the
4945 * searched bit where appropriate.
4947 void
4948 find_8bitsubj_in_messages(MAILSTREAM *stream, SEARCHSET *searchset,
4949 int stat_8bitsubj, int saveseqbits)
4951 char *savebits = NULL;
4952 SEARCHSET *s, *ss = NULL;
4953 MESSAGECACHE *mc;
4954 long count = 0L;
4955 unsigned long msgno;
4958 * If we are being called while in build_header_line we may
4959 * call build_header_line recursively. So save and restore the
4960 * sequence bits.
4962 if(saveseqbits)
4963 savebits = (char *) fs_get((stream->nmsgs+1) * sizeof(char));
4965 for(msgno = 1L; msgno <= stream->nmsgs; msgno++){
4966 if((mc = mail_elt(stream, msgno)) != NULL){
4967 if(savebits)
4968 savebits[msgno] = mc->sequence;
4970 mc->sequence = 0;
4975 * Build a searchset so we can look at all the envelopes
4976 * we need to look at but only those we need to look at.
4977 * Everything with the searched bit set is still a
4978 * possibility, so restrict to that set.
4981 for(s = searchset; s; s = s->next)
4982 for(msgno = s->first; msgno <= s->last; msgno++)
4983 if(msgno > 0L && msgno <= stream->nmsgs
4984 && (mc = mail_elt(stream, msgno)) && mc->searched){
4985 mc->sequence = 1;
4986 count++;
4989 ss = build_searchset(stream);
4991 if(count){
4992 SEARCHSET **sset;
4994 mail_parameters(NULL, SET_FETCHLOOKAHEADLIMIT, (void *) count);
4997 * This causes the lookahead to fetch precisely
4998 * the messages we want (in the searchset) instead
4999 * of just fetching the next 20 sequential
5000 * messages. If the searching so far has caused
5001 * a sparse searchset in a large mailbox, the
5002 * difference can be substantial.
5003 * This resets automatically after the first fetch.
5005 sset = (SEARCHSET **) mail_parameters(stream,
5006 GET_FETCHLOOKAHEAD,
5007 (void *) stream);
5008 if(sset)
5009 *sset = ss;
5012 for(s = ss; s; s = s->next){
5013 for(msgno = s->first; msgno <= s->last; msgno++){
5014 ENVELOPE *e;
5016 if(!stream || msgno <= 0L || msgno > stream->nmsgs)
5017 continue;
5019 e = pine_mail_fetchenvelope(stream, msgno);
5020 if(stat_8bitsubj == PAT_STAT_YES){
5021 if(e && e->subject){
5022 char *p;
5024 for(p = e->subject; *p; p++)
5025 if(*p & 0x80)
5026 break;
5028 if(!*p && msgno > 0L && msgno <= stream->nmsgs
5029 && (mc = mail_elt(stream, msgno)))
5030 mc->searched = NIL;
5032 else if(msgno > 0L && msgno <= stream->nmsgs
5033 && (mc = mail_elt(stream, msgno)))
5034 mc->searched = NIL;
5036 else if(stat_8bitsubj == PAT_STAT_NO){
5037 if(e && e->subject){
5038 char *p;
5040 for(p = e->subject; *p; p++)
5041 if(*p & 0x80)
5042 break;
5044 if(*p && msgno > 0L && msgno <= stream->nmsgs
5045 && (mc = mail_elt(stream, msgno)))
5046 mc->searched = NIL;
5052 if(savebits){
5053 for(msgno = 1L; msgno <= stream->nmsgs; msgno++)
5054 if((mc = mail_elt(stream, msgno)) != NULL)
5055 mc->sequence = savebits[msgno];
5057 fs_give((void **) &savebits);
5060 if(ss)
5061 mail_free_searchset(&ss);
5066 * Look through messages in searchset to see if they contain any of the
5067 * charsets or scripts listed in charsets pattern. All of the messages in
5068 * searchset should initially have the searched bit set. Turn off the
5069 * searched bit where appropriate.
5071 void
5072 find_charsets_in_messages(MAILSTREAM *stream, SEARCHSET *searchset,
5073 PATGRP_S *patgrp, int saveseqbits)
5075 char *savebits = NULL;
5076 unsigned long msgno;
5077 long count = 0L;
5078 MESSAGECACHE *mc;
5079 SEARCHSET *s, *ss;
5081 if(!stream || !patgrp)
5082 return;
5085 * When we actually want to use charsets, we convert it into a list
5086 * of charsets instead of the mixed list of scripts and charsets and
5087 * we eliminate duplicates. This is more efficient when we actually
5088 * do the lookups and compares.
5090 if(!patgrp->charsets_list){
5091 PATTERN_S *cs;
5092 const CHARSET *cset;
5093 STRLIST_S *sl = NULL, *newsl;
5094 unsigned long scripts = 0L;
5095 SCRIPT *script;
5097 for(cs = patgrp->charsets; cs; cs = cs->next){
5099 * Run through the charsets pattern looking for
5100 * scripts and set the corresponding script bits.
5101 * If it isn't a script, it is a character set.
5103 if(cs->substring && (script = utf8_script(cs->substring)))
5104 scripts |= script->script;
5105 else{
5106 /* add it to list as a specific character set */
5107 newsl = new_strlist(cs->substring);
5108 if(compare_strlists_for_match(sl, newsl)) /* already in list */
5109 free_strlist(&newsl);
5110 else{
5111 newsl->next = sl;
5112 sl = newsl;
5118 * Now scripts has a bit set for each script the user
5119 * specified in the charsets pattern. Go through all of
5120 * the known charsets and include ones in these scripts.
5122 if(scripts){
5123 for(cset = utf8_charset(NIL); cset && cset->name; cset++){
5124 if(cset->script & scripts){
5126 /* filter this out of each script, not very useful */
5127 if(!strucmp("ISO-2022-JP-2", cset->name)
5128 || !strucmp("UTF-7", cset->name)
5129 || !strucmp("UTF-8", cset->name))
5130 continue;
5132 /* add cset->name to the list */
5133 newsl = new_strlist(cset->name);
5134 if(compare_strlists_for_match(sl, newsl))
5135 free_strlist(&newsl);
5136 else{
5137 newsl->next = sl;
5138 sl = newsl;
5144 patgrp->charsets_list = sl;
5148 * This may call build_header_line recursively because we may be in
5149 * build_header_line now. So we have to preserve and restore the
5150 * sequence bits since we want to use them here.
5152 if(saveseqbits)
5153 savebits = (char *) fs_get((stream->nmsgs+1) * sizeof(char));
5155 for(msgno = 1L; msgno <= stream->nmsgs; msgno++){
5156 if((mc = mail_elt(stream, msgno)) != NULL){
5157 if(savebits)
5158 savebits[msgno] = mc->sequence;
5160 mc->sequence = 0;
5166 * Build a searchset so we can look at all the bodies
5167 * we need to look at but only those we need to look at.
5168 * Everything with the searched bit set is still a
5169 * possibility, so restrict to that set.
5172 for(s = searchset; s; s = s->next)
5173 for(msgno = s->first; msgno <= s->last; msgno++)
5174 if(msgno > 0L && msgno <= stream->nmsgs
5175 && (mc = mail_elt(stream, msgno)) && mc->searched){
5176 mc->sequence = 1;
5177 count++;
5180 ss = build_searchset(stream);
5182 if(count){
5183 SEARCHSET **sset;
5185 mail_parameters(NULL, SET_FETCHLOOKAHEADLIMIT, (void *) count);
5188 * This causes the lookahead to fetch precisely
5189 * the messages we want (in the searchset) instead
5190 * of just fetching the next 20 sequential
5191 * messages. If the searching so far has caused
5192 * a sparse searchset in a large mailbox, the
5193 * difference can be substantial.
5194 * This resets automatically after the first fetch.
5196 sset = (SEARCHSET **) mail_parameters(stream,
5197 GET_FETCHLOOKAHEAD,
5198 (void *) stream);
5199 if(sset)
5200 *sset = ss;
5203 for(s = ss; s; s = s->next){
5204 for(msgno = s->first; msgno <= s->last; msgno++){
5206 if(msgno <= 0L || msgno > stream->nmsgs)
5207 continue;
5209 if(patgrp->charsets_list
5210 && charsets_present_in_msg(stream,msgno,patgrp->charsets_list)){
5211 if(patgrp->charsets->not){
5212 if((mc = mail_elt(stream, msgno)))
5213 mc->searched = NIL;
5215 /* else leave it */
5217 else{ /* charset isn't in message */
5218 if(!patgrp->charsets->not){
5219 if((mc = mail_elt(stream, msgno)))
5220 mc->searched = NIL;
5222 /* else leave it */
5227 if(savebits){
5228 for(msgno = 1L; msgno <= stream->nmsgs; msgno++)
5229 if((mc = mail_elt(stream, msgno)) != NULL)
5230 mc->sequence = savebits[msgno];
5232 fs_give((void **) &savebits);
5235 if(ss)
5236 mail_free_searchset(&ss);
5241 * Look for any of the charsets in this particular message.
5243 * Returns 1 if there is a match, 0 otherwise.
5246 charsets_present_in_msg(MAILSTREAM *stream, long unsigned int rawmsgno, STRLIST_S *charsets)
5248 BODY *body = NULL;
5249 ENVELOPE *env = NULL;
5250 STRLIST_S *msg_charsets = NULL;
5251 int ret = 0;
5253 if(charsets && stream && rawmsgno > 0L && rawmsgno <= stream->nmsgs){
5254 env = pine_mail_fetchstructure(stream, rawmsgno, &body);
5255 collect_charsets_from_subj(env, &msg_charsets);
5256 collect_charsets_from_body(body, &msg_charsets);
5257 if(msg_charsets){
5258 ret = compare_strlists_for_match(msg_charsets, charsets);
5259 free_strlist(&msg_charsets);
5263 return(ret);
5267 void
5268 collect_charsets_from_subj(ENVELOPE *env, STRLIST_S **listptr)
5270 STRLIST_S *newsl;
5271 char *text, *e;
5273 if(listptr && env && env->subject){
5274 /* find encoded word */
5275 for(text = env->subject; *text; text++){
5276 if((*text == '=') && (text[1] == '?') && isalpha(text[2]) &&
5277 (e = strchr(text+2,'?'))){
5278 *e = '\0'; /* tie off charset name */
5280 newsl = new_strlist(text+2);
5281 *e = '?';
5283 if(compare_strlists_for_match(*listptr, newsl))
5284 free_strlist(&newsl);
5285 else{
5286 newsl->next = *listptr;
5287 *listptr = newsl;
5296 * Check for any of the charsets in any of the charset params in
5297 * any of the text parts of the body of a message. Put them in the list
5298 * pointed to by listptr.
5300 void
5301 collect_charsets_from_body(struct mail_bodystruct *body, STRLIST_S **listptr)
5303 PART *part;
5304 char *cset;
5306 if(listptr && body){
5307 switch(body->type){
5308 case TYPEMULTIPART:
5309 for(part = body->nested.part; part; part = part->next)
5310 collect_charsets_from_body(&part->body, listptr);
5312 break;
5314 case TYPEMESSAGE:
5315 if(!strucmp(body->subtype, "RFC822")){
5316 collect_charsets_from_subj(body->nested.msg->env, listptr);
5317 collect_charsets_from_body(body->nested.msg->body, listptr);
5318 break;
5320 /* else fall through to text case */
5322 case TYPETEXT:
5323 cset = parameter_val(body->parameter, "charset");
5324 if(cset){
5325 STRLIST_S *newsl;
5327 newsl = new_strlist(cset);
5329 if(compare_strlists_for_match(*listptr, newsl))
5330 free_strlist(&newsl);
5331 else{
5332 newsl->next = *listptr;
5333 *listptr = newsl;
5336 fs_give((void **) &cset);
5339 break;
5341 default: /* non-text terminal mode */
5342 break;
5349 * If any of the names in list1 is the same as any of the names in list2
5350 * then return 1, else return 0. Comparison is case independent.
5353 compare_strlists_for_match(STRLIST_S *list1, STRLIST_S *list2)
5355 int ret = 0;
5356 STRLIST_S *cs1, *cs2;
5358 for(cs1 = list1; !ret && cs1; cs1 = cs1->next)
5359 for(cs2 = list2; !ret && cs2; cs2 = cs2->next)
5360 if(cs1->name && cs2->name && !strucmp(cs1->name, cs2->name))
5361 ret = 1;
5363 return(ret);
5368 match_pattern_folder(PATGRP_S *patgrp, MAILSTREAM *stream)
5370 int is_news;
5372 /* change by sderr : we match FLDR_ANY even if stream is NULL */
5373 return((patgrp->fldr_type == FLDR_ANY)
5374 || (stream
5375 && (((is_news = IS_NEWS(stream))
5376 && patgrp->fldr_type == FLDR_NEWS)
5377 || (!is_news && patgrp->fldr_type == FLDR_EMAIL)
5378 || (patgrp->fldr_type == FLDR_SPECIFIC
5379 && match_pattern_folder_specific(patgrp->folder,
5380 stream, FOR_PATTERN)))));
5385 * Returns positive if this stream is open on one of the folders in the
5386 * folders argument, 0 otherwise.
5388 * If FOR_PATTERN is set, this interprets simple names as nicknames in
5389 * the incoming collection, otherwise it treats simple names as being in
5390 * the primary collection.
5391 * If FOR_FILT is set, the folder names are detokenized before being used.
5394 match_pattern_folder_specific(PATTERN_S *folders, MAILSTREAM *stream, int flags)
5396 PATTERN_S *p;
5397 int match = 0;
5398 char *patfolder, *free_this = NULL;
5400 dprint((8, "match_pattern_folder_specific\n"));
5402 if(!(stream && stream->mailbox && stream->mailbox[0]))
5403 return(0);
5406 * For each of the folders in the pattern, see if we get
5407 * a match. We're just looking for any match. If none match,
5408 * we return 0, otherwise we fall through and check the rest
5409 * of the pattern. The fact that the string is called "substring"
5410 * is not meaningful. We're just using the convenient pattern
5411 * structure to store a list of folder names. They aren't
5412 * substrings of names, they are the whole name.
5414 for(p = folders; !match && p; p = p->next){
5415 free_this = NULL;
5416 if(flags & FOR_FILTER)
5417 patfolder = free_this = detoken_src(p->substring, FOR_FILT, NULL,
5418 NULL, NULL, NULL);
5419 else
5420 patfolder = p->substring;
5422 if(patfolder
5423 && (!strucmp(patfolder, ps_global->inbox_name)
5424 || !strcmp(patfolder, ps_global->VAR_INBOX_PATH))){
5425 if(sp_flagged(stream, SP_INBOX))
5426 match++;
5428 else{
5429 char *fname;
5430 char *t, *streamfolder;
5431 char tmp1[MAILTMPLEN], tmp2[MAX(MAILTMPLEN,NETMAXMBX)];
5432 CONTEXT_S *cntxt = NULL;
5434 if(flags & FOR_PATTERN){
5436 * See if patfolder is a nickname in the incoming collection.
5437 * If so, use its real name instead.
5439 if(patfolder[0] &&
5440 (ps_global->context_list->use & CNTXT_INCMNG) &&
5441 (fname = (folder_is_nick(patfolder,
5442 FOLDERS(ps_global->context_list),
5443 0))))
5444 patfolder = fname;
5446 else{
5447 char *save_ref = NULL;
5450 * If it's an absolute pathname, we treat is as a local file
5451 * instead of interpreting it in the primary context.
5453 if(!is_absolute_path(patfolder)
5454 && !(cntxt = default_save_context(ps_global->context_list)))
5455 cntxt = ps_global->context_list;
5458 * Because this check is independent of where the user is
5459 * in the folder hierarchy and has nothing to do with that,
5460 * we want to ignore the reference field built into the
5461 * context. Zero it out temporarily here.
5463 if(cntxt && cntxt->dir){
5464 save_ref = cntxt->dir->ref;
5465 cntxt->dir->ref = NULL;
5468 patfolder = context_apply(tmp1, cntxt, patfolder, sizeof(tmp1));
5469 if(save_ref)
5470 cntxt->dir->ref = save_ref;
5473 switch(patfolder[0]){
5474 case '{':
5475 if(stream->mailbox[0] == '{' &&
5476 same_stream(patfolder, stream) &&
5477 (streamfolder = strindex(&stream->mailbox[1], '}')) &&
5478 (t = strindex(&patfolder[1], '}')) &&
5479 (!strcmp(t+1, streamfolder+1) ||
5480 (*(t+1) == '\0' && !strcmp("INBOX", streamfolder+1))))
5481 match++;
5483 break;
5485 case '#':
5486 if(!strcmp(patfolder, stream->mailbox))
5487 match++;
5489 break;
5491 default:
5492 t = (strlen(patfolder) < (MAILTMPLEN/2))
5493 ? mailboxfile(tmp2, patfolder) : NULL;
5494 if(t && *t && !strcmp(t, stream->mailbox))
5495 match++;
5497 break;
5501 if(free_this)
5502 fs_give((void **) &free_this);
5505 return(match);
5510 * generate a search program corresponding to the provided patgrp
5512 SEARCHPGM *
5513 match_pattern_srchpgm(PATGRP_S *patgrp, MAILSTREAM *stream, SEARCHSET *searchset)
5515 SEARCHPGM *pgm, *tmppgm;
5516 SEARCHOR *or;
5517 SEARCHSET **sp;
5519 pgm = mail_newsearchpgm();
5521 sp = &pgm->msgno;
5522 /* copy the searchset */
5523 while(searchset){
5524 SEARCHSET *s;
5526 s = mail_newsearchset();
5527 s->first = searchset->first;
5528 s->last = searchset->last;
5529 searchset = searchset->next;
5530 *sp = s;
5531 sp = &s->next;
5534 if(!patgrp)
5535 return(pgm);
5537 if(patgrp->subj){
5538 if(patgrp->subj->not)
5539 tmppgm = next_not(pgm);
5540 else
5541 tmppgm = pgm;
5543 set_up_search_pgm("subject", patgrp->subj, tmppgm);
5546 if(patgrp->cc){
5547 if(patgrp->cc->not)
5548 tmppgm = next_not(pgm);
5549 else
5550 tmppgm = pgm;
5552 set_up_search_pgm("cc", patgrp->cc, tmppgm);
5555 if(patgrp->from){
5556 if(patgrp->from->not)
5557 tmppgm = next_not(pgm);
5558 else
5559 tmppgm = pgm;
5561 set_up_search_pgm("from", patgrp->from, tmppgm);
5564 if(patgrp->to){
5565 if(patgrp->to->not)
5566 tmppgm = next_not(pgm);
5567 else
5568 tmppgm = pgm;
5570 set_up_search_pgm("to", patgrp->to, tmppgm);
5573 if(patgrp->sender){
5574 if(patgrp->sender->not)
5575 tmppgm = next_not(pgm);
5576 else
5577 tmppgm = pgm;
5579 set_up_search_pgm("sender", patgrp->sender, tmppgm);
5582 if(patgrp->news){
5583 if(patgrp->news->not)
5584 tmppgm = next_not(pgm);
5585 else
5586 tmppgm = pgm;
5588 set_up_search_pgm("newsgroups", patgrp->news, tmppgm);
5591 /* To OR Cc */
5592 if(patgrp->recip){
5593 if(patgrp->recip->not)
5594 tmppgm = next_not(pgm);
5595 else
5596 tmppgm = pgm;
5598 or = next_or(&tmppgm->or);
5600 set_up_search_pgm("to", patgrp->recip, or->first);
5601 set_up_search_pgm("cc", patgrp->recip, or->second);
5604 /* To OR Cc OR From */
5605 if(patgrp->partic){
5606 if(patgrp->partic->not)
5607 tmppgm = next_not(pgm);
5608 else
5609 tmppgm = pgm;
5611 or = next_or(&tmppgm->or);
5613 set_up_search_pgm("to", patgrp->partic, or->first);
5615 or->second->or = mail_newsearchor();
5616 set_up_search_pgm("cc", patgrp->partic, or->second->or->first);
5617 set_up_search_pgm("from", patgrp->partic, or->second->or->second);
5620 if(patgrp->arbhdr){
5621 ARBHDR_S *a;
5623 for(a = patgrp->arbhdr; a; a = a->next)
5624 if(a->field && a->field[0] && a->p){
5625 if(a->p->not)
5626 tmppgm = next_not(pgm);
5627 else
5628 tmppgm = pgm;
5630 set_up_search_pgm(a->field, a->p, tmppgm);
5634 if(patgrp->alltext){
5635 if(patgrp->alltext->not)
5636 tmppgm = next_not(pgm);
5637 else
5638 tmppgm = pgm;
5640 set_up_search_pgm("alltext", patgrp->alltext, tmppgm);
5643 if(patgrp->bodytext){
5644 if(patgrp->bodytext->not)
5645 tmppgm = next_not(pgm);
5646 else
5647 tmppgm = pgm;
5649 set_up_search_pgm("bodytext", patgrp->bodytext, tmppgm);
5652 if(patgrp->keyword){
5653 PATTERN_S *p_old, *p_new, *new_pattern = NULL, **nextp;
5654 char *q;
5656 if(patgrp->keyword->not)
5657 tmppgm = next_not(pgm);
5658 else
5659 tmppgm = pgm;
5662 * The keyword entries may be nicknames instead of the actual
5663 * keywords, so those need to be converted to actual keywords.
5665 * If we search for keywords that are not defined for a folder
5666 * we may get error messages back that we don't want instead of
5667 * just no match. We will build a replacement pattern here which
5668 * contains only the defined subset of the keywords.
5671 nextp = &new_pattern;
5673 for(p_old = patgrp->keyword; p_old; p_old = p_old->next){
5674 q = nick_to_keyword(p_old->substring);
5675 if(user_flag_index(stream, q) >= 0){
5676 p_new = (PATTERN_S *) fs_get(sizeof(*p_new));
5677 memset(p_new, 0, sizeof(*p_new));
5678 p_new->substring = cpystr(q);
5679 *nextp = p_new;
5680 nextp = &p_new->next;
5685 * If there are some matching keywords that are defined in
5686 * the folder, then we are ok because we will match only if
5687 * we match one of those. However, if the list is empty, then
5688 * we can't just leave this part of the search program empty.
5689 * That would result in a match instead of not a match.
5690 * We can fake our way around the problem with NOT. If the
5691 * list is empty we want the opposite, so we insert a NOT in
5692 * front of an empty program. We may end up with NOT NOT if
5693 * this was already NOT'd, but that's ok, too. Alternatively,
5694 * we could undo the first NOT instead.
5697 if(new_pattern){
5698 set_up_search_pgm("keyword", new_pattern, tmppgm);
5699 free_pattern(&new_pattern);
5701 else
5702 (void) next_not(tmppgm); /* add NOT of something that matches,
5703 so the NOT thing doesn't match */
5706 if(patgrp->do_age && patgrp->age){
5707 INTVL_S *iv;
5708 SEARCHOR *or;
5710 tmppgm = pgm;
5712 for(iv = patgrp->age; iv; iv = iv->next){
5713 if(iv->next){
5714 or = next_or(&tmppgm->or);
5715 set_search_by_age(iv, or->first, patgrp->age_uses_sentdate);
5716 tmppgm = or->second;
5718 else
5719 set_search_by_age(iv, tmppgm, patgrp->age_uses_sentdate);
5723 if(patgrp->do_size && patgrp->size){
5724 INTVL_S *iv;
5725 SEARCHOR *or;
5727 tmppgm = pgm;
5729 for(iv = patgrp->size; iv; iv = iv->next){
5730 if(iv->next){
5731 or = next_or(&tmppgm->or);
5732 set_search_by_size(iv, or->first);
5733 tmppgm = or->second;
5735 else
5736 set_search_by_size(iv, tmppgm);
5740 SETPGMSTATUS(patgrp->stat_new,pgm->unseen,pgm->seen);
5741 SETPGMSTATUS(patgrp->stat_rec,pgm->recent,pgm->old);
5742 SETPGMSTATUS(patgrp->stat_del,pgm->deleted,pgm->undeleted);
5743 SETPGMSTATUS(patgrp->stat_imp,pgm->flagged,pgm->unflagged);
5744 SETPGMSTATUS(patgrp->stat_ans,pgm->answered,pgm->unanswered);
5746 return(pgm);
5750 SEARCHPGM *
5751 next_not(SEARCHPGM *pgm)
5753 SEARCHPGMLIST *not, **not_ptr;
5755 if(!pgm)
5756 return(NULL);
5758 /* find next unused not slot */
5759 for(not = pgm->not; not && not->next; not = not->next)
5762 if(not)
5763 not_ptr = &not->next;
5764 else
5765 not_ptr = &pgm->not;
5767 /* allocate */
5768 *not_ptr = mail_newsearchpgmlist();
5770 return((*not_ptr)->pgm);
5774 SEARCHOR *
5775 next_or(struct search_or **startingor)
5777 SEARCHOR *or, **or_ptr;
5779 /* find next unused or slot */
5780 for(or = (*startingor); or && or->next; or = or->next)
5783 if(or)
5784 or_ptr = &or->next;
5785 else
5786 or_ptr = startingor;
5788 /* allocate */
5789 *or_ptr = mail_newsearchor();
5791 return(*or_ptr);
5795 void
5796 set_up_search_pgm(char *field, PATTERN_S *pattern, SEARCHPGM *pgm)
5798 SEARCHOR *or;
5800 if(field && pattern && pgm){
5803 * To is special because we want to use the ReSent-To header instead
5804 * of the To header if it exists. We set up something like:
5806 * if((resent-to matches pat1 or pat2...)
5807 * OR
5808 * (<resent-to doesn't exist> AND (to matches pat1 or pat2...)))
5810 * Some servers (Exchange, apparently) seem to have trouble with
5811 * the search for the empty string to decide if the header exists
5812 * or not. So, we will search for either the empty string OR the
5813 * header with a SPACE in it. Some still have trouble with this
5814 * so we are changing it to be off by default.
5816 if(!strucmp(field, "to") && F_ON(F_USE_RESENTTO, ps_global)){
5817 or = next_or(&pgm->or);
5819 add_type_to_pgm("resent-to", pattern, or->first);
5821 /* check for resent-to doesn't exist */
5822 or->second->not = mail_newsearchpgmlist();
5824 or->second->not->pgm->or = mail_newsearchor();
5825 set_srch("resent-to", " ", or->second->not->pgm->or->first);
5826 set_srch("resent-to", "", or->second->not->pgm->or->second);
5828 /* now add the real To search to second */
5829 add_type_to_pgm(field, pattern, or->second);
5831 else
5832 add_type_to_pgm(field, pattern, pgm);
5837 void
5838 add_type_to_pgm(char *field, PATTERN_S *pattern, SEARCHPGM *pgm)
5840 PATTERN_S *p;
5841 SEARCHOR *or;
5842 SEARCHPGM *notpgm, *tpgm;
5843 int cnt = 0;
5845 if(field && pattern && pgm){
5847 * Here is a weird bit of logic. What we want here is simply
5848 * A or B or C or D
5849 * for all of the elements of pattern. Ors are a bit complicated.
5850 * The list of ORs in the SEARCHPGM structure are ANDed together,
5851 * not ORd together. It's for things like
5852 * Subject A or B AND From C or D
5853 * The Subject part would be one member of the OR list and the From
5854 * part would be another member of the OR list. Instead we want
5855 * a big OR which may have more than two members (first and second)
5856 * but the structure just has two members. So we have to build an
5857 * OR tree and we build it by going down one branch of the tree
5858 * instead of by balancing the branches.
5860 * or
5861 * / \
5862 * first==A second
5863 * / \
5864 * first==B second
5865 * / \
5866 * first==C second==D
5868 * There is an additional problem. Some servers don't like deeply
5869 * nested logic in the SEARCH command. The tree above produces a
5870 * fairly deeply nested command if the user wants to match on
5871 * several different From addresses or Subjects...
5872 * We use the tried and true equation
5874 * (A or B) == !(!A and !B)
5876 * to change the deeply nested OR tree into ANDs which aren't nested.
5877 * Right now we're only doing that if the nesting is fairly deep.
5878 * We can think of some reasons to do that. First, we know that the
5879 * OR thing works, that's what we've been using for a while and the
5880 * only problem is the deep nesting. 2nd, it is easier to understand.
5881 * 3rd, it looks dumb to use NOT NOT A instead of A.
5882 * It is probably dumb to mix the two, but what the heck.
5883 * Hubert 2003-04-02
5885 for(p = pattern; p; p = p->next)
5886 cnt++;
5888 if(cnt < 10){ /* use ORs if count is low */
5889 for(p = pattern; p; p = p->next){
5890 if(p->next){
5891 or = next_or(&pgm->or);
5893 set_srch(field, p->substring ? p->substring : "", or->first);
5894 pgm = or->second;
5896 else
5897 set_srch(field, p->substring ? p->substring : "", pgm);
5900 else{ /* else use ANDs */
5901 /* ( A or B or C ) <=> ! ( !A and !B and !C ) */
5903 /* first, NOT of the whole thing */
5904 notpgm = next_not(pgm);
5906 /* then the not list is ANDed together */
5907 for(p = pattern; p; p = p->next){
5908 tpgm = next_not(notpgm);
5909 set_srch(field, p->substring ? p->substring : "", tpgm);
5916 void
5917 set_srch(char *field, char *value, SEARCHPGM *pgm)
5919 char *decoded;
5920 STRINGLIST **list;
5922 if(!(field && value && pgm))
5923 return;
5925 if(!strucmp(field, "subject"))
5926 list = &pgm->subject;
5927 else if(!strucmp(field, "from"))
5928 list = &pgm->from;
5929 else if(!strucmp(field, "to"))
5930 list = &pgm->to;
5931 else if(!strucmp(field, "cc"))
5932 list = &pgm->cc;
5933 else if(!strucmp(field, "sender"))
5934 list = &pgm->sender;
5935 else if(!strucmp(field, "reply-to"))
5936 list = &pgm->reply_to;
5937 else if(!strucmp(field, "in-reply-to"))
5938 list = &pgm->in_reply_to;
5939 else if(!strucmp(field, "message-id"))
5940 list = &pgm->message_id;
5941 else if(!strucmp(field, "newsgroups"))
5942 list = &pgm->newsgroups;
5943 else if(!strucmp(field, "followup-to"))
5944 list = &pgm->followup_to;
5945 else if(!strucmp(field, "alltext"))
5946 list = &pgm->text;
5947 else if(!strucmp(field, "bodytext"))
5948 list = &pgm->body;
5949 else if(!strucmp(field, "keyword"))
5950 list = &pgm->keyword;
5951 else{
5952 set_srch_hdr(field, value, pgm);
5953 return;
5956 if(!list)
5957 return;
5959 *list = mail_newstringlist();
5960 decoded = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, value);
5962 (*list)->text.data = (unsigned char *)cpystr(decoded);
5963 (*list)->text.size = strlen(decoded);
5967 void
5968 set_srch_hdr(char *field, char *value, SEARCHPGM *pgm)
5970 char *decoded;
5971 SEARCHHEADER **hdr;
5973 if(!(field && value && pgm))
5974 return;
5976 hdr = &pgm->header;
5977 if(!hdr)
5978 return;
5980 decoded = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
5981 SIZEOF_20KBUF, value);
5982 while(*hdr && (*hdr)->next)
5983 *hdr = (*hdr)->next;
5985 if(*hdr)
5986 (*hdr)->next = mail_newsearchheader(field, decoded);
5987 else
5988 *hdr = mail_newsearchheader(field, decoded);
5992 void
5993 set_search_by_age(INTVL_S *age, SEARCHPGM *pgm, int age_uses_sentdate)
5995 time_t now, comparetime;
5996 struct tm *tm;
5997 unsigned short i;
5999 if(!(age && pgm))
6000 return;
6002 now = time(0);
6004 if(age->imin >= 0L && age->imin == age->imax){
6005 comparetime = now;
6006 comparetime -= (age->imin * 86400L);
6007 tm = localtime(&comparetime);
6008 if(tm && tm->tm_year >= 70){
6009 i = mail_shortdate(tm->tm_year - 70, tm->tm_mon + 1,
6010 tm->tm_mday);
6011 if(age_uses_sentdate)
6012 pgm->senton = i;
6013 else
6014 pgm->on = i;
6017 else{
6019 * The 20000's are just protecting against overflows.
6020 * That's back past the start of email time, anyway.
6022 if(age->imin > 0L && age->imin < 20000L){
6023 comparetime = now;
6024 comparetime -= ((age->imin - 1L) * 86400L);
6025 tm = localtime(&comparetime);
6026 if(tm && tm->tm_year >= 70){
6027 i = mail_shortdate(tm->tm_year - 70, tm->tm_mon + 1,
6028 tm->tm_mday);
6029 if(age_uses_sentdate)
6030 pgm->sentbefore = i;
6031 else
6032 pgm->before = i;
6036 if(age->imax >= 0L && age->imax < 20000L){
6037 comparetime = now;
6038 comparetime -= (age->imax * 86400L);
6039 tm = localtime(&comparetime);
6040 if(tm && tm->tm_year >= 70){
6041 i = mail_shortdate(tm->tm_year - 70, tm->tm_mon + 1,
6042 tm->tm_mday);
6043 if(age_uses_sentdate)
6044 pgm->sentsince = i;
6045 else
6046 pgm->since = i;
6053 void
6054 set_search_by_size(INTVL_S *size, SEARCHPGM *pgm)
6056 if(!(size && pgm))
6057 return;
6060 * INTVL_S intervals include the endpoints, pgm larger and smaller
6061 * do not include the endpoints.
6063 if(size->imin != INTVL_UNDEF && size->imin > 0L)
6064 pgm->larger = size->imin - 1L;
6066 if(size->imax != INTVL_UNDEF && size->imax >= 0L && size->imax != INTVL_INF)
6067 pgm->smaller = size->imax + 1L;
6071 static char *extra_hdrs;
6074 * Run through the patterns and note which headers we'll need to ask for
6075 * which aren't normally asked for and so won't be cached.
6077 void
6078 calc_extra_hdrs(void)
6080 PAT_S *pat = NULL;
6081 int alloced_size;
6082 long type = (ROLE_INCOL | ROLE_SCORE);
6083 ARBHDR_S *a;
6084 PAT_STATE pstate;
6085 char *q, *p = NULL, *hdrs[MLCMD_COUNT + 1], **pp;
6086 INDEX_COL_S *cdesc;
6087 #define INITIALSIZE 1000
6089 q = (char *)fs_get((INITIALSIZE+1) * sizeof(char));
6090 q[0] = '\0';
6091 alloced_size = INITIALSIZE;
6092 p = q;
6095 * *ALWAYS* make sure Resent-To is in the set of
6096 * extra headers getting fetched.
6098 * This is because we *will* reference it when we're
6099 * building header lines and thus want it fetched with
6100 * the standard envelope data. Worse, in the IMAP case
6101 * we're called back from c-client with the envelope data
6102 * so we can format and display the index lines as they
6103 * arrive, so we have to ensure the resent-to field
6104 * is in the cache so we don't reenter c-client
6105 * to look for it from the callback. Yeouch.
6107 add_eh(&q, &p, "resent-to", &alloced_size);
6108 add_eh(&q, &p, "resent-date", &alloced_size);
6109 add_eh(&q, &p, "resent-from", &alloced_size);
6110 add_eh(&q, &p, "resent-cc", &alloced_size);
6111 add_eh(&q, &p, "resent-subject", &alloced_size);
6114 * Sniff at viewer-hdrs too so we can include them
6115 * if there are any...
6117 for(pp = ps_global->VAR_VIEW_HEADERS; pp && *pp; pp++)
6118 if(non_eh(*pp))
6119 add_eh(&q, &p, *pp, &alloced_size);
6122 * Be sure to ask for List management headers too
6123 * since we'll offer their use in the message view
6125 for(pp = rfc2369_hdrs(hdrs); *pp; pp++)
6126 add_eh(&q, &p, *pp, &alloced_size);
6128 if(nonempty_patterns(type, &pstate))
6129 for(pat = first_pattern(&pstate);
6130 pat;
6131 pat = next_pattern(&pstate)){
6133 * This section wouldn't be necessary if sender was retreived
6134 * from the envelope. But if not, we do need to add it.
6136 if(pat->patgrp && pat->patgrp->sender)
6137 add_eh(&q, &p, "sender", &alloced_size);
6139 if(pat->patgrp && pat->patgrp->arbhdr)
6140 for(a = pat->patgrp->arbhdr; a; a = a->next)
6141 if(a->field && a->field[0] && a->p && non_eh(a->field))
6142 add_eh(&q, &p, a->field, &alloced_size);
6146 * Check for use of HEADER or X-Priority in index-format.
6148 for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing; cdesc++){
6149 if(cdesc->ctype == iHeader && cdesc->hdrtok && cdesc->hdrtok->hdrname
6150 && cdesc->hdrtok->hdrname[0] && non_eh(cdesc->hdrtok->hdrname))
6151 add_eh(&q, &p, cdesc->hdrtok->hdrname, &alloced_size);
6152 else if(cdesc->ctype == iPrio
6153 || cdesc->ctype == iPrioAlpha
6154 || cdesc->ctype == iPrioBang)
6155 add_eh(&q, &p, PRIORITYNAME, &alloced_size);
6159 * Check for use of scorevalhdrtok in scoring patterns.
6161 type = ROLE_SCORE;
6162 if(nonempty_patterns(type, &pstate))
6163 for(pat = first_pattern(&pstate);
6164 pat;
6165 pat = next_pattern(&pstate)){
6167 * This section wouldn't be necessary if sender was retreived
6168 * from the envelope. But if not, we do need to add it.
6170 if(pat->action && pat->action->scorevalhdrtok
6171 && pat->action->scorevalhdrtok->hdrname
6172 && pat->action->scorevalhdrtok->hdrname[0]
6173 && non_eh(pat->action->scorevalhdrtok->hdrname))
6174 add_eh(&q, &p, pat->action->scorevalhdrtok->hdrname, &alloced_size);
6177 set_extra_hdrs(q);
6178 if(q)
6179 fs_give((void **)&q);
6184 non_eh(char *field)
6186 char **t;
6187 static char *existing[] = {"subject", "from", "to", "cc", "sender",
6188 "reply-to", "in-reply-to", "message-id",
6189 "path", "newsgroups", "followup-to",
6190 "references", NULL};
6193 * If it is one of these, we should already have it
6194 * from the envelope or from the extra headers c-client
6195 * already adds to the list (hdrheader and hdrtrailer
6196 * in imap4r1.c, Aug 99, slh).
6198 for(t = existing; *t; t++)
6199 if(!strucmp(field, *t))
6200 return(FALSE);
6202 return(TRUE);
6207 * Add field to extra headers string if not already there.
6209 void
6210 add_eh(char **start, char **ptr, char *field, int *asize)
6212 char *s;
6214 /* already there? */
6215 for(s = *start; (s = srchstr(s, field)) != NULL; s++)
6216 if(s[strlen(field)] == SPACE || s[strlen(field)] == '\0')
6217 return;
6219 /* enough space for it? */
6220 while(strlen(field) + (*ptr - *start) + 1 > *asize){
6221 (*asize) *= 2;
6222 fs_resize((void **)start, (*asize)+1);
6223 *ptr = *start + strlen(*start);
6226 if(*ptr > *start)
6227 sstrncpy(ptr, " ", *asize-(*ptr - *start));
6229 sstrncpy(ptr, field, *asize-(*ptr - *start));
6231 (*start)[*asize] = '\0';
6235 void
6236 set_extra_hdrs(char *hdrs)
6238 free_extra_hdrs();
6239 if(hdrs && *hdrs)
6240 extra_hdrs = cpystr(hdrs);
6244 char *
6245 get_extra_hdrs(void)
6247 return(extra_hdrs);
6251 void
6252 free_extra_hdrs(void)
6254 if(extra_hdrs)
6255 fs_give((void **)&extra_hdrs);
6260 is_ascii_string(char *str)
6262 if(!str)
6263 return(0);
6265 while(*str && isascii(*str))
6266 str++;
6268 return(*str == '\0');
6272 void
6273 free_patline(PAT_LINE_S **patline)
6275 if(patline && *patline){
6276 free_patline(&(*patline)->next);
6277 if((*patline)->filename)
6278 fs_give((void **)&(*patline)->filename);
6279 if((*patline)->filepath)
6280 fs_give((void **)&(*patline)->filepath);
6281 free_pat(&(*patline)->first);
6282 fs_give((void **)patline);
6287 void
6288 free_pat(PAT_S **pat)
6290 if(pat && *pat){
6291 free_pat(&(*pat)->next);
6292 free_patgrp(&(*pat)->patgrp);
6293 free_action(&(*pat)->action);
6294 if((*pat)->raw)
6295 fs_give((void **)&(*pat)->raw);
6297 fs_give((void **)pat);
6302 void
6303 free_patgrp(PATGRP_S **patgrp)
6305 if(patgrp && *patgrp){
6306 if((*patgrp)->nick)
6307 fs_give((void **) &(*patgrp)->nick);
6309 if((*patgrp)->comment)
6310 fs_give((void **) &(*patgrp)->comment);
6312 if((*patgrp)->category_cmd)
6313 free_list_array(&(*patgrp)->category_cmd);
6315 if((*patgrp)->charsets_list)
6316 free_strlist(&(*patgrp)->charsets_list);
6318 free_pattern(&(*patgrp)->to);
6319 free_pattern(&(*patgrp)->cc);
6320 free_pattern(&(*patgrp)->recip);
6321 free_pattern(&(*patgrp)->partic);
6322 free_pattern(&(*patgrp)->from);
6323 free_pattern(&(*patgrp)->sender);
6324 free_pattern(&(*patgrp)->news);
6325 free_pattern(&(*patgrp)->subj);
6326 free_pattern(&(*patgrp)->alltext);
6327 free_pattern(&(*patgrp)->bodytext);
6328 free_pattern(&(*patgrp)->keyword);
6329 free_pattern(&(*patgrp)->charsets);
6330 free_pattern(&(*patgrp)->folder);
6331 free_arbhdr(&(*patgrp)->arbhdr);
6332 free_intvl(&(*patgrp)->score);
6333 free_intvl(&(*patgrp)->age);
6334 fs_give((void **) patgrp);
6339 void
6340 free_pattern(PATTERN_S **pattern)
6342 if(pattern && *pattern){
6343 free_pattern(&(*pattern)->next);
6344 if((*pattern)->substring)
6345 fs_give((void **)&(*pattern)->substring);
6346 fs_give((void **)pattern);
6351 void
6352 free_arbhdr(ARBHDR_S **arbhdr)
6354 if(arbhdr && *arbhdr){
6355 free_arbhdr(&(*arbhdr)->next);
6356 if((*arbhdr)->field)
6357 fs_give((void **)&(*arbhdr)->field);
6358 free_pattern(&(*arbhdr)->p);
6359 fs_give((void **)arbhdr);
6364 void
6365 free_intvl(INTVL_S **intvl)
6367 if(intvl && *intvl){
6368 free_intvl(&(*intvl)->next);
6369 fs_give((void **) intvl);
6374 void
6375 free_action(ACTION_S **action)
6377 if(action && *action){
6378 if((*action)->from)
6379 mail_free_address(&(*action)->from);
6380 if((*action)->replyto)
6381 mail_free_address(&(*action)->replyto);
6382 if((*action)->fcc)
6383 fs_give((void **)&(*action)->fcc);
6384 if((*action)->litsig)
6385 fs_give((void **)&(*action)->litsig);
6386 if((*action)->sig)
6387 fs_give((void **)&(*action)->sig);
6388 if((*action)->template)
6389 fs_give((void **)&(*action)->template);
6390 if((*action)->scorevalhdrtok)
6391 free_hdrtok(&(*action)->scorevalhdrtok);
6392 if((*action)->cstm)
6393 free_list_array(&(*action)->cstm);
6394 if((*action)->smtp)
6395 free_list_array(&(*action)->smtp);
6396 if((*action)->nntp)
6397 free_list_array(&(*action)->nntp);
6398 if((*action)->nick)
6399 fs_give((void **)&(*action)->nick);
6400 if((*action)->inherit_nick)
6401 fs_give((void **)&(*action)->inherit_nick);
6402 if((*action)->incol)
6403 free_color_pair(&(*action)->incol);
6404 if((*action)->folder)
6405 free_pattern(&(*action)->folder);
6406 if((*action)->index_format)
6407 fs_give((void **)&(*action)->index_format);
6408 if((*action)->keyword_set)
6409 free_pattern(&(*action)->keyword_set);
6410 if((*action)->keyword_clr)
6411 free_pattern(&(*action)->keyword_clr);
6413 fs_give((void **)action);
6419 * Returns an allocated copy of the pat.
6421 * Args pat -- the source pat
6423 * Returns a copy of pat.
6425 PAT_S *
6426 copy_pat(PAT_S *pat)
6428 PAT_S *new_pat = NULL;
6430 if(pat){
6431 new_pat = (PAT_S *)fs_get(sizeof(*new_pat));
6432 memset((void *)new_pat, 0, sizeof(*new_pat));
6434 new_pat->patgrp = copy_patgrp(pat->patgrp);
6435 new_pat->action = copy_action(pat->action);
6438 return(new_pat);
6443 * Returns an allocated copy of the patgrp.
6445 * Args patgrp -- the source patgrp
6447 * Returns a copy of patgrp.
6449 PATGRP_S *
6450 copy_patgrp(PATGRP_S *patgrp)
6452 char *p;
6453 PATGRP_S *new_patgrp = NULL;
6455 if(patgrp){
6456 new_patgrp = (PATGRP_S *)fs_get(sizeof(*new_patgrp));
6457 memset((void *)new_patgrp, 0, sizeof(*new_patgrp));
6459 if(patgrp->nick)
6460 new_patgrp->nick = cpystr(patgrp->nick);
6462 if(patgrp->comment)
6463 new_patgrp->comment = cpystr(patgrp->comment);
6465 if(patgrp->to){
6466 p = pattern_to_string(patgrp->to);
6467 new_patgrp->to = string_to_pattern(p);
6468 fs_give((void **)&p);
6469 new_patgrp->to->not = patgrp->to->not;
6472 if(patgrp->from){
6473 p = pattern_to_string(patgrp->from);
6474 new_patgrp->from = string_to_pattern(p);
6475 fs_give((void **)&p);
6476 new_patgrp->from->not = patgrp->from->not;
6479 if(patgrp->sender){
6480 p = pattern_to_string(patgrp->sender);
6481 new_patgrp->sender = string_to_pattern(p);
6482 fs_give((void **)&p);
6483 new_patgrp->sender->not = patgrp->sender->not;
6486 if(patgrp->cc){
6487 p = pattern_to_string(patgrp->cc);
6488 new_patgrp->cc = string_to_pattern(p);
6489 fs_give((void **)&p);
6490 new_patgrp->cc->not = patgrp->cc->not;
6493 if(patgrp->recip){
6494 p = pattern_to_string(patgrp->recip);
6495 new_patgrp->recip = string_to_pattern(p);
6496 fs_give((void **)&p);
6497 new_patgrp->recip->not = patgrp->recip->not;
6500 if(patgrp->partic){
6501 p = pattern_to_string(patgrp->partic);
6502 new_patgrp->partic = string_to_pattern(p);
6503 fs_give((void **)&p);
6504 new_patgrp->partic->not = patgrp->partic->not;
6507 if(patgrp->news){
6508 p = pattern_to_string(patgrp->news);
6509 new_patgrp->news = string_to_pattern(p);
6510 fs_give((void **)&p);
6511 new_patgrp->news->not = patgrp->news->not;
6514 if(patgrp->subj){
6515 p = pattern_to_string(patgrp->subj);
6516 new_patgrp->subj = string_to_pattern(p);
6517 fs_give((void **)&p);
6518 new_patgrp->subj->not = patgrp->subj->not;
6521 if(patgrp->alltext){
6522 p = pattern_to_string(patgrp->alltext);
6523 new_patgrp->alltext = string_to_pattern(p);
6524 fs_give((void **)&p);
6525 new_patgrp->alltext->not = patgrp->alltext->not;
6528 if(patgrp->bodytext){
6529 p = pattern_to_string(patgrp->bodytext);
6530 new_patgrp->bodytext = string_to_pattern(p);
6531 fs_give((void **)&p);
6532 new_patgrp->bodytext->not = patgrp->bodytext->not;
6535 if(patgrp->keyword){
6536 p = pattern_to_string(patgrp->keyword);
6537 new_patgrp->keyword = string_to_pattern(p);
6538 fs_give((void **)&p);
6539 new_patgrp->keyword->not = patgrp->keyword->not;
6542 if(patgrp->charsets){
6543 p = pattern_to_string(patgrp->charsets);
6544 new_patgrp->charsets = string_to_pattern(p);
6545 fs_give((void **)&p);
6546 new_patgrp->charsets->not = patgrp->charsets->not;
6549 if(patgrp->charsets_list)
6550 new_patgrp->charsets_list = copy_strlist(patgrp->charsets_list);
6552 if(patgrp->arbhdr){
6553 ARBHDR_S *aa, *a, *new_a;
6555 aa = NULL;
6556 for(a = patgrp->arbhdr; a; a = a->next){
6557 new_a = (ARBHDR_S *)fs_get(sizeof(*new_a));
6558 memset((void *)new_a, 0, sizeof(*new_a));
6560 if(a->field)
6561 new_a->field = cpystr(a->field);
6563 if(a->p){
6564 p = pattern_to_string(a->p);
6565 new_a->p = string_to_pattern(p);
6566 fs_give((void **)&p);
6567 new_a->p->not = a->p->not;
6570 new_a->isemptyval = a->isemptyval;
6572 if(aa){
6573 aa->next = new_a;
6574 aa = aa->next;
6576 else{
6577 new_patgrp->arbhdr = new_a;
6578 aa = new_patgrp->arbhdr;
6583 new_patgrp->fldr_type = patgrp->fldr_type;
6585 if(patgrp->folder){
6586 p = pattern_to_string(patgrp->folder);
6587 new_patgrp->folder = string_to_pattern(p);
6588 fs_give((void **)&p);
6591 new_patgrp->inabook = patgrp->inabook;
6593 if(patgrp->abooks){
6594 p = pattern_to_string(patgrp->abooks);
6595 new_patgrp->abooks = string_to_pattern(p);
6596 fs_give((void **)&p);
6599 new_patgrp->do_score = patgrp->do_score;
6600 if(patgrp->score){
6601 INTVL_S *intvl, *iv, *new_iv;
6603 intvl = NULL;
6604 for(iv = patgrp->score; iv; iv = iv->next){
6605 new_iv = (INTVL_S *) fs_get(sizeof(*new_iv));
6606 memset((void *) new_iv, 0, sizeof(*new_iv));
6608 new_iv->imin = iv->imin;
6609 new_iv->imax = iv->imax;
6611 if(intvl){
6612 intvl->next = new_iv;
6613 intvl = intvl->next;
6615 else{
6616 new_patgrp->score = new_iv;
6617 intvl = new_patgrp->score;
6622 new_patgrp->do_age = patgrp->do_age;
6623 if(patgrp->age){
6624 INTVL_S *intvl, *iv, *new_iv;
6626 intvl = NULL;
6627 for(iv = patgrp->age; iv; iv = iv->next){
6628 new_iv = (INTVL_S *) fs_get(sizeof(*new_iv));
6629 memset((void *) new_iv, 0, sizeof(*new_iv));
6631 new_iv->imin = iv->imin;
6632 new_iv->imax = iv->imax;
6634 if(intvl){
6635 intvl->next = new_iv;
6636 intvl = intvl->next;
6638 else{
6639 new_patgrp->age = new_iv;
6640 intvl = new_patgrp->age;
6645 new_patgrp->age_uses_sentdate = patgrp->age_uses_sentdate;
6647 new_patgrp->do_size = patgrp->do_size;
6648 if(patgrp->size){
6649 INTVL_S *intvl, *iv, *new_iv;
6651 intvl = NULL;
6652 for(iv = patgrp->size; iv; iv = iv->next){
6653 new_iv = (INTVL_S *) fs_get(sizeof(*new_iv));
6654 memset((void *) new_iv, 0, sizeof(*new_iv));
6656 new_iv->imin = iv->imin;
6657 new_iv->imax = iv->imax;
6659 if(intvl){
6660 intvl->next = new_iv;
6661 intvl = intvl->next;
6663 else{
6664 new_patgrp->size = new_iv;
6665 intvl = new_patgrp->size;
6670 new_patgrp->stat_new = patgrp->stat_new;
6671 new_patgrp->stat_rec = patgrp->stat_rec;
6672 new_patgrp->stat_del = patgrp->stat_del;
6673 new_patgrp->stat_imp = patgrp->stat_imp;
6674 new_patgrp->stat_ans = patgrp->stat_ans;
6676 new_patgrp->stat_8bitsubj = patgrp->stat_8bitsubj;
6677 new_patgrp->stat_bom = patgrp->stat_bom;
6678 new_patgrp->stat_boy = patgrp->stat_boy;
6680 new_patgrp->do_cat = patgrp->do_cat;
6681 if(patgrp->cat){
6682 INTVL_S *intvl, *iv, *new_iv;
6684 intvl = NULL;
6685 for(iv = patgrp->cat; iv; iv = iv->next){
6686 new_iv = (INTVL_S *) fs_get(sizeof(*new_iv));
6687 memset((void *) new_iv, 0, sizeof(*new_iv));
6689 new_iv->imin = iv->imin;
6690 new_iv->imax = iv->imax;
6692 if(intvl){
6693 intvl->next = new_iv;
6694 intvl = intvl->next;
6696 else{
6697 new_patgrp->cat = new_iv;
6698 intvl = new_patgrp->cat;
6703 if(patgrp->category_cmd)
6704 new_patgrp->category_cmd = copy_list_array(patgrp->category_cmd);
6707 return(new_patgrp);
6712 * Returns an allocated copy of the action.
6714 * Args action -- the source action
6716 * Returns a copy of action.
6718 ACTION_S *
6719 copy_action(ACTION_S *action)
6721 ACTION_S *newaction = NULL;
6722 char *p;
6724 if(action){
6725 newaction = (ACTION_S *)fs_get(sizeof(*newaction));
6726 memset((void *)newaction, 0, sizeof(*newaction));
6728 newaction->is_a_role = action->is_a_role;
6729 newaction->is_a_incol = action->is_a_incol;
6730 newaction->is_a_score = action->is_a_score;
6731 newaction->is_a_filter = action->is_a_filter;
6732 newaction->is_a_other = action->is_a_other;
6733 newaction->is_a_srch = action->is_a_srch;
6734 newaction->repl_type = action->repl_type;
6735 newaction->forw_type = action->forw_type;
6736 newaction->comp_type = action->comp_type;
6737 newaction->scoreval = action->scoreval;
6738 newaction->kill = action->kill;
6739 newaction->state_setting_bits = action->state_setting_bits;
6740 newaction->move_only_if_not_deleted = action->move_only_if_not_deleted;
6741 newaction->non_terminating = action->non_terminating;
6742 newaction->sort_is_set = action->sort_is_set;
6743 newaction->sortorder = action->sortorder;
6744 newaction->revsort = action->revsort;
6745 newaction->startup_rule = action->startup_rule;
6747 if(action->from)
6748 newaction->from = copyaddrlist(action->from);
6749 if(action->replyto)
6750 newaction->replyto = copyaddrlist(action->replyto);
6751 if(action->cstm)
6752 newaction->cstm = copy_list_array(action->cstm);
6753 if(action->smtp)
6754 newaction->smtp = copy_list_array(action->smtp);
6755 if(action->nntp)
6756 newaction->nntp = copy_list_array(action->nntp);
6757 if(action->fcc)
6758 newaction->fcc = cpystr(action->fcc);
6759 if(action->litsig)
6760 newaction->litsig = cpystr(action->litsig);
6761 if(action->sig)
6762 newaction->sig = cpystr(action->sig);
6763 if(action->template)
6764 newaction->template = cpystr(action->template);
6765 if(action->nick)
6766 newaction->nick = cpystr(action->nick);
6767 if(action->inherit_nick)
6768 newaction->inherit_nick = cpystr(action->inherit_nick);
6769 if(action->incol)
6770 newaction->incol = new_color_pair(action->incol->fg,
6771 action->incol->bg);
6772 if(action->scorevalhdrtok){
6773 newaction->scorevalhdrtok = new_hdrtok(action->scorevalhdrtok->hdrname);
6774 if(action->scorevalhdrtok && action->scorevalhdrtok->fieldseps){
6775 if(newaction->scorevalhdrtok->fieldseps)
6776 fs_give((void **) &newaction->scorevalhdrtok->fieldseps);
6778 newaction->scorevalhdrtok->fieldseps = cpystr(action->scorevalhdrtok->fieldseps);
6782 if(action->folder){
6783 p = pattern_to_string(action->folder);
6784 newaction->folder = string_to_pattern(p);
6785 fs_give((void **) &p);
6788 if(action->keyword_set){
6789 p = pattern_to_string(action->keyword_set);
6790 newaction->keyword_set = string_to_pattern(p);
6791 fs_give((void **) &p);
6794 if(action->keyword_clr){
6795 p = pattern_to_string(action->keyword_clr);
6796 newaction->keyword_clr = string_to_pattern(p);
6797 fs_give((void **) &p);
6800 if(action->index_format)
6801 newaction->index_format = cpystr(action->index_format);
6804 return(newaction);
6809 * Given a role, return an allocated role. If this role inherits from
6810 * another role, then do the correct inheriting so that the result is
6811 * the role we want to use. The inheriting that is done is just the set
6812 * of set- actions. This is for role stuff, no inheriting happens for scores
6813 * or for colors.
6815 * Args role -- The source role
6817 * Returns a role.
6819 ACTION_S *
6820 combine_inherited_role(ACTION_S *role)
6822 PAT_STATE pstate;
6823 PAT_S *pat;
6826 * Protect against loops in the role inheritance.
6828 if(role && role->is_a_role && nonempty_patterns(ROLE_DO_ROLES, &pstate))
6829 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
6830 if(pat->action){
6831 if(pat->action == role)
6832 pat->action->been_here_before = 1;
6833 else
6834 pat->action->been_here_before = 0;
6837 return(combine_inherited_role_guts(role));
6841 ACTION_S *
6842 combine_inherited_role_guts(ACTION_S *role)
6844 ACTION_S *newrole = NULL, *inherit_role = NULL;
6845 PAT_STATE pstate;
6847 if(role && role->is_a_role){
6848 newrole = (ACTION_S *)fs_get(sizeof(*newrole));
6849 memset((void *)newrole, 0, sizeof(*newrole));
6851 newrole->repl_type = role->repl_type;
6852 newrole->forw_type = role->forw_type;
6853 newrole->comp_type = role->comp_type;
6854 newrole->is_a_role = role->is_a_role;
6856 if(role->inherit_nick && role->inherit_nick[0] &&
6857 nonempty_patterns(ROLE_DO_ROLES, &pstate)){
6858 PAT_S *pat;
6860 /* find the inherit_nick pattern */
6861 for(pat = first_pattern(&pstate);
6862 pat;
6863 pat = next_pattern(&pstate)){
6864 if(pat->patgrp &&
6865 pat->patgrp->nick &&
6866 !strucmp(role->inherit_nick, pat->patgrp->nick)){
6867 /* found it, if it has a role, use it */
6868 if(!pat->action->been_here_before){
6869 pat->action->been_here_before = 1;
6870 inherit_role = pat->action;
6873 break;
6878 * inherit_role might inherit further from other roles.
6879 * In any case, we copy it so that we'll consistently have
6880 * an allocated copy.
6882 if(inherit_role){
6883 if(inherit_role->inherit_nick && inherit_role->inherit_nick[0])
6884 inherit_role = combine_inherited_role_guts(inherit_role);
6885 else
6886 inherit_role = copy_action(inherit_role);
6890 if(role->from)
6891 newrole->from = copyaddrlist(role->from);
6892 else if(inherit_role && inherit_role->from)
6893 newrole->from = copyaddrlist(inherit_role->from);
6895 if(role->replyto)
6896 newrole->replyto = copyaddrlist(role->replyto);
6897 else if(inherit_role && inherit_role->replyto)
6898 newrole->replyto = copyaddrlist(inherit_role->replyto);
6900 if(role->fcc)
6901 newrole->fcc = cpystr(role->fcc);
6902 else if(inherit_role && inherit_role->fcc)
6903 newrole->fcc = cpystr(inherit_role->fcc);
6905 if(role->litsig)
6906 newrole->litsig = cpystr(role->litsig);
6907 else if(inherit_role && inherit_role->litsig)
6908 newrole->litsig = cpystr(inherit_role->litsig);
6910 if(role->sig)
6911 newrole->sig = cpystr(role->sig);
6912 else if(inherit_role && inherit_role->sig)
6913 newrole->sig = cpystr(inherit_role->sig);
6915 if(role->template)
6916 newrole->template = cpystr(role->template);
6917 else if(inherit_role && inherit_role->template)
6918 newrole->template = cpystr(inherit_role->template);
6920 if(role->cstm)
6921 newrole->cstm = copy_list_array(role->cstm);
6922 else if(inherit_role && inherit_role->cstm)
6923 newrole->cstm = copy_list_array(inherit_role->cstm);
6925 if(role->smtp)
6926 newrole->smtp = copy_list_array(role->smtp);
6927 else if(inherit_role && inherit_role->smtp)
6928 newrole->smtp = copy_list_array(inherit_role->smtp);
6930 if(role->nntp)
6931 newrole->nntp = copy_list_array(role->nntp);
6932 else if(inherit_role && inherit_role->nntp)
6933 newrole->nntp = copy_list_array(inherit_role->nntp);
6935 if(role->nick)
6936 newrole->nick = cpystr(role->nick);
6938 if(inherit_role)
6939 free_action(&inherit_role);
6942 return(newrole);
6946 void
6947 mail_expunge_prefilter(MAILSTREAM *stream, int flags)
6949 int sfdo_state = 0, /* Some Filter Depends On or Sets State */
6950 sfdo_scores = 0, /* Some Filter Depends On Scores */
6951 ssdo_state = 0; /* Some Score Depends On State */
6953 if(!stream || !sp_flagged(stream, SP_LOCKED))
6954 return;
6957 * An Expunge causes a re-examination of the filters to
6958 * see if any state changes have caused new matches.
6961 sfdo_scores = (scores_are_used(SCOREUSE_GET) & SCOREUSE_FILTERS);
6962 if(sfdo_scores)
6963 ssdo_state = (scores_are_used(SCOREUSE_GET) & SCOREUSE_STATEDEP);
6965 if(!(sfdo_scores && ssdo_state))
6966 sfdo_state = some_filter_depends_on_active_state();
6969 if(sfdo_state || (sfdo_scores && ssdo_state)){
6970 if(sfdo_scores && ssdo_state)
6971 clear_folder_scores(stream);
6973 reprocess_filter_patterns(stream, sp_msgmap(stream),
6974 (flags & MI_CLOSING) |
6975 MI_REFILTERING | MI_STATECHGONLY);
6980 /*----------------------------------------------------------------------
6981 Dispatch messages matching FILTER patterns.
6983 Args:
6984 stream -- mail stream serving messages
6985 msgmap -- sequence to msgno mapping table
6986 recent -- number of recent messages to check (but really only its
6987 nonzeroness is used)
6989 When we're done, any filtered messages are filtered and the message
6990 mapping table has any filtered messages removed.
6991 ---*/
6992 void
6993 process_filter_patterns(MAILSTREAM *stream, MSGNO_S *msgmap, long int recent)
6995 long i, n, raw;
6996 imapuid_t uid;
6997 int we_cancel = 0, any_msgs = 0, any_to_filter = 0;
6998 int exbits, nt = 0, pending_actions = 0, for_debugging = 0;
6999 int cleared_index_cache = 0;
7000 long rflags = ROLE_DO_FILTER;
7001 char *nick = NULL;
7002 char busymsg[80];
7003 MSGNO_S *tmpmap = NULL;
7004 MESSAGECACHE *mc;
7005 PAT_S *pat, *nextpat = NULL;
7006 SEARCHPGM *pgm = NULL;
7007 SEARCHSET *srchset = NULL;
7008 long flags = (SE_NOPREFETCH|SE_FREE);
7009 PAT_STATE pstate;
7011 dprint((5, "process_filter_patterns(stream=%s, recent=%ld)\n",
7012 !stream ? "<null>" :
7013 sp_flagged(stream, SP_INBOX) ? "inbox" :
7014 stream->original_mailbox ? stream->original_mailbox :
7015 stream->mailbox ? stream->mailbox :
7016 "?",
7017 recent));
7019 if(!msgmap || !stream)
7020 return;
7022 if(!recent)
7023 sp_set_flags(stream, sp_flags(stream) | SP_FILTERED);
7025 while(stream && stream->nmsgs && nonempty_patterns(rflags, &pstate)){
7027 for_debugging++;
7028 pending_actions = 0;
7029 nextpat = NULL;
7031 uid = mail_uid(stream, stream->nmsgs);
7034 * Some of the search stuff won't work on old servers so we
7035 * get the data and search locally. Big performance hit.
7037 if(is_imap_stream(stream) && !modern_imap_stream(stream))
7038 flags |= SE_NOSERVER;
7041 * ignore all previously filtered messages
7042 * and, if requested, anything not a recent
7043 * arrival...
7045 * Here we're using spare6 (MN_STMP), meaning we'll only
7046 * search the ones with spare6 marked, new messages coming
7047 * in will not be considered. There used to be orig_nmsgs,
7048 * which kept track of this, but if a message gets expunged,
7049 * then a new message could be lower than orig_nmsgs.
7051 for(i = 1; i <= stream->nmsgs; i++)
7052 if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){
7053 if(exbits & MSG_EX_FILTERED){
7054 if((mc = mail_elt(stream, i)) != NULL)
7055 mc->spare6 = 0;
7057 else if(!recent || !(exbits & MSG_EX_TESTED)){
7058 if((mc = mail_elt(stream, i)) != NULL)
7059 mc->spare6 = 1;
7061 any_to_filter++;
7063 else if((mc = mail_elt(stream, i)) != NULL)
7064 mc->spare6 = 0;
7066 else{
7067 if((mc = mail_elt(stream, i)) != NULL)
7068 mc->spare6 = !recent;
7070 any_to_filter += !recent;
7073 if(!any_to_filter){
7074 dprint((5, "No messages need filtering\n"));
7077 /* Then start searching */
7078 for(pat = first_pattern(&pstate); any_to_filter && pat; pat = nextpat){
7079 nextpat = next_pattern(&pstate);
7080 dprint((5,
7081 "Trying filter \"%s\"\n",
7082 (pat->patgrp && pat->patgrp->nick)
7083 ? pat->patgrp->nick : "?"));
7084 if(pat->patgrp && !pat->patgrp->bogus
7085 && pat->action && !pat->action->bogus
7086 && !trivial_patgrp(pat->patgrp)
7087 && match_pattern_folder(pat->patgrp, stream)
7088 && !match_pattern_folder_specific(pat->action->folder,
7089 stream, FOR_FILTER)){
7092 * We could just keep track of spare6 accurately when
7093 * we change the msgno_exceptions flags, but...
7095 for(i = 1; i <= stream->nmsgs; i++){
7096 if((mc=mail_elt(stream, i)) && mc->spare6){
7097 if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){
7098 if(exbits & MSG_EX_FILTERED)
7099 mc->sequence = 0;
7100 else if(!recent || !(exbits & MSG_EX_TESTED))
7101 mc->sequence = 1;
7102 else
7103 mc->sequence = 0;
7105 else
7106 mc->sequence = !recent;
7108 else
7109 mc->sequence = 0;
7112 if(!(srchset = build_searchset(stream))){
7113 dprint((5, "Empty searchset\n"));
7114 continue; /* nothing to search, move on */
7117 #ifdef DEBUG
7118 {SEARCHSET *s;
7119 dprint((5, "searchset="));
7120 for(s = srchset; s; s = s->next){
7121 if(s->first == s->last || s->last == 0L){
7122 dprint((5, " %ld", s->first));
7124 else{
7125 dprint((5, " %ld-%ld", s->first, s->last));
7128 dprint((5, "\n"));
7130 #endif
7131 nick = (pat && pat->patgrp && pat->patgrp->nick
7132 && pat->patgrp->nick[0]) ? pat->patgrp->nick : NULL;
7133 snprintf(busymsg, sizeof(busymsg), _("Processing filter \"%s\""),
7134 nick ? nick : "?");
7137 * The strange last argument is so that the busy message
7138 * won't come out until after a second if the user sets
7139 * the feature to quell "filtering done". That's because
7140 * they are presumably interested in the filtering actions
7141 * themselves more than what is happening, so they'd
7142 * rather see the action messages instead of the processing
7143 * message. That's my theory anyway.
7145 if(F_OFF(F_QUELL_FILTER_MSGS, ps_global))
7146 any_msgs = we_cancel = busy_cue(busymsg, NULL,
7147 F_ON(F_QUELL_FILTER_DONE_MSG, ps_global)
7148 ? 1 : 0);
7150 if(pat->patgrp->stat_bom != PAT_STAT_EITHER){
7151 if(pat->patgrp->stat_bom == PAT_STAT_YES){
7152 if(!ps_global->beginning_of_month){
7153 dprint((5,
7154 "Filter %s wants beginning of month and it isn't bom\n",
7155 nick ? nick : "?"));
7156 continue;
7159 else if(pat->patgrp->stat_bom == PAT_STAT_NO){
7160 if(ps_global->beginning_of_month){
7161 dprint((5,
7162 "Filter %s does not want beginning of month and it is bom\n",
7163 nick ? nick : "?"));
7164 continue;
7169 if(pat->patgrp->stat_boy != PAT_STAT_EITHER){
7170 if(pat->patgrp->stat_boy == PAT_STAT_YES){
7171 if(!ps_global->beginning_of_year){
7172 dprint((5,
7173 "Filter %s wants beginning of year and it isn't boy\n",
7174 nick ? nick : "?"));
7175 continue;
7178 else if(pat->patgrp->stat_boy == PAT_STAT_NO){
7179 if(ps_global->beginning_of_year){
7180 dprint((5,
7181 "Filter %s does not want beginning of year and it is boy\n",
7182 nick ? nick : "?"));
7183 continue;
7188 pgm = match_pattern_srchpgm(pat->patgrp, stream, srchset);
7190 pine_mail_search_full(stream, "UTF-8", pgm, flags);
7192 /* check scores */
7193 if(scores_are_used(SCOREUSE_GET) & SCOREUSE_FILTERS &&
7194 pat->patgrp->do_score){
7195 SEARCHSET *s, *ss;
7198 * Build a searchset so we can get all the scores we
7199 * need and only the scores we need efficiently.
7202 for(i = 1; i <= stream->nmsgs; i++)
7203 if((mc = mail_elt(stream, i)) != NULL)
7204 mc->sequence = 0;
7206 for(s = srchset; s; s = s->next)
7207 for(i = s->first; i <= s->last; i++)
7208 if(i > 0L && stream && i <= stream->nmsgs
7209 && (mc=mail_elt(stream, i)) && mc->searched &&
7210 get_msg_score(stream, i) == SCORE_UNDEF)
7211 mc->sequence = 1;
7213 if((ss = build_searchset(stream)) != NULL){
7214 (void)calculate_some_scores(stream, ss, 0);
7215 mail_free_searchset(&ss);
7219 * Now check the patterns which have matched so far
7220 * to see if their score is in the score interval.
7222 for(s = srchset; s; s = s->next)
7223 for(i = s->first; i <= s->last; i++)
7224 if(i > 0L && stream && i <= stream->nmsgs
7225 && (mc=mail_elt(stream, i)) && mc->searched){
7226 long score;
7228 score = get_msg_score(stream, i);
7231 * If the score is outside all of the intervals,
7232 * turn off the searched bit.
7233 * So that means we check each interval and if
7234 * it is inside any interval we stop and leave
7235 * the bit set. If it is outside we keep checking.
7237 if(score != SCORE_UNDEF){
7238 INTVL_S *iv;
7240 for(iv = pat->patgrp->score; iv; iv = iv->next)
7241 if(score >= iv->imin && score <= iv->imax)
7242 break;
7244 if(!iv)
7245 mc->searched = NIL;
7250 /* check for 8bit subject match or not */
7251 if(pat->patgrp->stat_8bitsubj != PAT_STAT_EITHER)
7252 find_8bitsubj_in_messages(stream, srchset,
7253 pat->patgrp->stat_8bitsubj, 0);
7255 /* if there are still matches, check for charset matches */
7256 if(pat->patgrp->charsets)
7257 find_charsets_in_messages(stream, srchset, pat->patgrp, 0);
7259 if(pat->patgrp->inabook != IAB_EITHER)
7260 address_in_abook(stream, srchset, pat->patgrp->inabook, pat->patgrp->abooks);
7262 /* Still matches? Run the categorization command on each msg. */
7263 if(pith_opt_filter_pattern_cmd)
7264 (*pith_opt_filter_pattern_cmd)(pat->patgrp->category_cmd, srchset, stream, pat->patgrp->cat_lim, pat->patgrp->cat);
7266 if(we_cancel){
7267 cancel_busy_cue(-1);
7268 we_cancel = 0;
7271 nt = pat->action->non_terminating;
7272 pending_actions = MAX(nt, pending_actions);
7275 * Change some state bits.
7276 * This used to only happen if kill was not set, but
7277 * it can be useful to Delete a message even if killing.
7278 * That way, it will show up in another pine that isn't
7279 * running the same filter as Deleted, so the user won't
7280 * bother looking at it. Hubert 2004-11-16
7282 if(pat->action->state_setting_bits
7283 || pat->action->keyword_set
7284 || pat->action->keyword_clr){
7285 tmpmap = NULL;
7286 mn_init(&tmpmap, stream->nmsgs);
7288 for(i = 1L, n = 0L; i <= stream->nmsgs; i++)
7289 if((mc = mail_elt(stream, i)) && mc->searched
7290 && !(msgno_exceptions(stream, i, "0", &exbits, FALSE)
7291 && (exbits & MSG_EX_FILTERED))){
7292 if(!n++){
7293 mn_set_cur(tmpmap, i);
7295 else{
7296 mn_add_cur(tmpmap, i);
7300 if(n){
7301 long flagbits;
7302 char **keywords_to_set = NULL,
7303 **keywords_to_clr = NULL;
7304 PATTERN_S *pp;
7305 int cnt;
7307 flagbits = pat->action->state_setting_bits;
7309 if(pat->action->keyword_set){
7310 for(cnt = 0, pp = pat->action->keyword_set;
7311 pp; pp = pp->next)
7312 cnt++;
7314 keywords_to_set = (char **) fs_get((cnt+1) *
7315 sizeof(*keywords_to_set));
7316 memset(keywords_to_set, 0,
7317 (cnt+1) * sizeof(*keywords_to_set));
7318 for(cnt = 0, pp = pat->action->keyword_set;
7319 pp; pp = pp->next){
7320 char *q;
7322 q = nick_to_keyword(pp->substring);
7323 if(q && q[0])
7324 keywords_to_set[cnt++] = cpystr(q);
7327 flagbits |= F_KEYWORD;
7330 if(pat->action->keyword_clr){
7331 for(cnt = 0, pp = pat->action->keyword_clr;
7332 pp; pp = pp->next)
7333 cnt++;
7335 keywords_to_clr = (char **) fs_get((cnt+1) *
7336 sizeof(*keywords_to_clr));
7337 memset(keywords_to_clr, 0,
7338 (cnt+1) * sizeof(*keywords_to_clr));
7339 for(cnt = 0, pp = pat->action->keyword_clr;
7340 pp; pp = pp->next){
7341 char *q;
7343 q = nick_to_keyword(pp->substring);
7344 if(q && q[0])
7345 keywords_to_clr[cnt++] = cpystr(q);
7348 flagbits |= F_UNKEYWORD;
7351 set_some_flags(stream, tmpmap, flagbits,
7352 keywords_to_set, keywords_to_clr, 1,
7353 nick);
7356 mn_give(&tmpmap);
7360 * The two halves of the if-else are almost the same and
7361 * could probably be combined cleverly. The if clause
7362 * is simply setting the MSG_EX_FILTERED bit, and leaving
7363 * n set to zero. The msgno_exclude is not done in this case.
7364 * The else clause excludes each message (because it is
7365 * either filtered into nothing or moved to folder). The
7366 * exclude messes with the msgmap and that changes max_msgno,
7367 * so the loop control is a little tricky.
7369 if(!(pat->action->kill || pat->action->folder)){
7370 n = 0L;
7371 for(i = 1L; i <= mn_get_total(msgmap); i++)
7372 if((raw = mn_m2raw(msgmap, i)) > 0L
7373 && stream && raw <= stream->nmsgs
7374 && (mc = mail_elt(stream, raw)) && mc->searched){
7375 dprint((5,
7376 "FILTER matching \"%s\": msg %ld%s\n",
7377 nick ? nick : "unnamed",
7378 raw, nt ? " (dont stop)" : ""));
7379 if(msgno_exceptions(stream, raw, "0", &exbits, FALSE))
7380 exbits |= (nt ? MSG_EX_FILTONCE : MSG_EX_FILTERED);
7381 else
7382 exbits = (nt ? MSG_EX_FILTONCE : MSG_EX_FILTERED);
7385 * If this matched an earlier non-terminating rule
7386 * we've been keeping track of that so that we can
7387 * turn it into a permanent match at the end.
7388 * However, now we've matched another rule that is
7389 * terminating so we don't have to worry about it
7390 * anymore. Turn off the flag.
7392 if(!nt && exbits & MSG_EX_FILTONCE)
7393 exbits ^= MSG_EX_FILTONCE;
7395 exbits &= ~MSG_EX_STATECHG;
7397 msgno_exceptions(stream, raw, "0", &exbits, TRUE);
7400 else{
7401 for(i = 1L, n = 0L; i <= mn_get_total(msgmap); )
7402 if((raw = mn_m2raw(msgmap, i))
7403 && raw > 0L && stream && raw <= stream->nmsgs
7404 && (mc = mail_elt(stream, raw)) && mc->searched){
7405 dprint((5,
7406 "FILTER matching \"%s\": msg %ld %s%s\n",
7407 nick ? nick : "unnamed",
7408 raw, pat->action->folder ? "filed" : "killed",
7409 nt ? " (dont stop)" : ""));
7410 if(nt)
7411 i++;
7412 else{
7413 if(!cleared_index_cache
7414 && stream == ps_global->mail_stream){
7415 cleared_index_cache = 1;
7416 clear_index_cache(stream, 0);
7419 msgno_exclude(stream, msgmap, i, 1);
7421 * If this message is new, decrement
7422 * new_mail_count. Previously, the caller would
7423 * do this by counting MN_EXCLUDE before and after,
7424 * but the results weren't accurate in the case
7425 * where new messages arrived while filtering,
7426 * or the filtered message could have gotten
7427 * expunged.
7429 if(msgno_exceptions(stream, raw, "0", &exbits,
7430 FALSE)
7431 && (exbits & MSG_EX_RECENT)){
7432 long l, ll;
7434 l = sp_new_mail_count(stream);
7435 ll = sp_recent_since_visited(stream);
7436 dprint((5, "New message being filtered, decrement new_mail_count: %ld -> %ld\n", l, l-1L));
7437 if(l > 0L)
7438 sp_set_new_mail_count(stream, l-1L);
7439 if(ll > 0L)
7440 sp_set_recent_since_visited(stream, ll-1L);
7444 if(msgno_exceptions(stream, raw, "0", &exbits, FALSE))
7445 exbits |= (nt ? MSG_EX_FILTONCE : MSG_EX_FILTERED);
7446 else
7447 exbits = (nt ? MSG_EX_FILTONCE : MSG_EX_FILTERED);
7449 /* set pending exclusion for later */
7450 if(nt)
7451 exbits |= MSG_EX_PEND_EXLD;
7454 * If this matched an earlier non-terminating rule
7455 * we've been keeping track of that so that we can
7456 * turn it into a permanent match at the end.
7457 * However, now we've matched another rule that is
7458 * terminating so we don't have to worry about it
7459 * anymore. Turn off the flags.
7461 if(!nt && exbits & MSG_EX_FILTONCE){
7462 exbits ^= MSG_EX_FILTONCE;
7464 /* we've already excluded it, too */
7465 if(exbits & MSG_EX_PEND_EXLD)
7466 exbits ^= MSG_EX_PEND_EXLD;
7469 exbits &= ~MSG_EX_STATECHG;
7471 msgno_exceptions(stream, raw, "0", &exbits, TRUE);
7472 n++;
7474 else
7475 i++;
7478 if(n && pat->action->folder){
7479 PATTERN_S *p;
7480 int err = 0;
7482 tmpmap = NULL;
7483 mn_init(&tmpmap, stream->nmsgs);
7486 * For everything matching msg that hasn't
7487 * already been saved somewhere, do it...
7489 for(i = 1L, n = 0L; i <= stream->nmsgs; i++)
7490 if((mc = mail_elt(stream, i)) && mc->searched
7491 && !(msgno_exceptions(stream, i, "0", &exbits, FALSE)
7492 && (exbits & MSG_EX_FILED))){
7493 if(!n++){
7494 mn_set_cur(tmpmap, i);
7496 else{
7497 mn_add_cur(tmpmap, i);
7502 * Remove already deleted messages from the tmp
7503 * message map.
7504 * There is a bug with this. If a filter moves a
7505 * message to another folder _and_ sets the deleted
7506 * status, then the setting of the deleted status
7507 * will already have happened above in set_some_flags.
7508 * So if the move_only_if_not_deleted bit is set that
7509 * message will never be moved. A workaround for the
7510 * user is to not set the move-only-if-not-deleted
7511 * option.
7513 if(n && pat->action->move_only_if_not_deleted){
7514 char *seq;
7515 MSGNO_S *tmpmap2 = NULL;
7516 long nn = 0L;
7517 MESSAGECACHE *mc;
7519 mn_init(&tmpmap2, stream->nmsgs);
7522 * First, make sure elts are valid for all the
7523 * interesting messages.
7525 if((seq = invalid_elt_sequence(stream, tmpmap)) != NULL){
7526 pine_mail_fetch_flags(stream, seq, NIL);
7527 fs_give((void **) &seq);
7530 for(i = mn_first_cur(tmpmap); i > 0L;
7531 i = mn_next_cur(tmpmap)){
7532 mc = ((raw = mn_m2raw(tmpmap, i)) > 0L
7533 && stream && raw <= stream->nmsgs)
7534 ? mail_elt(stream, raw) : NULL;
7535 if(mc && !mc->deleted){
7536 if(!nn++){
7537 mn_set_cur(tmpmap2, i);
7539 else{
7540 mn_add_cur(tmpmap2, i);
7545 mn_give(&tmpmap);
7546 tmpmap = tmpmap2;
7547 n = nn;
7550 if(n){
7551 for(p = pat->action->folder; p; p = p->next){
7552 int dval;
7553 int flags_for_save;
7554 char *processed_name;
7556 /* does this filter set delete bit? ... */
7557 convert_statebits_to_vals(pat->action->state_setting_bits, &dval, NULL, NULL, NULL);
7558 /* ... if so, tell save not to fix it before copy */
7559 flags_for_save = SV_FOR_FILT | SV_INBOXWOCNTXT |
7560 (nt ? 0 : SV_DELETE) |
7561 ((dval != ACT_STAT_SET) ? SV_FIX_DELS : 0);
7562 processed_name = detoken_src(p->substring,
7563 FOR_FILT, NULL,
7564 NULL, NULL, NULL);
7565 if(move_filtered_msgs(stream, tmpmap,
7566 (processed_name && *processed_name)
7567 ? processed_name : p->substring,
7568 flags_for_save, nick)){
7570 * If we filtered into the current
7571 * folder, chuck a ping down the
7572 * stream so the user can notice it
7573 * before the next new mail check...
7575 if(ps_global->mail_stream
7576 && ps_global->mail_stream != stream
7577 && match_pattern_folder_specific(
7578 pat->action->folder,
7579 ps_global->mail_stream,
7580 FOR_FILTER)){
7581 (void) pine_mail_ping(ps_global->mail_stream);
7584 else{
7585 err = 1;
7586 break;
7589 if(processed_name)
7590 fs_give((void **) &processed_name);
7593 if(!err)
7594 for(n = mn_first_cur(tmpmap);
7595 n > 0L;
7596 n = mn_next_cur(tmpmap)){
7598 if(msgno_exceptions(stream, mn_m2raw(tmpmap, n),
7599 "0", &exbits, FALSE))
7600 exbits |= (nt ? MSG_EX_FILEONCE : MSG_EX_FILED);
7601 else
7602 exbits = (nt ? MSG_EX_FILEONCE : MSG_EX_FILED);
7604 exbits &= ~MSG_EX_STATECHG;
7606 msgno_exceptions(stream, mn_m2raw(tmpmap, n),
7607 "0", &exbits, TRUE);
7611 mn_give(&tmpmap);
7614 mail_free_searchset(&srchset);
7618 * If this is the last rule,
7619 * we make sure we delete messages that we delayed deleting
7620 * in the save. We delayed so that the deletion wouldn't have
7621 * an effect on later rules. We convert any temporary
7622 * FILED (FILEONCE) and FILTERED (FILTONCE) flags
7623 * (which were set by an earlier non-terminating rule)
7624 * to permanent. We also exclude some messages from the view.
7626 if(pending_actions && !nextpat){
7628 pending_actions = 0;
7629 tmpmap = NULL;
7630 mn_init(&tmpmap, stream->nmsgs);
7632 for(i = 1L, n = 0L; i <= mn_get_total(msgmap); i++){
7634 raw = mn_m2raw(msgmap, i);
7635 if(msgno_exceptions(stream, raw, "0", &exbits, FALSE)){
7636 if(exbits & MSG_EX_FILEONCE){
7637 if(!n++){
7638 mn_set_cur(tmpmap, raw);
7640 else{
7641 mn_add_cur(tmpmap, raw);
7647 if(n)
7648 set_some_flags(stream, tmpmap, F_DEL, NULL, NULL, 0, NULL);
7650 mn_give(&tmpmap);
7652 for(i = 1L; i <= mn_get_total(msgmap); i++){
7653 raw = mn_m2raw(msgmap, i);
7654 if(msgno_exceptions(stream, raw, "0", &exbits, FALSE)){
7655 if(exbits & MSG_EX_PEND_EXLD){
7656 if(!cleared_index_cache
7657 && stream == ps_global->mail_stream){
7658 cleared_index_cache = 1;
7659 clear_index_cache(stream, 0);
7662 msgno_exclude(stream, msgmap, i, 1);
7663 if(msgno_exceptions(stream, raw, "0",
7664 &exbits, FALSE)
7665 && (exbits & MSG_EX_RECENT)){
7666 long l, ll;
7669 * If this message is new, decrement
7670 * new_mail_count. See the above
7671 * call to msgno_exclude.
7673 l = sp_new_mail_count(stream);
7674 ll = sp_recent_since_visited(stream);
7675 dprint((5, "New message being filtered. Decrement new_mail_count: %ld -> %ld\n", l, l-1L));
7676 if(l > 0L)
7677 sp_set_new_mail_count(stream, l - 1L);
7678 if(ll > 0L)
7679 sp_set_recent_since_visited(stream, ll - 1L);
7682 i--; /* to compensate for loop's i++ */
7685 /* get rid of temporary flags */
7686 if(exbits & (MSG_EX_FILTONCE | MSG_EX_FILEONCE |
7687 MSG_EX_PEND_EXLD)){
7688 if(exbits & MSG_EX_FILTONCE){
7689 /* convert to permament */
7690 exbits ^= MSG_EX_FILTONCE;
7691 exbits |= MSG_EX_FILTERED;
7694 /* convert to permament */
7695 if(exbits & MSG_EX_FILEONCE){
7696 exbits ^= MSG_EX_FILEONCE;
7697 exbits |= MSG_EX_FILED;
7700 if(exbits & MSG_EX_PEND_EXLD)
7701 exbits ^= MSG_EX_PEND_EXLD;
7703 exbits &= ~MSG_EX_STATECHG;
7705 msgno_exceptions(stream, raw, "0", &exbits,TRUE);
7712 /* New mail arrival means start over */
7713 if(mail_uid(stream, stream->nmsgs) == uid)
7714 break;
7715 /* else, go again */
7717 recent = 1; /* only check recent ones now */
7720 if(!recent){
7721 /* clear status change flags */
7722 for(i = 1; i <= stream->nmsgs; i++){
7723 if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){
7724 if(exbits & MSG_EX_STATECHG){
7725 exbits &= ~MSG_EX_STATECHG;
7726 msgno_exceptions(stream, i, "0", &exbits, TRUE);
7732 /* clear any private "recent" flags and add TESTED flag */
7733 for(i = 1; i <= stream->nmsgs; i++){
7734 if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){
7735 if(exbits & MSG_EX_RECENT
7736 || !(exbits & MSG_EX_TESTED)
7737 || (!recent && exbits & MSG_EX_STATECHG)){
7738 exbits &= ~MSG_EX_RECENT;
7739 exbits |= MSG_EX_TESTED;
7740 if(!recent)
7741 exbits &= ~MSG_EX_STATECHG;
7743 msgno_exceptions(stream, i, "0", &exbits, TRUE);
7746 else{
7747 exbits = MSG_EX_TESTED;
7748 msgno_exceptions(stream, i, "0", &exbits, TRUE);
7751 /* clear any stmp flags just in case */
7752 if((mc = mail_elt(stream, i)) != NULL)
7753 mc->spare6 = 0;
7756 msgmap->flagged_stmp = 0L;
7758 if(any_msgs && F_OFF(F_QUELL_FILTER_MSGS, ps_global)
7759 && F_OFF(F_QUELL_FILTER_DONE_MSG, ps_global)){
7760 q_status_message(SM_ORDER, 0, 1, _("filtering done"));
7761 display_message('x');
7767 * Re-check the filters for matches because a change of message state may
7768 * have changed the results.
7770 void
7771 reprocess_filter_patterns(MAILSTREAM *stream, MSGNO_S *msgmap, int flags)
7773 if(stream){
7774 long i;
7775 int exbits;
7777 if(msgno_include(stream, msgmap, flags)
7778 && stream == ps_global->mail_stream
7779 && !(flags & MI_CLOSING)){
7780 clear_index_cache(stream, 0);
7781 refresh_sort(stream, msgmap, SRT_NON);
7782 ps_global->mangled_header = 1;
7786 * Passing 1 in the last argument causes it to only look at the
7787 * messages we included above, which should be only the ones we
7788 * need to look at.
7790 process_filter_patterns(stream, msgmap,
7791 (flags & MI_STATECHGONLY) ? 1L : 0);
7793 /* clear status change flags */
7794 for(i = 1; i <= stream->nmsgs; i++){
7795 if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){
7796 if(exbits & MSG_EX_STATECHG){
7797 exbits &= ~MSG_EX_STATECHG;
7798 msgno_exceptions(stream, i, "0", &exbits, TRUE);
7807 * When killing or filtering we don't want to match by mistake. So if
7808 * a pattern has nothing set except the Current Folder Type (which is always
7809 * set to something) we'll consider it to be trivial and not a match.
7810 * match_pattern uses this to determine if there is a match, where it is
7811 * just triggered on the Current Folder Type.
7814 trivial_patgrp(PATGRP_S *patgrp)
7816 int ret = 1;
7818 if(patgrp){
7819 if(patgrp->subj || patgrp->cc || patgrp->from || patgrp->to ||
7820 patgrp->sender || patgrp->news || patgrp->recip || patgrp->partic ||
7821 patgrp->alltext || patgrp->bodytext)
7822 ret = 0;
7824 if(ret && patgrp->do_age)
7825 ret = 0;
7827 if(ret && patgrp->do_size)
7828 ret = 0;
7830 if(ret && patgrp->do_score)
7831 ret = 0;
7833 if(ret && patgrp->category_cmd && patgrp->category_cmd[0])
7834 ret = 0;
7836 if(ret && patgrp_depends_on_state(patgrp))
7837 ret = 0;
7839 if(ret && patgrp->stat_8bitsubj != PAT_STAT_EITHER)
7840 ret = 0;
7842 if(ret && patgrp->charsets)
7843 ret = 0;
7845 if(ret && patgrp->stat_bom != PAT_STAT_EITHER)
7846 ret = 0;
7848 if(ret && patgrp->stat_boy != PAT_STAT_EITHER)
7849 ret = 0;
7851 if(ret && patgrp->inabook != IAB_EITHER)
7852 ret = 0;
7854 if(ret && patgrp->arbhdr){
7855 ARBHDR_S *a;
7857 for(a = patgrp->arbhdr; a && ret; a = a->next)
7858 if(a->field && a->field[0] && a->p)
7859 ret = 0;
7863 return(ret);
7868 some_filter_depends_on_active_state(void)
7870 long rflags = ROLE_DO_FILTER;
7871 PAT_S *pat;
7872 PAT_STATE pstate;
7873 int ret = 0;
7875 if(nonempty_patterns(rflags, &pstate)){
7877 for(pat = first_pattern(&pstate);
7878 pat && !ret;
7879 pat = next_pattern(&pstate))
7880 if(patgrp_depends_on_active_state(pat->patgrp))
7881 ret++;
7884 return(ret);
7888 /*----------------------------------------------------------------------
7889 Move all messages with sequence bit lit to dstfldr
7891 Args: stream -- stream to use
7892 msgmap -- map of messages to be moved
7893 dstfldr -- folder to receive moved messages
7894 flags_for_save
7896 Returns: nonzero on success or on readonly stream
7897 ----*/
7899 move_filtered_msgs(MAILSTREAM *stream, MSGNO_S *msgmap, char *dstfldr,
7900 int flags_for_save, char *nick)
7902 long n;
7903 int we_cancel = 0, width;
7904 CONTEXT_S *save_context = NULL;
7905 char buf[MAX_SCREEN_COLS+1], sbuf[MAX_SCREEN_COLS+1];
7906 char *save_ref = NULL;
7907 #define FILTMSG_MAX 30
7909 if(!stream)
7910 return 0;
7912 if(READONLY_FOLDER(stream)){
7913 dprint((1,
7914 "Can't delete messages in readonly folder \"%s\"\n",
7915 STREAMNAME(stream)));
7916 q_status_message1(SM_ORDER, 1, 3,
7917 _("Can't delete messages in readonly folder \"%s\""),
7918 STREAMNAME(stream));
7919 return 1;
7922 buf[0] = '\0';
7924 width = MAX(10, ps_global->ttyo ? ps_global->ttyo->screen_cols : 80);
7925 snprintf(buf, sizeof(buf), "%.30s%.2sMoving %.10s filtered message%.2s to \"\"",
7926 nick ? nick : "", nick ? ": " : "",
7927 comatose(mn_total_cur(msgmap)), plural(mn_total_cur(msgmap)));
7928 /* 2 is for brackets, 5 is for " DONE" in busy alarm */
7929 width -= (strlen(buf) + 2 + 5);
7930 snprintf(buf, sizeof(buf), "%.30s%.2sMoving %.10s filtered message%.2s to \"%s\"",
7931 nick ? nick : "", nick ? ": " : "",
7932 comatose(mn_total_cur(msgmap)), plural(mn_total_cur(msgmap)),
7933 short_str(dstfldr, sbuf, sizeof(sbuf), width, FrontDots));
7935 dprint((5, "%s\n", buf));
7937 if(F_OFF(F_QUELL_FILTER_MSGS, ps_global))
7938 we_cancel = busy_cue(buf, NULL, 0);
7940 if(!is_absolute_path(dstfldr)
7941 && !(save_context = default_save_context(ps_global->context_list)))
7942 save_context = ps_global->context_list;
7945 * Because this save is happening independent of where the user is
7946 * in the folder hierarchy and has nothing to do with that, we want
7947 * to ignore the reference field built into the context. Zero it out
7948 * temporarily here so it won't affect the results of context_apply
7949 * in save.
7951 * This might be a problem elsewhere, as well. The same thing as this
7952 * is also done in match_pattern_folder_specific, which is also only
7953 * called from within process_filter_patterns. But there could be
7954 * others. We could have a separate function, something like
7955 * copy_default_save_context(), that automatically zeroes out the
7956 * reference field in the copy. However, some of the uses of
7957 * default_save_context() require that a pointer into the actual
7958 * context list is returned, so this would have to be done carefully.
7959 * Besides, we don't know of any other problems so we'll just change
7960 * these known cases for now.
7962 if(save_context && save_context->dir){
7963 save_ref = save_context->dir->ref;
7964 save_context->dir->ref = NULL;
7967 n = save(ps_global, stream, save_context, dstfldr, msgmap, flags_for_save);
7969 if(save_ref)
7970 save_context->dir->ref = save_ref;
7972 if(n != mn_total_cur(msgmap)){
7973 int exbits;
7974 long x;
7976 buf[0] = '\0';
7978 /* Clear "filtered" flags for failed messages */
7979 for(x = mn_first_cur(msgmap); x > 0L; x = mn_next_cur(msgmap))
7980 if(n-- <= 0 && msgno_exceptions(stream, mn_m2raw(msgmap, x),
7981 "0", &exbits, FALSE)){
7982 exbits &= ~(MSG_EX_FILTONCE | MSG_EX_FILEONCE |
7983 MSG_EX_FILTERED | MSG_EX_FILED);
7984 msgno_exceptions(stream, mn_m2raw(msgmap, x),
7985 "0", &exbits, TRUE);
7988 /* then re-incorporate them into folder they belong */
7989 (void) msgno_include(stream, sp_msgmap(stream), MI_NONE);
7990 clear_index_cache(stream, 0);
7991 refresh_sort(stream, sp_msgmap(stream), SRT_NON);
7992 ps_global->mangled_header = 1;
7994 else{
7995 snprintf(buf, sizeof(buf), _("Filtered all %s message to \"%s\""),
7996 comatose(n), dstfldr);
7997 dprint((5, "%s\n", buf));
8000 if(we_cancel)
8001 cancel_busy_cue(buf[0] ? 0 : -1);
8003 return(buf[0] != '\0');
8007 /*----------------------------------------------------------------------
8008 Move all messages with sequence bit lit to dstfldr
8010 Args: stream -- stream to use
8011 msgmap -- which messages to set
8012 flagbits -- which flags to set or clear
8013 kw_on -- keywords to set
8014 kw_off -- keywords to clear
8015 verbose -- 1 => busy alarm after 1 second
8016 2 => forced busy alarm
8017 ----*/
8018 void
8019 set_some_flags(MAILSTREAM *stream, MSGNO_S *msgmap, long int flagbits,
8020 char **kw_on, char **kw_off, int verbose, char *nick)
8022 long count = 0L, flipped_flags;
8023 int we_cancel = 0;
8024 char buf[150], *seq;
8026 if(!stream)
8027 return;
8029 if(READONLY_FOLDER(stream)){
8030 dprint((1, "Can't set flags in readonly folder \"%s\"\n",
8031 STREAMNAME(stream)));
8032 q_status_message1(SM_ORDER, 1, 3,
8033 _("Can't set flags in readonly folder \"%s\""),
8034 STREAMNAME(stream));
8035 return;
8038 /* use this to determine if anything needs to be done */
8039 flipped_flags = ((flagbits & F_ANS) ? F_UNANS : 0) |
8040 ((flagbits & F_UNANS) ? F_ANS : 0) |
8041 ((flagbits & F_FLAG) ? F_UNFLAG : 0) |
8042 ((flagbits & F_UNFLAG) ? F_FLAG : 0) |
8043 ((flagbits & F_DEL) ? F_UNDEL : 0) |
8044 ((flagbits & F_UNDEL) ? F_DEL : 0) |
8045 ((flagbits & F_SEEN) ? F_UNSEEN : 0) |
8046 ((flagbits & F_UNSEEN) ? F_SEEN : 0) |
8047 ((flagbits & F_KEYWORD) ? F_UNKEYWORD : 0) |
8048 ((flagbits & F_UNKEYWORD) ? F_KEYWORD : 0);
8049 if((seq = currentf_sequence(stream, msgmap, flipped_flags, &count, 0,
8050 kw_off, kw_on)) != NULL){
8051 char *sets = NULL, *clears = NULL;
8052 char *ps, *pc, **t;
8053 size_t clen, slen;
8055 /* allocate enough space for mail_flags arguments */
8056 for(slen=100, t = kw_on; t && *t; t++)
8057 slen += (strlen(*t) + 1);
8059 sets = (char *) fs_get(slen * sizeof(*sets));
8061 for(clen=100, t = kw_off; t && *t; t++)
8062 clen += (strlen(*t) + 1);
8064 clears = (char *) fs_get(clen * sizeof(*clears));
8066 sets[0] = clears[0] = '\0';
8067 ps = sets;
8068 pc = clears;
8070 snprintf(buf, sizeof(buf), "%.30s%.2sSetting flags in %.10s message%.10s",
8071 nick ? nick : "", nick ? ": " : "",
8072 comatose(count), plural(count));
8074 if(F_OFF(F_QUELL_FILTER_MSGS, ps_global))
8075 we_cancel = busy_cue(buf, NULL, verbose ? 0 : 1);
8078 * What's going on here? If we want to set more than one flag
8079 * we can do it with a single roundtrip by combining the arguments
8080 * into a single call and separating them with spaces.
8082 if(flagbits & F_ANS)
8083 sstrncpy(&ps, "\\ANSWERED", slen-(ps-sets));
8084 if(flagbits & F_FLAG){
8085 if(ps > sets)
8086 sstrncpy(&ps, " ", slen-(ps-sets));
8088 sstrncpy(&ps, "\\FLAGGED", slen-(ps-sets));
8090 if(flagbits & F_DEL){
8091 if(ps > sets)
8092 sstrncpy(&ps, " ", slen-(ps-sets));
8094 sstrncpy(&ps, "\\DELETED", slen-(ps-sets));
8096 if(flagbits & F_SEEN){
8097 if(ps > sets)
8098 sstrncpy(&ps, " ", slen-(ps-sets));
8100 sstrncpy(&ps, "\\SEEN", slen-(ps-sets));
8102 if(flagbits & F_KEYWORD){
8103 for(t = kw_on; t && *t; t++){
8104 int i;
8107 * We may be able to tell that this will fail before
8108 * we actually try it.
8110 if(stream->kwd_create ||
8111 (((i=user_flag_index(stream, *t)) >= 0) && i < NUSERFLAGS)){
8112 if(ps > sets)
8113 sstrncpy(&ps, " ", slen-(ps-sets));
8115 sstrncpy(&ps, *t, slen-(ps-sets));
8117 else{
8118 int some_defined = 0;
8119 static int msg_delivered = 0;
8121 some_defined = some_user_flags_defined(stream);
8123 if(msg_delivered++ < 2){
8124 char b[200], c[200], *p;
8125 int w;
8127 if(some_defined){
8128 snprintf(b, sizeof(b), "Can't set \"%.30s\". No more keywords in ", keyword_to_nick(*t));
8129 w = MIN((ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - strlen(b) - 1 - 2, sizeof(c)-1);
8130 p = short_str(STREAMNAME(stream), c, sizeof(c), w, FrontDots);
8131 q_status_message2(SM_ORDER, 3, 3, "%s%s!", b, p);
8133 else{
8134 snprintf(b, sizeof(b), "Can't set \"%.30s\". Can't add keywords in ", keyword_to_nick(*t));
8135 w = MIN((ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - strlen(b) - 1 - 2, sizeof(c)-1);
8136 p = short_str(STREAMNAME(stream), c, sizeof(c), w, FrontDots);
8137 q_status_message2(SM_ORDER, 3, 3, "%s%s!", b, p);
8141 if(some_defined){
8142 dprint((1, "Can't set keyword \"%s\". No more keywords allowed in %s\n", *t, stream->mailbox ? stream->mailbox : "target folder"));
8144 else{
8145 dprint((1, "Can't set keyword \"%s\". Can't add keywords in %s\n", *t, stream->mailbox ? stream->mailbox : "target folder"));
8151 /* need a separate call for the clears */
8152 if(flagbits & F_UNANS)
8153 sstrncpy(&pc, "\\ANSWERED", clen-(pc-clears));
8154 if(flagbits & F_UNFLAG){
8155 if(pc > clears)
8156 sstrncpy(&pc, " ", clen-(pc-clears));
8158 sstrncpy(&pc, "\\FLAGGED", clen-(pc-clears));
8160 if(flagbits & F_UNDEL){
8161 if(pc > clears)
8162 sstrncpy(&pc, " ", clen-(pc-clears));
8164 sstrncpy(&pc, "\\DELETED", clen-(pc-clears));
8166 if(flagbits & F_UNSEEN){
8167 if(pc > clears)
8168 sstrncpy(&pc, " ", clen-(pc-clears));
8170 sstrncpy(&pc, "\\SEEN", clen-(pc-clears));
8172 if(flagbits & F_UNKEYWORD){
8173 for(t = kw_off; t && *t; t++){
8174 if(pc > clears)
8175 sstrncpy(&pc, " ", clen-(pc-clears));
8177 sstrncpy(&pc, *t, clen-(pc-clears));
8182 if(sets[0])
8183 mail_flag(stream, seq, sets, ST_SET);
8185 if(clears[0])
8186 mail_flag(stream, seq, clears, 0L);
8188 fs_give((void **) &sets);
8189 fs_give((void **) &clears);
8190 fs_give((void **) &seq);
8192 if(we_cancel)
8193 cancel_busy_cue(buf[0] ? 0 : -1);
8199 * Delete messages which are marked FILTERED and excluded.
8200 * Messages which are FILTERED but not excluded are those that have had
8201 * their state set by a filter pattern, but are to remain in the same
8202 * folder.
8204 void
8205 delete_filtered_msgs(MAILSTREAM *stream)
8207 int exbits;
8208 long i;
8209 char *seq;
8210 MESSAGECACHE *mc;
8212 for(i = 1L; i <= stream->nmsgs; i++)
8213 if(msgno_exceptions(stream, i, "0", &exbits, FALSE)
8214 && (exbits & MSG_EX_FILTERED)
8215 && get_lflag(stream, NULL, i, MN_EXLD)){
8216 if((mc = mail_elt(stream, i)) != NULL)
8217 mc->sequence = 1;
8219 else if((mc = mail_elt(stream, i)) != NULL)
8220 mc->sequence = 0;
8222 if((seq = build_sequence(stream, NULL, NULL)) != NULL){
8223 mail_flag(stream, seq, "\\DELETED", ST_SET | ST_SILENT);
8224 fs_give((void **) &seq);