* Implement a different way to delete a password from the cache.
[alpine.git] / pith / pattern.c
blobad8ad753255a14471553d564bd092cc36d1767a8
1 /*
2 * ========================================================================
3 * Copyright 2013-2022 Eduardo Chappa
4 * Copyright 2006-2009 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 #include "../pith/headers.h"
16 #include "../pith/pattern.h"
17 #include "../pith/state.h"
18 #include "../pith/conf.h"
19 #include "../pith/string.h"
20 #include "../pith/msgno.h"
21 #include "../pith/status.h"
22 #include "../pith/list.h"
23 #include "../pith/flag.h"
24 #include "../pith/tempfile.h"
25 #include "../pith/addrstring.h"
26 #include "../pith/search.h"
27 #include "../pith/mailcmd.h"
28 #include "../pith/filter.h"
29 #include "../pith/save.h"
30 #include "../pith/mimedesc.h"
31 #include "../pith/reply.h"
32 #include "../pith/folder.h"
33 #include "../pith/maillist.h"
34 #include "../pith/sort.h"
35 #include "../pith/copyaddr.h"
36 #include "../pith/pipe.h"
37 #include "../pith/list.h"
38 #include "../pith/news.h"
39 #include "../pith/util.h"
40 #include "../pith/sequence.h"
41 #include "../pith/detoken.h"
42 #include "../pith/busy.h"
43 #include "../pith/indxtype.h"
44 #include "../pith/mailindx.h"
45 #include "../pith/send.h"
46 #include "../pith/icache.h"
47 #include "../pith/ablookup.h"
48 #include "../pith/keyword.h"
52 * Internal prototypes
54 void open_any_patterns(long);
55 void sub_open_any_patterns(long);
56 void sub_close_patterns(long);
57 int sub_any_patterns(long, PAT_STATE *);
58 PAT_LINE_S *parse_pat_lit(char *);
59 PAT_LINE_S *parse_pat_inherit(void);
60 PAT_S *parse_pat(char *);
61 void parse_patgrp_slash(char *, PATGRP_S *);
62 void parse_action_slash(char *, ACTION_S *);
63 ARBHDR_S *parse_arbhdr(char *);
64 char *next_arb(char *);
65 PAT_S *first_any_pattern(PAT_STATE *);
66 PAT_S *last_any_pattern(PAT_STATE *);
67 PAT_S *prev_any_pattern(PAT_STATE *);
68 PAT_S *next_any_pattern(PAT_STATE *);
69 int sub_write_patterns(long);
70 int write_pattern_file(char **, PAT_LINE_S *);
71 int write_pattern_lit(char **, PAT_LINE_S *);
72 int write_pattern_inherit(char **, PAT_LINE_S *);
73 char *data_for_patline(PAT_S *);
74 int charsets_present_in_msg(MAILSTREAM *, unsigned long, STRLIST_S *);
75 void collect_charsets_from_subj(ENVELOPE *, STRLIST_S **);
76 void collect_charsets_from_body(BODY *, STRLIST_S **);
77 SEARCHPGM *next_not(SEARCHPGM *);
78 SEARCHOR *next_or(SEARCHOR **);
79 void set_up_search_pgm(char *, PATTERN_S *, SEARCHPGM *);
80 void add_type_to_pgm(char *, PATTERN_S *, SEARCHPGM *);
81 void set_srch(char *, char *, SEARCHPGM *);
82 void set_srch_hdr(char *, char *, SEARCHPGM *);
83 void set_search_by_age(INTVL_S *, SEARCHPGM *, int);
84 void set_search_by_size(INTVL_S *, SEARCHPGM *);
85 int non_eh(char *);
86 void add_eh(char **, char **, char *, int *);
87 void set_extra_hdrs(char *);
88 int is_ascii_string(char *);
89 ACTION_S *combine_inherited_role_guts(ACTION_S *);
90 int move_filtered_msgs(MAILSTREAM *, MSGNO_S *, char *, int, char *);
91 void set_some_flags(MAILSTREAM *, MSGNO_S *, long, char **, char **, int, char *);
95 * optional hook for external-program filter test
97 void (*pith_opt_filter_pattern_cmd)(char **, SEARCHSET *, MAILSTREAM *, long, INTVL_S *);
100 void
101 role_process_filters(void)
103 int i;
104 MAILSTREAM *stream;
105 MSGNO_S *msgmap;
107 for(i = 0; i < ps_global->s_pool.nstream; i++){
108 stream = ps_global->s_pool.streams[i];
109 if(stream && pine_mail_ping(stream)){
110 msgmap = sp_msgmap(stream);
111 if(msgmap)
112 reprocess_filter_patterns(stream, msgmap, MI_REFILTERING);
119 add_to_pattern(PAT_S *pat, long int rflags)
121 PAT_LINE_S *new_patline, *patline;
122 PAT_S *new_pat;
123 PAT_STATE dummy;
125 if(!any_patterns(rflags, &dummy))
126 return(0);
128 /* need a new patline */
129 new_patline = (PAT_LINE_S *)fs_get(sizeof(*new_patline));
130 memset((void *)new_patline, 0, sizeof(*new_patline));
131 new_patline->type = Literal;
132 (*cur_pat_h)->dirtypinerc = 1;
134 /* and a copy of pat */
135 new_pat = copy_pat(pat);
137 /* tie together */
138 new_patline->first = new_patline->last = new_pat;
139 new_pat->patline = new_patline;
143 * Manipulate bits directly in pattern list.
144 * Cur_pat_h is set by any_patterns.
148 /* find last patline */
149 for(patline = (*cur_pat_h)->patlinehead;
150 patline && patline->next;
151 patline = patline->next)
154 /* add new patline to end of list */
155 if(patline){
156 patline->next = new_patline;
157 new_patline->prev = patline;
159 else
160 (*cur_pat_h)->patlinehead = new_patline;
162 return(1);
167 * Does pattern quoting. Takes the string that the user sees and converts
168 * it to the config file string.
170 * Args: src -- The source string.
172 * The last arg to add_escapes causes \, and \\ to be replaced with hex
173 * versions of comma and backslash. That's so we can embed commas in
174 * list variables without having them act as separators. If the user wants
175 * a literal comma, they type backslash comma.
176 * If /, \, or " appear (other than the special cases in previous sentence)
177 * they are backslash-escaped like \/, \\, or \".
179 * Returns: An allocated string with quoting added.
181 * The caller is responsible for freeing the memory allocated for the answer.
183 char *
184 add_pat_escapes(char *src)
186 return(add_escapes(src, "/\\\"", '\\', "", ",\\"));
191 * Undoes the escape quoting done by add_pat_escapes.
193 * Args: src -- The source string.
195 * Returns: A string with backslash quoting removed or NULL. The string starts
196 * at src and goes until the end of src or until a / is reached. The
197 * / is not included in the string. /'s may be quoted by preceding
198 * them with a backslash (\) and \'s may also be quoted by
199 * preceding them with a \. In fact, \ quotes any character.
200 * Not quite, \nnn is octal escape, \xXX is hex escape.
201 * Hex escapes are undone but left with a backslash in front.
203 * The caller is responsible for freeing the memory allocated for the answer.
205 char *
206 remove_pat_escapes(char *src)
208 char *ans = NULL, *q, *p;
209 int done = 0;
211 if(src){
212 p = q = (char *)fs_get(strlen(src) + 1);
214 while(!done){
215 switch(*src){
216 case '\\':
217 src++;
218 if(*src){
219 if(isdigit((unsigned char)*src)){ /* octal escape */
220 *p++ = '\\';
221 *p++ = (char)read_octal(&src);
223 else if((*src == 'x' || *src == 'X') &&
224 *(src+1) && *(src+2) && isxpair(src+1)){
225 *p++ = '\\';
226 *p++ = (char)read_hex(src+1);
227 src += 3;
229 else
230 *p++ = *src++;
233 break;
235 case '\0':
236 case '/':
237 done++;
238 break;
240 default:
241 *p++ = *src++;
242 break;
246 *p = '\0';
248 ans = cpystr(q);
249 fs_give((void **)&q);
252 return(ans);
257 * This takes envelope data and adds the backslash escapes that the user
258 * would have been responsible for adding if editing manually.
259 * It just escapes commas and backslashes.
261 * Caller must free result.
263 char *
264 add_roletake_escapes(char *src)
266 return(add_escapes(src, ",\\", '\\', "", ""));
270 * This function only escapes commas.
272 char *
273 add_comma_escapes(char *src)
275 return(add_escapes(src, ",", '\\', "", ""));
280 * These are the global pattern handles which all of the pattern routines
281 * use. Once we open one of these we usually leave it open until exiting
282 * pine. The _any versions are only used if we are altering our configuration,
283 * the _ne (NonEmpty) versions are used routinely. We open the patterns by
284 * calling either nonempty_patterns (normal use) or any_patterns (config).
286 * There are eight different pinerc variables which contain patterns. They are
287 * patterns-filters2, patterns-roles, patterns-scores2, patterns-indexcolors,
288 * patterns-other, and the old patterns, patterns-filters, and patterns-scores.
289 * The first five are the active patterns variables and the old variable are
290 * kept around so that we can convert old patterns to new. The reason we
291 * split it into five separate variables is so that each can independently
292 * be controlled by the main pinerc or by the exception pinerc. The reason
293 * for the change to filters2 and scores2 was so we could change the semantics
294 * of how rules work when there are pieces in the rule that we don't
295 * understand. We added a rule to detect 8bitSubjects. So a user might have
296 * a filter that deletes messages with 8bitSubjects. The problem was that
297 * that same filter in a old patterns-filters pine would match because it
298 * would ignore the 8bitSubject part of the pattern and match on the rest.
299 * So we changed the semantics so that rules with unknown pieces would be
300 * ignored instead of used. We had to change variable names at the same time
301 * because we were adding the 8bit thing and the old pines are still out
302 * there. Filters and Scores can both be dangerous. Roles, Colors, and Other
303 * seem less dangerous so not worth adding a new variable for them.
305 * Each of the eight variables has its own handle and status variables below.
306 * That means that they operate independently.
308 * Looking at just a single one of those variables, it has four possible
309 * values. In normal use, we use the current_val of the variable to set
310 * up the patterns. We do that by calling nonempty_patterns() with the
311 * appropriate rflags. When editing configurations, we have the other two
312 * variables to deal with: main_user_val and post_user_val.
313 * We only ever deal with one of those at a time, so we re-use the variables.
314 * However, we do sometimes want to deal with one of those and at the same
315 * time refer to the current current_val. For example, if we are editing
316 * the post or main user_val for the filters variable, we still want
317 * to check for new mail. If we find new mail we'll want to call
318 * process_filter_patterns which uses the current_val for filter patterns.
319 * That means we have to provide for the case where we are using current_val
320 * at the same time as we're using one of the user_vals. That's why we have
321 * both the _ne variables (NonEmpty) and the _any variables.
323 * In any_patterns (and first_pattern...) use_flags may only be set to
324 * one value at a time, whereas rflags may be more than one value OR'd together.
326 PAT_HANDLE **cur_pat_h;
327 static PAT_HANDLE *pattern_h_roles_ne, *pattern_h_roles_any,
328 *pattern_h_scores_ne, *pattern_h_scores_any,
329 *pattern_h_filts_ne, *pattern_h_filts_any,
330 *pattern_h_filts_cfg,
331 *pattern_h_filts_ne, *pattern_h_filts_any,
332 *pattern_h_incol_ne, *pattern_h_incol_any,
333 *pattern_h_other_ne, *pattern_h_other_any,
334 *pattern_h_srch_ne, *pattern_h_srch_any,
335 *pattern_h_oldpat_ne, *pattern_h_oldpat_any;
338 * These contain the PAT_OPEN_MASK open status and the PAT_USE_MASK use status.
340 static long *cur_pat_status;
341 static long pat_status_roles_ne, pat_status_roles_any,
342 pat_status_scores_ne, pat_status_scores_any,
343 pat_status_filts_ne, pat_status_filts_any,
344 pat_status_incol_ne, pat_status_incol_any,
345 pat_status_other_ne, pat_status_other_any,
346 pat_status_srch_ne, pat_status_srch_any,
347 pat_status_oldpat_ne, pat_status_oldpat_any,
348 pat_status_oldfilt_ne, pat_status_oldfilt_any,
349 pat_status_oldscore_ne, pat_status_oldscore_any;
351 #define SET_PATTYPE(rflags) \
352 set_pathandle(rflags); \
353 cur_pat_status = \
354 ((rflags) & PAT_USE_CURRENT) \
355 ? (((rflags) & ROLE_DO_INCOLS) ? &pat_status_incol_ne : \
356 ((rflags) & ROLE_DO_OTHER) ? &pat_status_other_ne : \
357 ((rflags) & ROLE_DO_FILTER) ? &pat_status_filts_ne : \
358 ((rflags) & ROLE_DO_SCORES) ? &pat_status_scores_ne : \
359 ((rflags) & ROLE_DO_ROLES) ? &pat_status_roles_ne : \
360 ((rflags) & ROLE_DO_SRCH) ? &pat_status_srch_ne : \
361 ((rflags) & ROLE_OLD_FILT) ? &pat_status_oldfilt_ne : \
362 ((rflags) & ROLE_OLD_SCORE) ? &pat_status_oldscore_ne :\
363 &pat_status_oldpat_ne) \
364 : (((rflags) & ROLE_DO_INCOLS) ? &pat_status_incol_any : \
365 ((rflags) & ROLE_DO_OTHER) ? &pat_status_other_any : \
366 ((rflags) & ROLE_DO_FILTER) ? &pat_status_filts_any : \
367 ((rflags) & ROLE_DO_SCORES) ? &pat_status_scores_any : \
368 ((rflags) & ROLE_DO_ROLES) ? &pat_status_roles_any : \
369 ((rflags) & ROLE_DO_SRCH) ? &pat_status_srch_any : \
370 ((rflags) & ROLE_OLD_FILT) ? &pat_status_oldfilt_any :\
371 ((rflags) & ROLE_OLD_SCORE) ? &pat_status_oldscore_any:\
372 &pat_status_oldpat_any);
373 #define CANONICAL_RFLAGS(rflags) \
374 ((((rflags) & (ROLE_DO_ROLES | ROLE_REPLY | ROLE_FORWARD | ROLE_COMPOSE)) \
375 ? ROLE_DO_ROLES : 0) | \
376 (((rflags) & (ROLE_DO_INCOLS | ROLE_INCOL)) \
377 ? ROLE_DO_INCOLS : 0) | \
378 (((rflags) & (ROLE_DO_SCORES | ROLE_SCORE)) \
379 ? ROLE_DO_SCORES : 0) | \
380 (((rflags) & (ROLE_DO_FILTER)) \
381 ? ROLE_DO_FILTER : 0) | \
382 (((rflags) & (ROLE_DO_OTHER)) \
383 ? ROLE_DO_OTHER : 0) | \
384 (((rflags) & (ROLE_DO_SRCH)) \
385 ? ROLE_DO_SRCH : 0) | \
386 (((rflags) & (ROLE_OLD_FILT)) \
387 ? ROLE_OLD_FILT : 0) | \
388 (((rflags) & (ROLE_OLD_SCORE)) \
389 ? ROLE_OLD_SCORE : 0) | \
390 (((rflags) & (ROLE_OLD_PAT)) \
391 ? ROLE_OLD_PAT : 0))
393 #define SETPGMSTATUS(val,yes,no) \
394 switch(val){ \
395 case PAT_STAT_YES: \
396 (yes) = 1; \
397 break; \
398 case PAT_STAT_NO: \
399 (no) = 1; \
400 break; \
401 case PAT_STAT_EITHER: \
402 default: \
403 break; \
406 #define SET_STATUS(srchin,srchfor,assignto) \
407 {char *qq, *pp; \
408 int ii; \
409 NAMEVAL_S *vv; \
410 if((qq = srchstr(srchin, srchfor)) != NULL){ \
411 if((pp = remove_pat_escapes(qq+strlen(srchfor))) != NULL){ \
412 for(ii = 0; (vv = role_status_types(ii)); ii++) \
413 if(!strucmp(pp, vv->shortname)){ \
414 assignto = vv->value; \
415 break; \
418 fs_give((void **)&pp); \
423 #define SET_MSGSTATE(srchin,srchfor,assignto) \
424 {char *qq, *pp; \
425 int ii; \
426 NAMEVAL_S *vv; \
427 if((qq = srchstr(srchin, srchfor)) != NULL){ \
428 if((pp = remove_pat_escapes(qq+strlen(srchfor))) != NULL){ \
429 for(ii = 0; (vv = msg_state_types(ii)); ii++) \
430 if(!strucmp(pp, vv->shortname)){ \
431 assignto = vv->value; \
432 break; \
435 fs_give((void **)&pp); \
440 #define PATTERN_N (9)
443 void
444 set_pathandle(long int rflags)
446 cur_pat_h = (rflags & PAT_USE_CURRENT)
447 ? ((rflags & ROLE_DO_INCOLS) ? &pattern_h_incol_ne :
448 (rflags & ROLE_DO_OTHER) ? &pattern_h_other_ne :
449 (rflags & ROLE_DO_FILTER) ? &pattern_h_filts_ne :
450 (rflags & ROLE_DO_SCORES) ? &pattern_h_scores_ne :
451 (rflags & ROLE_DO_ROLES) ? &pattern_h_roles_ne :
452 (rflags & ROLE_DO_SRCH) ? &pattern_h_srch_ne :
453 &pattern_h_oldpat_ne)
454 : ((rflags & PAT_USE_CHANGED)
455 ? &pattern_h_filts_cfg
456 : ((rflags & ROLE_DO_INCOLS) ? &pattern_h_incol_any :
457 (rflags & ROLE_DO_OTHER) ? &pattern_h_other_any :
458 (rflags & ROLE_DO_FILTER) ? &pattern_h_filts_any :
459 (rflags & ROLE_DO_SCORES) ? &pattern_h_scores_any :
460 (rflags & ROLE_DO_ROLES) ? &pattern_h_roles_any :
461 (rflags & ROLE_DO_SRCH) ? &pattern_h_srch_any :
462 &pattern_h_oldpat_any));
467 * Rflags may be more than one pattern type OR'd together. It also contains
468 * the "use" parameter.
470 void
471 open_any_patterns(long int rflags)
473 long canon_rflags;
475 dprint((7, "open_any_patterns(0x%x)\n", rflags));
477 canon_rflags = CANONICAL_RFLAGS(rflags);
479 if(canon_rflags & ROLE_DO_INCOLS)
480 sub_open_any_patterns(ROLE_DO_INCOLS | (rflags & PAT_USE_MASK));
481 if(canon_rflags & ROLE_DO_FILTER)
482 sub_open_any_patterns(ROLE_DO_FILTER | (rflags & PAT_USE_MASK));
483 if(canon_rflags & ROLE_DO_OTHER)
484 sub_open_any_patterns(ROLE_DO_OTHER | (rflags & PAT_USE_MASK));
485 if(canon_rflags & ROLE_DO_SCORES)
486 sub_open_any_patterns(ROLE_DO_SCORES | (rflags & PAT_USE_MASK));
487 if(canon_rflags & ROLE_DO_ROLES)
488 sub_open_any_patterns(ROLE_DO_ROLES | (rflags & PAT_USE_MASK));
489 if(canon_rflags & ROLE_DO_SRCH)
490 sub_open_any_patterns(ROLE_DO_SRCH | (rflags & PAT_USE_MASK));
491 if(canon_rflags & ROLE_OLD_FILT)
492 sub_open_any_patterns(ROLE_OLD_FILT | (rflags & PAT_USE_MASK));
493 if(canon_rflags & ROLE_OLD_SCORE)
494 sub_open_any_patterns(ROLE_OLD_SCORE | (rflags & PAT_USE_MASK));
495 if(canon_rflags & ROLE_OLD_PAT)
496 sub_open_any_patterns(ROLE_OLD_PAT | (rflags & PAT_USE_MASK));
501 * This should only be called with a single pattern type (plus use flags).
502 * We assume that patterns of this type are closed before this is called.
503 * This always succeeds unless we run out of memory, in which case fs_get
504 * never returns.
506 void
507 sub_open_any_patterns(long int rflags)
509 PAT_LINE_S *patline = NULL, *pl = NULL;
510 char **t = NULL;
511 struct variable *var = NULL;
513 SET_PATTYPE(rflags);
515 *cur_pat_h = (PAT_HANDLE *)fs_get(sizeof(**cur_pat_h));
516 memset((void *)*cur_pat_h, 0, sizeof(**cur_pat_h));
518 if(rflags & ROLE_DO_ROLES)
519 var = &ps_global->vars[V_PAT_ROLES];
520 else if(rflags & ROLE_DO_FILTER)
521 var = &ps_global->vars[V_PAT_FILTS];
522 else if(rflags & ROLE_DO_OTHER)
523 var = &ps_global->vars[V_PAT_OTHER];
524 else if(rflags & ROLE_DO_SCORES)
525 var = &ps_global->vars[V_PAT_SCORES];
526 else if(rflags & ROLE_DO_INCOLS)
527 var = &ps_global->vars[V_PAT_INCOLS];
528 else if(rflags & ROLE_DO_SRCH)
529 var = &ps_global->vars[V_PAT_SRCH];
530 else if(rflags & ROLE_OLD_FILT)
531 var = &ps_global->vars[V_PAT_FILTS_OLD];
532 else if(rflags & ROLE_OLD_SCORE)
533 var = &ps_global->vars[V_PAT_SCORES_OLD];
534 else if(rflags & ROLE_OLD_PAT)
535 var = &ps_global->vars[V_PATTERNS];
537 switch(rflags & PAT_USE_MASK){
538 case PAT_USE_CURRENT:
539 t = var->current_val.l;
540 break;
541 case PAT_USE_CHANGED:
543 * some trickery to only use changed if actually changed.
544 * otherwise, use current_val
546 t = var->is_changed_val ? var->changed_val.l : var->current_val.l;
547 break;
548 case PAT_USE_MAIN:
549 t = var->main_user_val.l;
550 break;
551 case PAT_USE_POST:
552 t = var->post_user_val.l;
553 break;
556 if(t){
557 for(; t[0] && t[0][0]; t++){
558 if(*t && !strncmp("LIT:", *t, 4))
559 patline = parse_pat_lit(*t + 4);
560 else if(*t && !strncmp("FILE:", *t, 5))
561 patline = parse_pat_file(*t + 5);
562 else if(rflags & (PAT_USE_MAIN | PAT_USE_POST) &&
563 patline == NULL && *t && !strcmp(INHERIT, *t))
564 patline = parse_pat_inherit();
565 else
566 patline = NULL;
568 if(patline){
569 if(pl){
570 pl->next = patline;
571 patline->prev = pl;
572 pl = pl->next;
574 else{
575 (*cur_pat_h)->patlinehead = patline;
576 pl = patline;
579 else
580 q_status_message1(SM_ORDER, 0, 3,
581 "Invalid patterns line \"%.200s\"", *t);
585 *cur_pat_status = PAT_OPENED | (rflags & PAT_USE_MASK);
589 void
590 close_every_pattern(void)
592 close_patterns(ROLE_DO_INCOLS | ROLE_DO_FILTER | ROLE_DO_SCORES
593 | ROLE_DO_OTHER | ROLE_DO_ROLES | ROLE_DO_SRCH
594 | ROLE_OLD_FILT | ROLE_OLD_SCORE | ROLE_OLD_PAT
595 | PAT_USE_CURRENT);
597 * Since there is only one set of variables for the other three uses
598 * we can just close any one of them. There can only be one open at
599 * a time.
601 close_patterns(ROLE_DO_INCOLS | ROLE_DO_FILTER | ROLE_DO_SCORES
602 | ROLE_DO_OTHER | ROLE_DO_ROLES | ROLE_DO_SRCH
603 | ROLE_OLD_FILT | ROLE_OLD_SCORE | ROLE_OLD_PAT
604 | PAT_USE_MAIN);
609 * Can be called with more than one pattern type.
611 void
612 close_patterns(long int rflags)
614 long canon_rflags;
616 dprint((7, "close_patterns(0x%x)\n", rflags));
618 canon_rflags = CANONICAL_RFLAGS(rflags);
620 if(canon_rflags & ROLE_DO_INCOLS)
621 sub_close_patterns(ROLE_DO_INCOLS | (rflags & PAT_USE_MASK));
622 if(canon_rflags & ROLE_DO_OTHER)
623 sub_close_patterns(ROLE_DO_OTHER | (rflags & PAT_USE_MASK));
624 if(canon_rflags & ROLE_DO_FILTER)
625 sub_close_patterns(ROLE_DO_FILTER | (rflags & PAT_USE_MASK));
626 if(canon_rflags & ROLE_DO_SCORES)
627 sub_close_patterns(ROLE_DO_SCORES | (rflags & PAT_USE_MASK));
628 if(canon_rflags & ROLE_DO_ROLES)
629 sub_close_patterns(ROLE_DO_ROLES | (rflags & PAT_USE_MASK));
630 if(canon_rflags & ROLE_DO_SRCH)
631 sub_close_patterns(ROLE_DO_SRCH | (rflags & PAT_USE_MASK));
632 if(canon_rflags & ROLE_OLD_FILT)
633 sub_close_patterns(ROLE_OLD_FILT | (rflags & PAT_USE_MASK));
634 if(canon_rflags & ROLE_OLD_SCORE)
635 sub_close_patterns(ROLE_OLD_SCORE | (rflags & PAT_USE_MASK));
636 if(canon_rflags & ROLE_OLD_PAT)
637 sub_close_patterns(ROLE_OLD_PAT | (rflags & PAT_USE_MASK));
642 * Can be called with only a single pattern type.
644 void
645 sub_close_patterns(long int rflags)
647 SET_PATTYPE(rflags);
649 if(*cur_pat_h != NULL){
650 free_patline(&(*cur_pat_h)->patlinehead);
651 fs_give((void **)cur_pat_h);
654 *cur_pat_status = PAT_CLOSED;
656 scores_are_used(SCOREUSE_INVALID);
661 * Can be called with more than one pattern type.
662 * Nonempty always uses PAT_USE_CURRENT (the current_val).
665 nonempty_patterns(long int rflags, PAT_STATE *pstate)
667 return(any_patterns((rflags & ROLE_MASK) | PAT_USE_CURRENT, pstate));
672 * Initializes pstate and parses and sets up appropriate pattern variables.
673 * May be called with more than one pattern type OR'd together in rflags.
674 * Pstate will keep track of that and next_pattern et. al. will increment
675 * through all of those pattern types.
678 any_patterns(long int rflags, PAT_STATE *pstate)
680 int ret = 0;
681 long canon_rflags;
683 dprint((7, "any_patterns(0x%x)\n", rflags));
685 memset((void *)pstate, 0, sizeof(*pstate));
686 pstate->rflags = rflags;
688 canon_rflags = CANONICAL_RFLAGS(pstate->rflags);
690 if(canon_rflags & ROLE_DO_INCOLS)
691 ret += sub_any_patterns(ROLE_DO_INCOLS, pstate);
692 if(canon_rflags & ROLE_DO_OTHER)
693 ret += sub_any_patterns(ROLE_DO_OTHER, pstate);
694 if(canon_rflags & ROLE_DO_FILTER)
695 ret += sub_any_patterns(ROLE_DO_FILTER, pstate);
696 if(canon_rflags & ROLE_DO_SCORES)
697 ret += sub_any_patterns(ROLE_DO_SCORES, pstate);
698 if(canon_rflags & ROLE_DO_ROLES)
699 ret += sub_any_patterns(ROLE_DO_ROLES, pstate);
700 if(canon_rflags & ROLE_DO_SRCH)
701 ret += sub_any_patterns(ROLE_DO_SRCH, pstate);
702 if(canon_rflags & ROLE_OLD_FILT)
703 ret += sub_any_patterns(ROLE_OLD_FILT, pstate);
704 if(canon_rflags & ROLE_OLD_SCORE)
705 ret += sub_any_patterns(ROLE_OLD_SCORE, pstate);
706 if(canon_rflags & ROLE_OLD_PAT)
707 ret += sub_any_patterns(ROLE_OLD_PAT, pstate);
709 return(ret);
714 sub_any_patterns(long int rflags, PAT_STATE *pstate)
716 SET_PATTYPE(rflags | (pstate->rflags & PAT_USE_MASK));
718 if(*cur_pat_h &&
719 (((pstate->rflags & PAT_USE_MASK) == PAT_USE_CURRENT &&
720 (*cur_pat_status & PAT_USE_MASK) != PAT_USE_CURRENT) ||
721 ((pstate->rflags & PAT_USE_MASK) != PAT_USE_CURRENT &&
722 ((*cur_pat_status & PAT_OPEN_MASK) != PAT_OPENED ||
723 (*cur_pat_status & PAT_USE_MASK) !=
724 (pstate->rflags & PAT_USE_MASK)))))
725 close_patterns(rflags | (pstate->rflags & PAT_USE_MASK));
727 /* open_any always succeeds */
728 if(!*cur_pat_h && ((*cur_pat_status & PAT_OPEN_MASK) == PAT_CLOSED))
729 open_any_patterns(rflags | (pstate->rflags & PAT_USE_MASK));
731 if(!*cur_pat_h){ /* impossible */
732 *cur_pat_status = PAT_CLOSED;
733 return(0);
737 * Opening nonempty can fail. That just means there aren't any
738 * patterns of that type.
740 if((pstate->rflags & PAT_USE_MASK) == PAT_USE_CURRENT &&
741 !(*cur_pat_h)->patlinehead)
742 *cur_pat_status = (PAT_OPEN_FAILED | PAT_USE_CURRENT);
744 return(((*cur_pat_status & PAT_OPEN_MASK) == PAT_OPENED) ? 1 : 0);
749 edit_pattern(PAT_S *newpat, int pos, long int rflags)
751 PAT_S *oldpat;
752 PAT_LINE_S *tpatline;
753 int i;
754 PAT_STATE pstate;
756 if(!any_patterns(rflags, &pstate)) return(1);
758 for(i = 0, tpatline = (*cur_pat_h)->patlinehead;
759 i < pos && tpatline; tpatline = tpatline->next, i++);
760 if(i != pos) return(1);
761 oldpat = tpatline->first;
762 free_pat(&oldpat);
763 tpatline->first = tpatline->last = newpat;
764 newpat->patline = tpatline;
765 tpatline->dirty = 1;
767 (*cur_pat_h)->dirtypinerc = 1;
768 write_patterns(rflags);
770 return(0);
774 add_pattern(PAT_S *newpat, long int rflags)
776 PAT_LINE_S *tpatline, *newpatline;
777 PAT_STATE pstate;
779 any_patterns(rflags, &pstate);
781 for(tpatline = (*cur_pat_h)->patlinehead;
782 tpatline && tpatline->next ; tpatline = tpatline->next);
783 newpatline = (PAT_LINE_S *)fs_get(sizeof(PAT_LINE_S));
784 if(tpatline)
785 tpatline->next = newpatline;
786 else
787 (*cur_pat_h)->patlinehead = newpatline;
788 memset((void *)newpatline, 0, sizeof(PAT_LINE_S));
789 newpatline->prev = tpatline;
790 newpatline->first = newpatline->last = newpat;
791 newpatline->type = Literal;
792 newpat->patline = newpatline;
793 newpatline->dirty = 1;
795 (*cur_pat_h)->dirtypinerc = 1;
796 write_patterns(rflags);
798 return(0);
802 delete_pattern(int pos, long int rflags)
804 PAT_LINE_S *tpatline;
805 int i;
806 PAT_STATE pstate;
808 if(!any_patterns(rflags, &pstate)) return(1);
810 for(i = 0, tpatline = (*cur_pat_h)->patlinehead;
811 i < pos && tpatline; tpatline = tpatline->next, i++);
812 if(i != pos) return(1);
814 if(tpatline == (*cur_pat_h)->patlinehead)
815 (*cur_pat_h)->patlinehead = tpatline->next;
816 if(tpatline->prev) tpatline->prev->next = tpatline->next;
817 if(tpatline->next) tpatline->next->prev = tpatline->prev;
818 tpatline->prev = NULL;
819 tpatline->next = NULL;
821 free_patline(&tpatline);
823 (*cur_pat_h)->dirtypinerc = 1;
824 write_patterns(rflags);
826 return(0);
830 shuffle_pattern(int pos, int up, long int rflags)
832 PAT_LINE_S *tpatline, *shufpatline;
833 int i;
834 PAT_STATE pstate;
836 if(!any_patterns(rflags, &pstate)) return(1);
838 for(i = 0, tpatline = (*cur_pat_h)->patlinehead;
839 i < pos && tpatline; tpatline = tpatline->next, i++);
840 if(i != pos) return(1);
842 if(up == 1){
843 if(tpatline->prev == NULL) return(1);
844 shufpatline = tpatline->prev;
845 tpatline->prev = shufpatline->prev;
846 if(shufpatline->prev)
847 shufpatline->prev->next = tpatline;
848 if(tpatline->next)
849 tpatline->next->prev = shufpatline;
850 shufpatline->next = tpatline->next;
851 shufpatline->prev = tpatline;
852 tpatline->next = shufpatline;
853 if(shufpatline == (*cur_pat_h)->patlinehead)
854 (*cur_pat_h)->patlinehead = tpatline;
856 else if(up == -1){
857 if(tpatline->next == NULL) return(1);
858 shufpatline = tpatline->next;
859 tpatline->next = shufpatline->next;
860 if(shufpatline->next)
861 shufpatline->next->prev = tpatline;
862 if(tpatline->prev)
863 tpatline->prev->next = shufpatline;
864 shufpatline->prev = tpatline->prev;
865 shufpatline->next = tpatline;
866 tpatline->prev = shufpatline;
867 if(tpatline == (*cur_pat_h)->patlinehead)
868 (*cur_pat_h)->patlinehead = shufpatline;
870 else return(1);
872 shufpatline->dirty = 1;
873 tpatline->dirty = 1;
875 (*cur_pat_h)->dirtypinerc = 1;
876 write_patterns(rflags);
878 return(0);
881 PAT_LINE_S *
882 parse_pat_lit(char *litpat)
884 PAT_LINE_S *patline;
885 PAT_S *pat;
887 patline = (PAT_LINE_S *)fs_get(sizeof(*patline));
888 memset((void *)patline, 0, sizeof(*patline));
889 patline->type = Literal;
892 if((pat = parse_pat(litpat)) != NULL){
893 pat->patline = patline;
894 patline->first = pat;
895 patline->last = pat;
898 return(patline);
903 * This always returns a patline even if we can't read the file. The patline
904 * returned will say readonly in the worst case and there will be no patterns.
905 * If the file doesn't exist, this creates it if possible.
907 PAT_LINE_S *
908 parse_pat_file(char *filename)
910 #define BUF_SIZE 5000
911 PAT_LINE_S *patline;
912 PAT_S *pat, *p;
913 char path[MAXPATH+1], buf[BUF_SIZE];
914 char *dir, *q;
915 FILE *fp;
916 int ok = 0, some_pats = 0;
917 struct variable *vars = ps_global->vars;
919 signature_path(filename, path, MAXPATH);
921 if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, path)){
922 q_status_message1(SM_ORDER | SM_DING, 3, 4,
923 "Can't use Roles file outside of %.200s",
924 VAR_OPER_DIR);
925 return(NULL);
928 patline = (PAT_LINE_S *)fs_get(sizeof(*patline));
929 memset((void *)patline, 0, sizeof(*patline));
930 patline->type = File;
931 patline->filename = cpystr(filename);
932 patline->filepath = cpystr(path);
934 if((q = last_cmpnt(path)) != NULL){
935 int save;
937 save = *--q;
938 *q = '\0';
939 dir = cpystr(*path ? path : "/");
940 *q = save;
942 else
943 dir = cpystr(".");
945 #if defined(DOS) || defined(OS2)
947 * If the dir has become a drive letter and : (e.g. "c:")
948 * then append a "\". The library function access() in the
949 * win 16 version of MSC seems to require this.
951 if(isalpha((unsigned char) *dir)
952 && *(dir+1) == ':' && *(dir+2) == '\0'){
953 *(dir+2) = '\\';
954 *(dir+3) = '\0';
956 #endif /* DOS || OS2 */
959 * Even if we can edit the file itself, we aren't going
960 * to be able to change it unless we can also write in
961 * the directory that contains it (because we write into a
962 * temp file and then rename).
964 if(can_access(dir, EDIT_ACCESS) != 0)
965 patline->readonly = 1;
967 if(can_access(path, EDIT_ACCESS) == 0){
968 if(patline->readonly)
969 q_status_message1(SM_ORDER, 0, 3,
970 "Pattern file directory (%.200s) is ReadOnly", dir);
972 else if(can_access(path, READ_ACCESS) == 0)
973 patline->readonly = 1;
975 if(can_access(path, ACCESS_EXISTS) == 0){
976 if((fp = our_fopen(path, "rb")) != NULL){
977 /* Check to see if this is a valid patterns file */
978 if(fp_file_size(fp) <= 0L)
979 ok++;
980 else{
981 size_t len;
983 len = strlen(PATTERN_MAGIC);
984 if(fread(buf, sizeof(char), len+3, fp) == len+3){
985 buf[len+3] = '\0';
986 buf[len] = '\0';
987 if(strcmp(buf, PATTERN_MAGIC) == 0){
988 if(atoi(PATTERN_FILE_VERS) < atoi(buf + len + 1))
989 q_status_message1(SM_ORDER, 0, 4,
990 "Pattern file \"%.200s\" is made by newer Alpine, will try to use it anyway",
991 filename);
993 ok++;
994 some_pats++;
995 /* toss rest of first line */
996 (void)fgets(buf, BUF_SIZE, fp);
1001 if(!ok){
1002 patline->readonly = 1;
1003 q_status_message1(SM_ORDER | SM_DING, 3, 4,
1004 "\"%.200s\" is not a Pattern file", path);
1007 p = NULL;
1008 while(some_pats && fgets(buf, BUF_SIZE, fp) != NULL){
1009 if((pat = parse_pat(buf)) != NULL){
1010 pat->patline = patline;
1011 if(!patline->first)
1012 patline->first = pat;
1014 patline->last = pat;
1016 if(p){
1017 p->next = pat;
1018 pat->prev = p;
1019 p = p->next;
1021 else
1022 p = pat;
1026 (void)fclose(fp);
1028 else{
1029 patline->readonly = 1;
1030 q_status_message2(SM_ORDER | SM_DING, 3, 4,
1031 "Error \"%.200s\" reading pattern file \"%.200s\"",
1032 error_description(errno), path);
1035 else{ /* doesn't exist yet, try to create it */
1036 if(patline->readonly)
1037 q_status_message1(SM_ORDER, 0, 3,
1038 "Pattern file directory (%.200s) is ReadOnly", dir);
1039 else{
1041 * We try to create it by making up an empty patline and calling
1042 * write_pattern_file.
1044 patline->dirty = 1;
1045 if(write_pattern_file(NULL, patline) != 0){
1046 patline->readonly = 1;
1047 patline->dirty = 0;
1048 q_status_message1(SM_ORDER | SM_DING, 3, 4,
1049 "Error creating pattern file \"%.200s\"",
1050 path);
1055 if(dir)
1056 fs_give((void **)&dir);
1058 return(patline);
1062 PAT_LINE_S *
1063 parse_pat_inherit(void)
1065 PAT_LINE_S *patline;
1066 PAT_S *pat;
1068 patline = (PAT_LINE_S *)fs_get(sizeof(*patline));
1069 memset((void *)patline, 0, sizeof(*patline));
1070 patline->type = Inherit;
1072 pat = (PAT_S *)fs_get(sizeof(*pat));
1073 memset((void *)pat, 0, sizeof(*pat));
1074 pat->inherit = 1;
1076 pat->patline = patline;
1077 patline->first = pat;
1078 patline->last = pat;
1080 return(patline);
1085 * There are three forms that a PATTERN_S has at various times. There is
1086 * the actual PATTERN_S struct which is used internally and is used whenever
1087 * we are actually doing something with the pattern, like filtering or
1088 * something. There is the version that goes in the config file. And there
1089 * is the version the user edits.
1091 * To go between these three forms we have the helper routines
1093 * pattern_to_config
1094 * config_to_pattern
1095 * pattern_to_editlist
1096 * editlist_to_pattern
1098 * Here's what is supposed to be happening. A PATTERN_S is a linked list
1099 * of strings with nothing escaped. That is, a backslash or a comma is
1100 * just in there as a backslash or comma.
1102 * The version the user edits is very similar. Because we have historically
1103 * used commas as separators the user has always had to enter a \, in order
1104 * to put a real comma in one of the items. That is the only difference
1105 * between a PATTERN_S string and the editlist strings. Note that backslashes
1106 * themselves are not escaped. A backslash which is not followed by a comma
1107 * is a backslash. It doesn't escape the following character. That's a bit
1108 * odd, it is that way because most people will never know about this
1109 * backslash stuff but PC-Pine users may have backslashes in folder names.
1111 * The version that goes in the config file has a few transformations made.
1112 * PATTERN_S intermediate_form Config string
1113 * , \, \x2C
1114 * \ \\ \x5C
1115 * / \/
1116 * " \"
1118 * The commas are turned into hex commas so that we can tell the separators
1119 * in the comma-separated lists from those commas.
1120 * The backslashes are escaped because they escape commas.
1121 * The /'s are escaped because they separate pattern pieces.
1122 * The "'s are escaped because they are significant to parse_list when
1123 * parsing the config file.
1124 * hubert - 2004-04-01
1125 * (date is only coincidental!)
1127 * Addendum. The not's are handled separately from all the strings. Not sure
1128 * why that is or if there is a good reason. Nevertheless, now is not the
1129 * time to figure it out so leave it that way.
1130 * hubert - 2004-07-14
1132 PAT_S *
1133 parse_pat(char *str)
1135 PAT_S *pat = NULL;
1136 char *p, *q, *astr, *pstr;
1137 int backslashed;
1138 #define PTRN "pattern="
1139 #define PTRNLEN 8
1140 #define ACTN "action="
1141 #define ACTNLEN 7
1143 if(str)
1144 removing_trailing_white_space(str);
1146 if(!str || !*str || *str == '#')
1147 return(pat);
1149 pat = (PAT_S *)fs_get(sizeof(*pat));
1150 memset((void *)pat, 0, sizeof(*pat));
1152 if((p = srchstr(str, PTRN)) != NULL){
1153 pat->patgrp = (PATGRP_S *)fs_get(sizeof(*pat->patgrp));
1154 memset((void *)pat->patgrp, 0, sizeof(*pat->patgrp));
1155 pat->patgrp->fldr_type = FLDR_DEFL;
1156 pat->patgrp->inabook = IAB_DEFL;
1157 pat->patgrp->cat_lim = -1L;
1159 if((pstr = copy_quoted_string_asis(p+PTRNLEN)) != NULL){
1160 /* move to next slash */
1161 for(q=pstr, backslashed=0; *q; q++){
1162 switch(*q){
1163 case '\\':
1164 backslashed = !backslashed;
1165 break;
1167 case '/':
1168 if(!backslashed){
1169 parse_patgrp_slash(q, pat->patgrp);
1170 if(pat->patgrp->bogus && !pat->raw)
1171 pat->raw = cpystr(str);
1174 /* fall through */
1176 default:
1177 backslashed = 0;
1178 break;
1182 /* we always force a nickname */
1183 if(!pat->patgrp->nick)
1184 pat->patgrp->nick = cpystr("Alternate Role");
1186 fs_give((void **)&pstr);
1190 if((p = srchstr(str, ACTN)) != NULL){
1191 pat->action = (ACTION_S *)fs_get(sizeof(*pat->action));
1192 memset((void *)pat->action, 0, sizeof(*pat->action));
1193 pat->action->startup_rule = IS_NOTSET;
1194 pat->action->repl_type = ROLE_REPL_DEFL;
1195 pat->action->forw_type = ROLE_FORW_DEFL;
1196 pat->action->comp_type = ROLE_COMP_DEFL;
1197 pat->action->nick = cpystr((pat->patgrp && pat->patgrp->nick
1198 && pat->patgrp->nick[0])
1199 ? pat->patgrp->nick : "Alternate Role");
1201 if((astr = copy_quoted_string_asis(p+ACTNLEN)) != NULL){
1202 /* move to next slash */
1203 for(q=astr, backslashed=0; *q; q++){
1204 switch(*q){
1205 case '\\':
1206 backslashed = !backslashed;
1207 break;
1209 case '/':
1210 if(!backslashed){
1211 parse_action_slash(q, pat->action);
1212 if(pat->action->bogus && !pat->raw)
1213 pat->raw = cpystr(str);
1216 /* fall through */
1218 default:
1219 backslashed = 0;
1220 break;
1224 fs_give((void **)&astr);
1226 if(!pat->action->is_a_score)
1227 pat->action->scoreval = 0L;
1229 if(pat->action->is_a_filter)
1230 pat->action->kill = (pat->action->folder
1231 || pat->action->kill == -1) ? 0 : 1;
1232 else{
1233 if(pat->action->folder)
1234 free_pattern(&pat->action->folder);
1237 if(!pat->action->is_a_role){
1238 pat->action->repl_type = ROLE_NOTAROLE_DEFL;
1239 pat->action->forw_type = ROLE_NOTAROLE_DEFL;
1240 pat->action->comp_type = ROLE_NOTAROLE_DEFL;
1241 if(pat->action->from)
1242 mail_free_address(&pat->action->from);
1243 if(pat->action->replyto)
1244 mail_free_address(&pat->action->replyto);
1245 if(pat->action->fcc)
1246 fs_give((void **)&pat->action->fcc);
1247 if(pat->action->litsig)
1248 fs_give((void **)&pat->action->litsig);
1249 if(pat->action->sig)
1250 fs_give((void **)&pat->action->sig);
1251 if(pat->action->template)
1252 fs_give((void **)&pat->action->template);
1253 if(pat->action->cstm)
1254 free_list_array(&pat->action->cstm);
1255 if(pat->action->smtp)
1256 free_list_array(&pat->action->smtp);
1257 if(pat->action->nntp)
1258 free_list_array(&pat->action->nntp);
1259 if(pat->action->inherit_nick)
1260 fs_give((void **)&pat->action->inherit_nick);
1263 if(!pat->action->is_a_incol){
1264 if(pat->action->incol)
1265 free_color_pair(&pat->action->incol);
1268 if(!pat->action->is_a_other){
1269 pat->action->sort_is_set = 0;
1270 pat->action->sortorder = 0;
1271 pat->action->revsort = 0;
1272 pat->action->startup_rule = IS_NOTSET;
1273 if(pat->action->index_format)
1274 fs_give((void **)&pat->action->index_format);
1279 return(pat);
1284 * Fill in one member of patgrp from str.
1286 * The multiple constant strings are lame but it evolved this way from
1287 * previous versions and isn't worth fixing.
1289 void
1290 parse_patgrp_slash(char *str, PATGRP_S *patgrp)
1292 char *p;
1294 if(!patgrp)
1295 alpine_panic("NULL patgrp to parse_patgrp_slash");
1296 else if(!(str && *str)){
1297 alpine_panic("NULL or empty string to parse_patgrp_slash");
1298 patgrp->bogus = 1;
1300 else if(!strncmp(str, "/NICK=", 6))
1301 patgrp->nick = remove_pat_escapes(str+6);
1302 else if(!strncmp(str, "/COMM=", 6))
1303 patgrp->comment = remove_pat_escapes(str+6);
1304 else if(!strncmp(str, "/TO=", 4) || !strncmp(str, "/!TO=", 5))
1305 patgrp->to = parse_pattern("TO", str, 1);
1306 else if(!strncmp(str, "/CC=", 4) || !strncmp(str, "/!CC=", 5))
1307 patgrp->cc = parse_pattern("CC", str, 1);
1308 else if(!strncmp(str, "/RECIP=", 7) || !strncmp(str, "/!RECIP=", 8))
1309 patgrp->recip = parse_pattern("RECIP", str, 1);
1310 else if(!strncmp(str, "/PARTIC=", 8) || !strncmp(str, "/!PARTIC=", 9))
1311 patgrp->partic = parse_pattern("PARTIC", str, 1);
1312 else if(!strncmp(str, "/FROM=", 6) || !strncmp(str, "/!FROM=", 7))
1313 patgrp->from = parse_pattern("FROM", str, 1);
1314 else if(!strncmp(str, "/SENDER=", 8) || !strncmp(str, "/!SENDER=", 9))
1315 patgrp->sender = parse_pattern("SENDER", str, 1);
1316 else if(!strncmp(str, "/NEWS=", 6) || !strncmp(str, "/!NEWS=", 7))
1317 patgrp->news = parse_pattern("NEWS", str, 1);
1318 else if(!strncmp(str, "/SUBJ=", 6) || !strncmp(str, "/!SUBJ=", 7))
1319 patgrp->subj = parse_pattern("SUBJ", str, 1);
1320 else if(!strncmp(str, "/ALL=", 5) || !strncmp(str, "/!ALL=", 6))
1321 patgrp->alltext = parse_pattern("ALL", str, 1);
1322 else if(!strncmp(str, "/BODY=", 6) || !strncmp(str, "/!BODY=", 7))
1323 patgrp->bodytext = parse_pattern("BODY", str, 1);
1324 else if(!strncmp(str, "/KEY=", 5) || !strncmp(str, "/!KEY=", 6))
1325 patgrp->keyword = parse_pattern("KEY", str, 1);
1326 else if(!strncmp(str, "/CHAR=", 6) || !strncmp(str, "/!CHAR=", 7))
1327 patgrp->charsets = parse_pattern("CHAR", str, 1);
1328 else if(!strncmp(str, "/FOLDER=", 8) || !strncmp(str, "/!FOLDER=", 9))
1329 patgrp->folder = parse_pattern("FOLDER", str, 1);
1330 else if(!strncmp(str, "/ABOOKS=", 8) || !strncmp(str, "/!ABOOKS=", 9))
1331 patgrp->abooks = parse_pattern("ABOOKS", str, 1);
1333 * A problem with arbhdrs is that more than one of them can appear in
1334 * the string. We come back here the second time, but we already took
1335 * care of the whole thing on the first pass. Hence the check for
1336 * arbhdr already set.
1338 else if(!strncmp(str, "/ARB", 4) || !strncmp(str, "/!ARB", 5)
1339 || !strncmp(str, "/EARB", 5) || !strncmp(str, "/!EARB", 6)){
1340 if(!patgrp->arbhdr)
1341 patgrp->arbhdr = parse_arbhdr(str);
1342 /* else do nothing */
1344 else if(!strncmp(str, "/SENTDATE=", 10))
1345 patgrp->age_uses_sentdate = 1;
1346 else if(!strncmp(str, "/SCOREI=", 8)){
1347 if((p = remove_pat_escapes(str+8)) != NULL){
1348 if((patgrp->score = parse_intvl(p)) != NULL)
1349 patgrp->do_score = 1;
1351 fs_give((void **)&p);
1354 else if(!strncmp(str, "/AGE=", 5)){
1355 if((p = remove_pat_escapes(str+5)) != NULL){
1356 if((patgrp->age = parse_intvl(p)) != NULL)
1357 patgrp->do_age = 1;
1359 fs_give((void **)&p);
1362 else if(!strncmp(str, "/SIZE=", 6)){
1363 if((p = remove_pat_escapes(str+6)) != NULL){
1364 if((patgrp->size = parse_intvl(p)) != NULL)
1365 patgrp->do_size = 1;
1367 fs_give((void **)&p);
1370 else if(!strncmp(str, "/CATCMD=", 8)){
1371 if((p = remove_pat_escapes(str+8)) != NULL){
1372 int commas = 0;
1373 char *q;
1375 /* count elements in list */
1376 for(q = p; q && *q; q++)
1377 if(*q == ',')
1378 commas++;
1380 patgrp->category_cmd = parse_list(p, commas+1, PL_REMSURRQUOT,NULL);
1381 fs_give((void **)&p);
1384 else if(!strncmp(str, "/CATVAL=", 8)){
1385 if((p = remove_pat_escapes(str+8)) != NULL){
1386 if((patgrp->cat = parse_intvl(p)) != NULL)
1387 patgrp->do_cat = 1;
1389 fs_give((void **)&p);
1392 else if(!strncmp(str, "/CATLIM=", 8)){
1393 if((p = remove_pat_escapes(str+8)) != NULL){
1394 long i;
1396 i = atol(p);
1397 patgrp->cat_lim = i;
1398 fs_give((void **)&p);
1401 else if(!strncmp(str, "/FLDTYPE=", 9)){
1402 if((p = remove_pat_escapes(str+9)) != NULL){
1403 int i;
1404 NAMEVAL_S *v;
1406 for(i = 0; (v = pat_fldr_types(i)); i++)
1407 if(!strucmp(p, v->shortname)){
1408 patgrp->fldr_type = v->value;
1409 break;
1412 fs_give((void **)&p);
1415 else if(!strncmp(str, "/AFROM=", 7)){
1416 if((p = remove_pat_escapes(str+7)) != NULL){
1417 int i;
1418 NAMEVAL_S *v;
1420 for(i = 0; (v = inabook_fldr_types(i)); i++)
1421 if(!strucmp(p, v->shortname)){
1422 patgrp->inabook |= v->value;
1423 break;
1426 /* to match old semantics */
1427 patgrp->inabook |= IAB_FROM;
1428 patgrp->inabook |= IAB_REPLYTO;
1430 fs_give((void **)&p);
1433 else if(!strncmp(str, "/AFROMA=", 8)){
1434 if((p = remove_pat_escapes(str+8)) != NULL){
1436 /* make sure AFROMA comes after AFROM in config lines */
1437 patgrp->inabook &= ~IAB_ADDR_MASK;
1439 if(strchr(p, 'F'))
1440 patgrp->inabook |= IAB_FROM;
1441 if(strchr(p, 'R'))
1442 patgrp->inabook |= IAB_REPLYTO;
1443 if(strchr(p, 'S'))
1444 patgrp->inabook |= IAB_SENDER;
1445 if(strchr(p, 'T'))
1446 patgrp->inabook |= IAB_TO;
1447 if(strchr(p, 'C'))
1448 patgrp->inabook |= IAB_CC;
1450 fs_give((void **)&p);
1453 else if(!strncmp(str, "/STATN=", 7)){
1454 SET_STATUS(str,"/STATN=",patgrp->stat_new);
1456 else if(!strncmp(str, "/STATR=", 7)){
1457 SET_STATUS(str,"/STATR=",patgrp->stat_rec);
1459 else if(!strncmp(str, "/STATI=", 7)){
1460 SET_STATUS(str,"/STATI=",patgrp->stat_imp);
1462 else if(!strncmp(str, "/STATA=", 7)){
1463 SET_STATUS(str,"/STATA=",patgrp->stat_ans);
1465 else if(!strncmp(str, "/STATD=", 7)){
1466 SET_STATUS(str,"/STATD=",patgrp->stat_del);
1468 else if(!strncmp(str, "/8BITS=", 7)){
1469 SET_STATUS(str,"/8BITS=",patgrp->stat_8bitsubj);
1471 else if(!strncmp(str, "/BOM=", 5)){
1472 SET_STATUS(str,"/BOM=",patgrp->stat_bom);
1474 else if(!strncmp(str, "/BOY=", 5)){
1475 SET_STATUS(str,"/BOY=",patgrp->stat_boy);
1477 else{
1478 char save = '\0';;
1480 patgrp->bogus = 1;
1482 if((p = strindex(str, '=')) != NULL){
1483 save = *(p+1);
1484 *(p+1) = '\0';
1487 dprint((1,
1488 "parse_patgrp_slash(%.20s): unrecognized in \"%s\"\n",
1489 str ? str : "?",
1490 (patgrp && patgrp->nick) ? patgrp->nick : ""));
1491 q_status_message4(SM_ORDER, 1, 3,
1492 "Warning: unrecognized pattern element \"%.20s\"%.20s%.20s%.20s",
1493 str, patgrp->nick ? " in rule \"" : "",
1494 patgrp->nick ? patgrp->nick : "", patgrp->nick ? "\"" : "");
1496 if(p)
1497 *(p+1) = save;
1503 * Fill in one member of action struct from str.
1505 * The multiple constant strings are lame but it evolved this way from
1506 * previous versions and isn't worth fixing.
1508 void
1509 parse_action_slash(char *str, ACTION_S *action)
1511 char *p;
1512 int stateval, i;
1513 NAMEVAL_S *v;
1515 if(!action)
1516 alpine_panic("NULL action to parse_action_slash");
1517 else if(!(str && *str))
1518 alpine_panic("NULL or empty string to parse_action_slash");
1519 else if(!strncmp(str, "/ROLE=1", 7))
1520 action->is_a_role = 1;
1521 else if(!strncmp(str, "/OTHER=1", 8))
1522 action->is_a_other = 1;
1523 else if(!strncmp(str, "/ISINCOL=1", 10))
1524 action->is_a_incol = 1;
1525 else if(!strncmp(str, "/ISSRCH=1", 9))
1526 action->is_a_srch = 1;
1528 * This is unfortunate. If a new filter is set to only set
1529 * state bits it will be interpreted by an older pine which
1530 * doesn't have that feature like a filter that is set to Delete.
1531 * So we change the filter indicator to FILTER=2 to disable the
1532 * filter for older versions.
1534 else if(!strncmp(str, "/FILTER=1", 9) || !strncmp(str, "/FILTER=2", 9))
1535 action->is_a_filter = 1;
1536 else if(!strncmp(str, "/ISSCORE=1", 10))
1537 action->is_a_score = 1;
1538 else if(!strncmp(str, "/SCORE=", 7)){
1539 if((p = remove_pat_escapes(str+7)) != NULL){
1540 long i;
1542 i = atol(p);
1543 if(i >= SCORE_MIN && i <= SCORE_MAX)
1544 action->scoreval = i;
1546 fs_give((void **)&p);
1549 else if(!strncmp(str, "/SCOREHDRTOK=", 13))
1550 action->scorevalhdrtok = config_to_hdrtok(str+13);
1551 else if(!strncmp(str, "/FOLDER=", 8))
1552 action->folder = parse_pattern("FOLDER", str, 1);
1553 else if(!strncmp(str, "/KEYSET=", 8))
1554 action->keyword_set = parse_pattern("KEYSET", str, 1);
1555 else if(!strncmp(str, "/KEYCLR=", 8))
1556 action->keyword_clr = parse_pattern("KEYCLR", str, 1);
1557 else if(!strncmp(str, "/NOKILL=", 8))
1558 action->kill = -1;
1559 else if(!strncmp(str, "/NOTDEL=", 8))
1560 action->move_only_if_not_deleted = 1;
1561 else if(!strncmp(str, "/NONTERM=", 9))
1562 action->non_terminating = 1;
1563 else if(!strncmp(str, "/STATI=", 7)){
1564 stateval = ACT_STAT_LEAVE;
1565 SET_MSGSTATE(str,"/STATI=",stateval);
1566 switch(stateval){
1567 case ACT_STAT_LEAVE:
1568 break;
1569 case ACT_STAT_SET:
1570 action->state_setting_bits |= F_FLAG;
1571 break;
1572 case ACT_STAT_CLEAR:
1573 action->state_setting_bits |= F_UNFLAG;
1574 break;
1577 else if(!strncmp(str, "/STATD=", 7)){
1578 stateval = ACT_STAT_LEAVE;
1579 SET_MSGSTATE(str,"/STATD=",stateval);
1580 switch(stateval){
1581 case ACT_STAT_LEAVE:
1582 break;
1583 case ACT_STAT_SET:
1584 action->state_setting_bits |= F_DEL;
1585 break;
1586 case ACT_STAT_CLEAR:
1587 action->state_setting_bits |= F_UNDEL;
1588 break;
1591 else if(!strncmp(str, "/STATA=", 7)){
1592 stateval = ACT_STAT_LEAVE;
1593 SET_MSGSTATE(str,"/STATA=",stateval);
1594 switch(stateval){
1595 case ACT_STAT_LEAVE:
1596 break;
1597 case ACT_STAT_SET:
1598 action->state_setting_bits |= F_ANS;
1599 break;
1600 case ACT_STAT_CLEAR:
1601 action->state_setting_bits |= F_UNANS;
1602 break;
1605 else if(!strncmp(str, "/STATN=", 7)){
1606 stateval = ACT_STAT_LEAVE;
1607 SET_MSGSTATE(str,"/STATN=",stateval);
1608 switch(stateval){
1609 case ACT_STAT_LEAVE:
1610 break;
1611 case ACT_STAT_SET:
1612 action->state_setting_bits |= F_UNSEEN;
1613 break;
1614 case ACT_STAT_CLEAR:
1615 action->state_setting_bits |= F_SEEN;
1616 break;
1619 else if(!strncmp(str, "/RTYPE=", 7)){
1620 /* reply type */
1621 action->repl_type = ROLE_REPL_DEFL;
1622 if((p = remove_pat_escapes(str+7)) != NULL){
1623 for(i = 0; (v = role_repl_types(i)); i++)
1624 if(!strucmp(p, v->shortname)){
1625 action->repl_type = v->value;
1626 break;
1629 fs_give((void **)&p);
1632 else if(!strncmp(str, "/FTYPE=", 7)){
1633 /* forward type */
1634 action->forw_type = ROLE_FORW_DEFL;
1635 if((p = remove_pat_escapes(str+7)) != NULL){
1636 for(i = 0; (v = role_forw_types(i)); i++)
1637 if(!strucmp(p, v->shortname)){
1638 action->forw_type = v->value;
1639 break;
1642 fs_give((void **)&p);
1645 else if(!strncmp(str, "/CTYPE=", 7)){
1646 /* compose type */
1647 action->comp_type = ROLE_COMP_DEFL;
1648 if((p = remove_pat_escapes(str+7)) != NULL){
1649 for(i = 0; (v = role_comp_types(i)); i++)
1650 if(!strucmp(p, v->shortname)){
1651 action->comp_type = v->value;
1652 break;
1655 fs_give((void **)&p);
1658 else if(!strncmp(str, "/FROM=", 6)){
1659 /* get the from */
1660 if((p = remove_pat_escapes(str+6)) != NULL){
1661 rfc822_parse_adrlist(&action->from, p,
1662 ps_global->maildomain);
1663 fs_give((void **)&p);
1666 else if(!strncmp(str, "/REPL=", 6)){
1667 /* get the reply-to */
1668 if((p = remove_pat_escapes(str+6)) != NULL){
1669 rfc822_parse_adrlist(&action->replyto, p,
1670 ps_global->maildomain);
1671 fs_give((void **)&p);
1674 else if(!strncmp(str, "/FCC=", 5))
1675 action->fcc = remove_pat_escapes(str+5);
1676 else if(!strncmp(str, "/LSIG=", 6))
1677 action->litsig = remove_pat_escapes(str+6);
1678 else if(!strncmp(str, "/SIG=", 5))
1679 action->sig = remove_pat_escapes(str+5);
1680 else if(!strncmp(str, "/TEMPLATE=", 10))
1681 action->template = remove_pat_escapes(str+10);
1682 /* get the custom headers */
1683 else if(!strncmp(str, "/CSTM=", 6)){
1684 if((p = remove_pat_escapes(str+6)) != NULL){
1685 int commas = 0;
1686 char *q;
1688 /* count elements in list */
1689 for(q = p; q && *q; q++)
1690 if(*q == ',')
1691 commas++;
1693 action->cstm = parse_list(p, commas+1, 0, NULL);
1694 fs_give((void **)&p);
1697 else if(!strncmp(str, "/SMTP=", 6)){
1698 if((p = remove_pat_escapes(str+6)) != NULL){
1699 int commas = 0;
1700 char *q;
1702 /* count elements in list */
1703 for(q = p; q && *q; q++)
1704 if(*q == ',')
1705 commas++;
1707 action->smtp = parse_list(p, commas+1, PL_REMSURRQUOT, NULL);
1708 fs_give((void **)&p);
1711 else if(!strncmp(str, "/NNTP=", 6)){
1712 if((p = remove_pat_escapes(str+6)) != NULL){
1713 int commas = 0;
1714 char *q;
1716 /* count elements in list */
1717 for(q = p; q && *q; q++)
1718 if(*q == ',')
1719 commas++;
1721 action->nntp = parse_list(p, commas+1, PL_REMSURRQUOT, NULL);
1722 fs_give((void **)&p);
1725 else if(!strncmp(str, "/INICK=", 7))
1726 action->inherit_nick = remove_pat_escapes(str+7);
1727 else if(!strncmp(str, "/INCOL=", 7)){
1728 if((p = remove_pat_escapes(str+7)) != NULL){
1729 char *fg = NULL, *bg = NULL, *z;
1732 * Color should look like
1733 * /FG=white/BG=red
1735 if((z = srchstr(p, "/FG=")) != NULL)
1736 fg = remove_pat_escapes(z+4);
1737 if((z = srchstr(p, "/BG=")) != NULL)
1738 bg = remove_pat_escapes(z+4);
1740 if(fg && *fg && bg && *bg)
1741 action->incol = new_color_pair(fg, bg);
1743 if(fg)
1744 fs_give((void **)&fg);
1745 if(bg)
1746 fs_give((void **)&bg);
1747 fs_give((void **)&p);
1750 /* per-folder sort */
1751 else if(!strncmp(str, "/SORT=", 6)){
1752 if((p = remove_pat_escapes(str+6)) != NULL){
1753 SortOrder def_sort;
1754 int def_sort_rev;
1756 if(decode_sort(p, &def_sort, &def_sort_rev) != -1){
1757 action->sort_is_set = 1;
1758 action->sortorder = def_sort;
1759 action->revsort = (def_sort_rev ? 1 : 0);
1762 fs_give((void **)&p);
1765 /* per-folder index-format */
1766 else if(!strncmp(str, "/IFORM=", 7))
1767 action->index_format = remove_pat_escapes(str+7);
1768 /* per-folder startup-rule */
1769 else if(!strncmp(str, "/START=", 7)){
1770 if((p = remove_pat_escapes(str+7)) != NULL){
1771 for(i = 0; (v = startup_rules(i)); i++)
1772 if(!strucmp(p, S_OR_L(v))){
1773 action->startup_rule = v->value;
1774 break;
1777 fs_give((void **)&p);
1780 else{
1781 char save = '\0';
1783 action->bogus = 1;
1785 if((p = strindex(str, '=')) != NULL){
1786 save = *(p+1);
1787 *(p+1) = '\0';
1790 dprint((1,
1791 "parse_action_slash(%.20s): unrecognized in \"%s\"\n",
1792 str ? str : "?",
1793 (action && action->nick) ? action->nick : ""));
1794 q_status_message4(SM_ORDER, 1, 3,
1795 "Warning: unrecognized pattern action \"%.20s\"%.20s%.20s%.20s",
1796 str, action->nick ? " in rule \"" : "",
1797 action->nick ? action->nick : "", action->nick ? "\"" : "");
1799 if(p)
1800 *(p+1) = save;
1806 * Str looks like (min,max) or a comma-separated list of these.
1808 * Parens are optional if unambiguous, whitespace is ignored.
1809 * If min is left out it is -INF. If max is left out it is INF.
1810 * If only one number and no comma number is min and max is INF.
1812 * Returns the INTVL_S list.
1814 INTVL_S *
1815 parse_intvl(char *str)
1817 char *q;
1818 long left, right;
1819 INTVL_S *ret = NULL, **next = NULL;
1821 if(!str)
1822 return(ret);
1824 q = str;
1826 for(;;){
1827 left = right = INTVL_UNDEF;
1829 /* skip to first number */
1830 while(isspace((unsigned char) *q) || *q == LPAREN)
1831 q++;
1833 /* min number */
1834 if(*q == COMMA || !struncmp(q, "-INF", 4))
1835 left = - INTVL_INF;
1836 else if(*q == '-' || isdigit((unsigned char) *q))
1837 left = atol(q);
1839 if(left != INTVL_UNDEF){
1840 /* skip to second number */
1841 while(*q && *q != COMMA && *q != RPAREN)
1842 q++;
1843 if(*q == COMMA)
1844 q++;
1845 while(isspace((unsigned char) *q))
1846 q++;
1848 /* max number */
1849 if(*q == '\0' || *q == RPAREN || !struncmp(q, "INF", 3))
1850 right = INTVL_INF;
1851 else if(*q == '-' || isdigit((unsigned char) *q))
1852 right = atol(q);
1855 if(left == INTVL_UNDEF || right == INTVL_UNDEF
1856 || left > right){
1857 if(left != INTVL_UNDEF || right != INTVL_UNDEF || *q){
1858 if(left != INTVL_UNDEF && right != INTVL_UNDEF
1859 && left > right)
1860 q_status_message1(SM_ORDER, 3, 5,
1861 _("Error: Interval \"%s\", min > max"), str);
1862 else
1863 q_status_message1(SM_ORDER, 3, 5,
1864 _("Error: Interval \"%s\": syntax is (min,max)"), str);
1866 if(ret)
1867 free_intvl(&ret);
1869 ret = NULL;
1872 break;
1874 else{
1875 if(!ret){
1876 ret = (INTVL_S *) fs_get(sizeof(*ret));
1877 memset((void *) ret, 0, sizeof(*ret));
1878 ret->imin = left;
1879 ret->imax = right;
1880 next = &ret->next;
1882 else{
1883 *next = (INTVL_S *) fs_get(sizeof(*ret));
1884 memset((void *) *next, 0, sizeof(*ret));
1885 (*next)->imin = left;
1886 (*next)->imax = right;
1887 next = &(*next)->next;
1890 /* skip to next interval in list */
1891 while(*q && *q != COMMA && *q != RPAREN)
1892 q++;
1893 if(*q == RPAREN)
1894 q++;
1895 while(*q && *q != COMMA)
1896 q++;
1897 if(*q == COMMA)
1898 q++;
1902 return(ret);
1907 * Returns string that looks like "(left,right),(left2,right2)".
1908 * Caller is responsible for freeing memory.
1910 char *
1911 stringform_of_intvl(INTVL_S *intvl)
1913 char *res = NULL;
1915 if(intvl && intvl->imin != INTVL_UNDEF && intvl->imax != INTVL_UNDEF
1916 && intvl->imin <= intvl->imax){
1917 char lbuf[20], rbuf[20], buf[45], *p;
1918 INTVL_S *iv;
1919 int count = 0;
1920 size_t reslen;
1922 /* find a max size and allocate it for the result */
1923 for(iv = intvl;
1924 (iv && iv->imin != INTVL_UNDEF && iv->imax != INTVL_UNDEF
1925 && iv->imin <= iv->imax);
1926 iv = iv->next)
1927 count++;
1929 reslen = count * 50 * sizeof(char);
1930 res = (char *) fs_get(reslen+1);
1931 memset((void *) res, 0, reslen+1);
1932 p = res;
1934 for(iv = intvl;
1935 (iv && iv->imin != INTVL_UNDEF && iv->imax != INTVL_UNDEF
1936 && iv->imin <= iv->imax);
1937 iv = iv->next){
1939 if(iv->imin == - INTVL_INF){
1940 strncpy(lbuf, "-INF", sizeof(lbuf));
1941 lbuf[sizeof(lbuf)-1] = '\0';
1943 else
1944 snprintf(lbuf, sizeof(lbuf), "%ld", iv->imin);
1946 if(iv->imax == INTVL_INF){
1947 strncpy(rbuf, "INF", sizeof(rbuf));
1948 rbuf[sizeof(rbuf)-1] = '\0';
1950 else
1951 snprintf(rbuf, sizeof(rbuf), "%ld", iv->imax);
1953 snprintf(buf, sizeof(buf), "%.1s(%.20s,%.20s)", (p == res) ? "" : ",",
1954 lbuf, rbuf);
1956 sstrncpy(&p, buf, reslen+1 -(p-res));
1959 res[reslen] = '\0';
1962 return(res);
1966 char *
1967 hdrtok_to_stringform(HEADER_TOK_S *hdrtok)
1969 char buf[1024], nbuf[10];
1970 char *res = NULL;
1971 char *p, *ptr;
1973 if(hdrtok){
1974 snprintf(nbuf, sizeof(nbuf), "%d", hdrtok->fieldnum);
1975 ptr = buf;
1976 sstrncpy(&ptr, hdrtok->hdrname ? hdrtok->hdrname : "", sizeof(buf)-(ptr-buf));
1977 sstrncpy(&ptr, "(", sizeof(buf)-(ptr-buf));
1978 sstrncpy(&ptr, nbuf, sizeof(buf)-(ptr-buf));
1979 sstrncpy(&ptr, ",\"", sizeof(buf)-(ptr-buf));
1980 p = hdrtok->fieldseps;
1981 while(p && *p){
1982 if((*p == '\"' || *p == '\\') && ptr-buf < sizeof(buf))
1983 *ptr++ = '\\';
1985 if(ptr-buf < sizeof(buf))
1986 *ptr++ = *p++;
1989 sstrncpy(&ptr, "\")", sizeof(buf)-(ptr-buf));
1991 if(ptr-buf < sizeof(buf))
1992 *ptr = '\0';
1993 else
1994 buf[sizeof(buf)-1] = '\0';
1996 res = cpystr(buf);
1999 return(res);
2003 HEADER_TOK_S *
2004 stringform_to_hdrtok(char *str)
2006 char *p, *q, *w, hdrname[200];
2007 HEADER_TOK_S *hdrtok = NULL;
2009 if(str && *str){
2010 p = str;
2011 hdrname[0] = '\0';
2012 w = hdrname;
2014 if(*p == '\"'){ /* quoted name */
2015 p++;
2016 while(w < hdrname + sizeof(hdrname)-1 && *p != '\"'){
2017 if(*p == '\\')
2018 p++;
2020 *w++ = *p++;
2023 *w = '\0';
2024 if(*p == '\"')
2025 p++;
2027 else{
2028 while(w < hdrname + sizeof(hdrname)-1 &&
2029 !(!(*p & 0x80) && isspace((unsigned char)*p)) &&
2030 *p != '(')
2031 *w++ = *p++;
2033 *w = '\0';
2036 if(hdrname[0])
2037 hdrtok = new_hdrtok(hdrname);
2039 if(hdrtok){
2040 if(*p == '('){
2041 p++;
2043 if(*p && isdigit((unsigned char) *p)){
2044 q = p;
2045 while(*p && isdigit((unsigned char) *p))
2046 p++;
2048 hdrtok->fieldnum = atoi(q);
2050 if(*p == ','){
2051 int j;
2053 p++;
2054 /* don't use default */
2055 if(*p && *p != ')' && hdrtok->fieldseps){
2056 hdrtok->fieldseps[0] = '\0';
2057 hdrtok->fieldsepcnt = 0;
2060 j = 0;
2061 if(*p == '\"' && strchr(p+1, '\"')){ /* quoted */
2062 p++;
2063 while(*p && *p != '\"'){
2064 if(hdrtok->fieldseps)
2065 fs_resize((void **) &hdrtok->fieldseps, j+2);
2067 if(*p == '\\' && *(p+1))
2068 p++;
2070 if(hdrtok->fieldseps){
2071 hdrtok->fieldseps[j++] = *p++;
2072 hdrtok->fieldseps[j] = '\0';
2073 hdrtok->fieldsepcnt = j;
2077 else{
2078 while(*p && *p != ')'){
2079 if(hdrtok->fieldseps)
2080 fs_resize((void **) &hdrtok->fieldseps, j+2);
2082 if(*p == '\\' && *(p+1))
2083 p++;
2085 if(hdrtok->fieldseps){
2086 hdrtok->fieldseps[j++] = *p++;
2087 hdrtok->fieldseps[j] = '\0';
2088 hdrtok->fieldsepcnt = j;
2093 else{
2094 q_status_message(SM_ORDER | SM_DING, 3, 3, "Missing 2nd argument should be field number, a non-negative digit");
2097 else{
2098 q_status_message(SM_ORDER | SM_DING, 3, 3, "1st argument should be field number, a non-negative digit");
2101 else{
2102 q_status_message1(SM_ORDER | SM_DING, 3, 3, "Missing left parenthesis in %s", str);
2105 else{
2106 q_status_message1(SM_ORDER | SM_DING, 3, 3, "Missing header name in %s", str);
2110 return(hdrtok);
2114 char *
2115 hdrtok_to_config(HEADER_TOK_S *hdrtok)
2117 char *ptr, buf[1024], nbuf[10], *p1, *p2, *p3;
2118 char *res = NULL;
2120 if(hdrtok){
2121 snprintf(nbuf, sizeof(nbuf), "%d", hdrtok->fieldnum);
2122 memset(buf, 0, sizeof(buf));
2123 ptr = buf;
2124 sstrncpy(&ptr, "/HN=", sizeof(buf)-(ptr-buf));
2125 sstrncpy(&ptr, (p1=add_pat_escapes(hdrtok->hdrname ? hdrtok->hdrname : "")), sizeof(buf)-(ptr-buf));
2126 sstrncpy(&ptr, "/FN=", sizeof(buf)-(ptr-buf));
2127 sstrncpy(&ptr, (p2=add_pat_escapes(nbuf)), sizeof(buf)-(ptr-buf));
2128 sstrncpy(&ptr, "/FS=", sizeof(buf)-(ptr-buf));
2129 sstrncpy(&ptr, (p3=add_pat_escapes(hdrtok->fieldseps ? hdrtok->fieldseps : "")), sizeof(buf)-(ptr-buf));
2131 buf[sizeof(buf)-1] = '\0';
2132 res = add_pat_escapes(buf);
2134 if(p1)
2135 fs_give((void **)&p1);
2137 if(p2)
2138 fs_give((void **)&p2);
2140 if(p3)
2141 fs_give((void **)&p3);
2144 return(res);
2148 HEADER_TOK_S *
2149 config_to_hdrtok(char *str)
2151 HEADER_TOK_S *hdrtok = NULL;
2152 char *p, *q;
2153 int j;
2155 if(str && *str){
2156 if((q = remove_pat_escapes(str)) != NULL){
2157 char *hn = NULL, *fn = NULL, *fs = NULL, *z;
2159 if((z = srchstr(q, "/HN=")) != NULL)
2160 hn = remove_pat_escapes(z+4);
2161 if((z = srchstr(q, "/FN=")) != NULL)
2162 fn = remove_pat_escapes(z+4);
2163 if((z = srchstr(q, "/FS=")) != NULL)
2164 fs = remove_pat_escapes(z+4);
2166 hdrtok = new_hdrtok(hn);
2167 if(fn)
2168 hdrtok->fieldnum = atoi(fn);
2170 if(fs && *fs){
2171 if(hdrtok->fieldseps){
2172 hdrtok->fieldseps[0] = '\0';
2173 hdrtok->fieldsepcnt = 0;
2176 p = fs;
2177 j = 0;
2178 if(*p == '\"' && strchr(p+1, '\"')){
2179 p++;
2180 while(*p && *p != '\"'){
2181 if(hdrtok->fieldseps)
2182 fs_resize((void **) &hdrtok->fieldseps, j+2);
2184 if(*p == '\\' && *(p+1))
2185 p++;
2187 if(hdrtok->fieldseps){
2188 hdrtok->fieldseps[j++] = *p++;
2189 hdrtok->fieldseps[j] = '\0';
2190 hdrtok->fieldsepcnt = j;
2194 else{
2195 while(*p){
2196 if(hdrtok->fieldseps)
2197 fs_resize((void **) &hdrtok->fieldseps, j+2);
2199 if(*p == '\\' && *(p+1))
2200 p++;
2202 if(hdrtok->fieldseps){
2203 hdrtok->fieldseps[j++] = *p++;
2204 hdrtok->fieldseps[j] = '\0';
2205 hdrtok->fieldsepcnt = j;
2211 if(hn)
2212 fs_give((void **)&hn);
2213 if(fn)
2214 fs_give((void **)&fn);
2215 if(fs)
2216 fs_give((void **)&fs);
2218 fs_give((void **)&q);
2222 return(hdrtok);
2227 * Args -- flags - SCOREUSE_INVALID Mark scores_in_use invalid so that we'll
2228 * recalculate if we want to use it again.
2229 * - SCOREUSE_GET Return whether scores are being used or not.
2231 * Returns -- 0 - Scores not being used at all.
2232 * >0 - Scores are used. The return value consists of flag values
2233 * OR'd together. Possible values are:
2235 * SCOREUSE_INCOLS - scores needed for index line colors
2236 * SCOREUSE_ROLES - scores needed for roles
2237 * SCOREUSE_FILTERS - scores needed for filters
2238 * SCOREUSE_OTHER - scores needed for other stuff
2239 * SCOREUSE_INDEX - scores needed for index drawing
2241 * SCOREUSE_STATEDEP - scores depend on message state
2244 scores_are_used(int flags)
2246 static int scores_in_use = -1;
2247 long type1, type2;
2248 int scores_are_defined, scores_are_used_somewhere = 0;
2249 PAT_STATE pstate1, pstate2;
2251 if(flags & SCOREUSE_INVALID) /* mark invalid so we recalculate next time */
2252 scores_in_use = -1;
2253 else if(scores_in_use == -1){
2256 * Check the patterns to see if scores are potentially
2257 * being used.
2258 * The first_pattern() in the if checks whether there are any
2259 * non-zero scorevals. The loop checks whether any patterns
2260 * use those non-zero scorevals.
2262 type1 = ROLE_SCORE;
2263 type2 = (ROLE_REPLY | ROLE_FORWARD | ROLE_COMPOSE |
2264 ROLE_INCOL | ROLE_DO_FILTER);
2265 scores_are_defined = nonempty_patterns(type1, &pstate1)
2266 && first_pattern(&pstate1);
2267 if(scores_are_defined)
2268 scores_are_used_somewhere =
2269 ((nonempty_patterns(type2, &pstate2) && first_pattern(&pstate2))
2270 || ps_global->a_format_contains_score
2271 || mn_get_sort(ps_global->msgmap) == SortScore);
2273 if(scores_are_used_somewhere){
2274 PAT_S *pat;
2277 * Careful. nonempty_patterns() may call close_pattern()
2278 * which will set scores_in_use to -1! So we have to be
2279 * sure to reset it after we call nonempty_patterns().
2281 scores_in_use = 0;
2282 if(ps_global->a_format_contains_score
2283 || mn_get_sort(ps_global->msgmap) == SortScore)
2284 scores_in_use |= SCOREUSE_INDEX;
2286 if(nonempty_patterns(type2, &pstate2))
2287 for(pat = first_pattern(&pstate2);
2288 pat;
2289 pat = next_pattern(&pstate2))
2290 if(pat->patgrp && !pat->patgrp->bogus && pat->patgrp->do_score){
2291 if(pat->action && pat->action->is_a_incol)
2292 scores_in_use |= SCOREUSE_INCOLS;
2293 if(pat->action && pat->action->is_a_role)
2294 scores_in_use |= SCOREUSE_ROLES;
2295 if(pat->action && pat->action->is_a_filter)
2296 scores_in_use |= SCOREUSE_FILTERS;
2297 if(pat->action && pat->action->is_a_other)
2298 scores_in_use |= SCOREUSE_OTHER;
2302 * Note whether scores depend on message state or not.
2304 if(scores_in_use)
2305 for(pat = first_pattern(&pstate1);
2306 pat;
2307 pat = next_pattern(&pstate1))
2308 if(patgrp_depends_on_active_state(pat->patgrp)){
2309 scores_in_use |= SCOREUSE_STATEDEP;
2310 break;
2314 else
2315 scores_in_use = 0;
2318 return((scores_in_use == -1) ? 0 : scores_in_use);
2323 patgrp_depends_on_state(PATGRP_S *patgrp)
2325 return(patgrp && (patgrp_depends_on_active_state(patgrp)
2326 || patgrp->stat_rec != PAT_STAT_EITHER));
2331 * Recent doesn't count for this function because it doesn't change while
2332 * the mailbox is open.
2335 patgrp_depends_on_active_state(PATGRP_S *patgrp)
2337 return(patgrp && !patgrp->bogus
2338 && (patgrp->stat_new != PAT_STAT_EITHER ||
2339 patgrp->stat_del != PAT_STAT_EITHER ||
2340 patgrp->stat_imp != PAT_STAT_EITHER ||
2341 patgrp->stat_ans != PAT_STAT_EITHER ||
2342 patgrp->keyword));
2347 * Look for label in str and return a pointer to parsed string.
2348 * Actually, we look for "label=" or "!label=", the second means NOT.
2349 * Converts from string from patterns file which looks like
2350 * /NEWS=comp.mail.,comp.mail.pine/TO=...
2351 * This is the string that came from pattern="string" with the pattern=
2352 * and outer quotes removed.
2353 * This converts the string to a PATTERN_S list and returns
2354 * an allocated copy.
2356 PATTERN_S *
2357 parse_pattern(char *label, char *str, int hex_to_backslashed)
2359 char copy[50]; /* local copy of label */
2360 char copynot[50]; /* local copy of label, NOT'ed */
2361 char *q, *labeled_str;
2362 PATTERN_S *head = NULL;
2364 if(!label || !str)
2365 return(NULL);
2367 q = copy;
2368 sstrncpy(&q, "/", sizeof(copy));
2369 sstrncpy(&q, label, sizeof(copy) - (q-copy));
2370 sstrncpy(&q, "=", sizeof(copy) - (q-copy));
2371 copy[sizeof(copy)-1] = '\0';
2372 q = copynot;
2373 sstrncpy(&q, "/!", sizeof(copynot));
2374 sstrncpy(&q, label, sizeof(copynot) - (q-copynot));
2375 sstrncpy(&q, "=", sizeof(copynot) - (q-copynot));
2376 copynot[sizeof(copynot)-1] = '\0';
2378 if(hex_to_backslashed){
2379 if((q = srchstr(str, copy)) != NULL){
2380 head = config_to_pattern(q+strlen(copy));
2382 else if((q = srchstr(str, copynot)) != NULL){
2383 head = config_to_pattern(q+strlen(copynot));
2384 head->not = 1;
2387 else{
2388 if((q = srchstr(str, copy)) != NULL){
2389 if((labeled_str =
2390 remove_backslash_escapes(q+strlen(copy))) != NULL){
2391 head = string_to_pattern(labeled_str);
2392 fs_give((void **)&labeled_str);
2395 else if((q = srchstr(str, copynot)) != NULL){
2396 if((labeled_str =
2397 remove_backslash_escapes(q+strlen(copynot))) != NULL){
2398 head = string_to_pattern(labeled_str);
2399 head->not = 1;
2400 fs_give((void **)&labeled_str);
2405 return(head);
2410 * Look for /ARB's in str and return a pointer to parsed ARBHDR_S.
2411 * Actually, we look for /!ARB and /!EARB as well. Those mean NOT.
2412 * Converts from string from patterns file which looks like
2413 * /ARB<fieldname1>=pattern/.../ARB<fieldname2>=pattern...
2414 * This is the string that came from pattern="string" with the pattern=
2415 * and outer quotes removed.
2416 * This converts the string to a ARBHDR_S list and returns
2417 * an allocated copy.
2419 ARBHDR_S *
2420 parse_arbhdr(char *str)
2422 char *q, *s, *equals, *noesc;
2423 int not, empty, skip;
2424 ARBHDR_S *ahdr = NULL, *a, *aa;
2425 PATTERN_S *p = NULL;
2427 if(!str)
2428 return(NULL);
2430 aa = NULL;
2431 for(s = str; (q = next_arb(s)); s = q+1){
2432 not = (q[1] == '!') ? 1 : 0;
2433 empty = (q[not+1] == 'E') ? 1 : 0;
2434 skip = 4 + not + empty;
2435 if((noesc = remove_pat_escapes(q+skip)) != NULL){
2436 if(*noesc != '=' && (equals = strindex(noesc, '=')) != NULL){
2437 a = (ARBHDR_S *)fs_get(sizeof(*a));
2438 memset((void *)a, 0, sizeof(*a));
2439 *equals = '\0';
2440 a->isemptyval = empty;
2441 a->field = cpystr(noesc);
2442 if(empty)
2443 a->p = string_to_pattern("");
2444 else if(*(equals+1) &&
2445 (p = string_to_pattern(equals+1)) != NULL)
2446 a->p = p;
2448 if(not && a->p)
2449 a->p->not = 1;
2451 /* keep them in the same order */
2452 if(aa){
2453 aa->next = a;
2454 aa = aa->next;
2456 else{
2457 ahdr = a;
2458 aa = ahdr;
2462 fs_give((void **)&noesc);
2466 return(ahdr);
2470 char *
2471 next_arb(char *start)
2473 char *q1, *q2, *q3, *q4, *p;
2475 q1 = srchstr(start, "/ARB");
2476 q2 = srchstr(start, "/!ARB");
2477 q3 = srchstr(start, "/EARB");
2478 q4 = srchstr(start, "/!EARB");
2480 p = q1;
2481 if(!p || (q2 && q2 < p))
2482 p = q2;
2483 if(!p || (q3 && q3 < p))
2484 p = q3;
2485 if(!p || (q4 && q4 < p))
2486 p = q4;
2488 return(p);
2493 * Converts a string to a PATTERN_S list and returns an
2494 * allocated copy. The source string looks like
2495 * string1,string2,...
2496 * Commas and backslashes may be backslash-escaped in the original string
2497 * in order to include actual commas and backslashes in the pattern.
2498 * So \, is an actual comma and , is the separator character.
2500 PATTERN_S *
2501 string_to_pattern(char *str)
2503 char *q, *s, *workspace;
2504 PATTERN_S *p, *head = NULL, **nextp;
2506 if(!str)
2507 return(head);
2510 * We want an empty string to cause an empty substring in the pattern
2511 * instead of returning a NULL pattern. That can be used as a way to
2512 * match any header. For example, if all the patterns but the news
2513 * pattern were null and the news pattern was a substring of "" then
2514 * we use that to match any message with a newsgroups header.
2516 if(!*str){
2517 head = (PATTERN_S *)fs_get(sizeof(*p));
2518 memset((void *)head, 0, sizeof(*head));
2519 head->substring = cpystr("");
2521 else{
2522 nextp = &head;
2523 workspace = (char *)fs_get((strlen(str)+1) * sizeof(char));
2524 s = workspace;
2525 *s = '\0';
2526 q = str;
2527 do {
2528 switch(*q){
2529 case COMMA:
2530 case '\0':
2531 *s = '\0';
2532 removing_leading_and_trailing_white_space(workspace);
2533 p = (PATTERN_S *)fs_get(sizeof(*p));
2534 memset((void *)p, 0, sizeof(*p));
2535 p->substring = cpystr(workspace);
2537 convert_possibly_encoded_str_to_utf8(&p->substring);
2539 *nextp = p;
2540 nextp = &p->next;
2541 s = workspace;
2542 *s = '\0';
2543 break;
2545 case BSLASH:
2546 if(*(q+1) == COMMA || *(q+1) == BSLASH)
2547 *s++ = *(++q);
2548 else
2549 *s++ = *q;
2551 break;
2553 default:
2554 *s++ = *q;
2555 break;
2557 } while(*q++);
2559 fs_give((void **)&workspace);
2562 return(head);
2567 * Converts a PATTERN_S list to a string.
2568 * The resulting string is allocated here and looks like
2569 * string1,string2,...
2570 * Commas and backslashes in the original pattern
2571 * end up backslash-escaped in the string.
2573 char *
2574 pattern_to_string(PATTERN_S *pattern)
2576 PATTERN_S *p;
2577 char *result = NULL, *q, *s;
2578 size_t n;
2580 if(!pattern)
2581 return(result);
2583 /* how much space is needed? */
2584 n = 0;
2585 for(p = pattern; p; p = p->next){
2586 n += (p == pattern) ? 0 : 1;
2587 for(s = p->substring; s && *s; s++){
2588 if(*s == COMMA || *s == BSLASH)
2589 n++;
2591 n++;
2595 q = result = (char *)fs_get(++n);
2596 for(p = pattern; p; p = p->next){
2597 if(p != pattern)
2598 *q++ = COMMA;
2600 for(s = p->substring; s && *s; s++){
2601 if(*s == COMMA || *s == BSLASH)
2602 *q++ = '\\';
2604 *q++ = *s;
2608 *q = '\0';
2610 return(result);
2615 * Do the escaping necessary to take a string for a pattern into a comma-
2616 * separated string with escapes suitable for the config file.
2617 * Returns an allocated copy of that string.
2618 * In particular
2619 * , -> \, -> \x2C
2620 * \ -> \\ -> \x5C
2621 * " -> \"
2622 * / -> \/
2624 char *
2625 pattern_to_config(PATTERN_S *pat)
2627 char *s, *res = NULL;
2629 s = pattern_to_string(pat);
2630 if(s){
2631 res = add_pat_escapes(s);
2632 fs_give((void **) &s);
2635 return(res);
2639 * Opposite of pattern_to_config.
2641 PATTERN_S *
2642 config_to_pattern(char *str)
2644 char *s;
2645 PATTERN_S *pat = NULL;
2647 s = remove_pat_escapes(str);
2648 if(s){
2649 pat = string_to_pattern(s);
2650 fs_give((void **) &s);
2653 return(pat);
2658 * Converts an array of strings to a PATTERN_S list and returns an
2659 * allocated copy.
2660 * The list strings may not contain commas directly, because the UI turns
2661 * those into separate list members. Instead, the user types \, and
2662 * that backslash comma is converted to a comma here.
2663 * It is a bit odd. Backslash itself is not escaped. A backslash which is
2664 * not followed by a comma is a literal backslash, a backslash followed by
2665 * a comma is a comma.
2667 PATTERN_S *
2668 editlist_to_pattern(char **list)
2670 PATTERN_S *head = NULL;
2672 if(!(list && *list))
2673 return(head);
2676 * We want an empty string to cause an empty substring in the pattern
2677 * instead of returning a NULL pattern. That can be used as a way to
2678 * match any header. For example, if all the patterns but the news
2679 * pattern were null and the news pattern was a substring of "" then
2680 * we use that to match any message with a newsgroups header.
2682 if(!list[0][0]){
2683 head = (PATTERN_S *) fs_get(sizeof(*head));
2684 memset((void *) head, 0, sizeof(*head));
2685 head->substring = cpystr("");
2687 else{
2688 char *str, *s, *q, *workspace = NULL;
2689 size_t l = 0;
2690 PATTERN_S *p, **nextp;
2691 int i;
2693 nextp = &head;
2694 for(i = 0; (str = list[i]); i++){
2695 if(str[0]){
2696 if(!workspace){
2697 l = strlen(str) + 1;
2698 workspace = (char *) fs_get(l * sizeof(char));
2700 else if(strlen(str) + 1 > l){
2701 l = strlen(str) + 1;
2702 fs_give((void **) &workspace);
2703 workspace = (char *) fs_get(l * sizeof(char));
2706 s = workspace;
2707 *s = '\0';
2708 q = str;
2709 do {
2710 switch(*q){
2711 case '\0':
2712 *s = '\0';
2713 removing_leading_and_trailing_white_space(workspace);
2714 p = (PATTERN_S *) fs_get(sizeof(*p));
2715 memset((void *) p, 0, sizeof(*p));
2716 p->substring = cpystr(workspace);
2717 *nextp = p;
2718 nextp = &p->next;
2719 s = workspace;
2720 *s = '\0';
2721 break;
2723 case BSLASH:
2724 if(*(q+1) == COMMA)
2725 *s++ = *(++q);
2726 else
2727 *s++ = *q;
2729 break;
2731 default:
2732 *s++ = *q;
2733 break;
2735 } while(*q++);
2740 return(head);
2745 * Converts a PATTERN_S to an array of strings and returns an allocated copy.
2746 * Commas are converted to backslash-comma, because the text_tool UI uses
2747 * commas to separate items.
2748 * It is a bit odd. Backslash itself is not escaped. A backslash which is
2749 * not followed by a comma is a literal backslash, a backslash followed by
2750 * a comma is a comma.
2752 char **
2753 pattern_to_editlist(PATTERN_S *pat)
2755 int cnt, i;
2756 PATTERN_S *p;
2757 char **list = NULL;
2759 if(!pat)
2760 return(list);
2762 /* how many in list? */
2763 for(cnt = 0, p = pat; p; p = p->next)
2764 cnt++;
2766 list = (char **) fs_get((cnt + 1) * sizeof(*list));
2767 memset((void *) list, 0, (cnt + 1) * sizeof(*list));
2769 for(i = 0, p = pat; p; p = p->next, i++)
2770 list[i] = add_comma_escapes(p->substring);
2772 return(list);
2776 PATGRP_S *
2777 nick_to_patgrp(char *nick, int rflags)
2779 PAT_S *pat;
2780 PAT_STATE pstate;
2781 PATGRP_S *patgrp = NULL;
2783 if(!(nick && *nick
2784 && nonempty_patterns(rflags, &pstate) && first_pattern(&pstate)))
2785 return(patgrp);
2787 for(pat = first_pattern(&pstate);
2788 !patgrp && pat;
2789 pat = next_pattern(&pstate))
2790 if(pat->patgrp && pat->patgrp->nick && !strcmp(pat->patgrp->nick, nick))
2791 patgrp = copy_patgrp(pat->patgrp);
2793 return(patgrp);
2798 * Must be called with a pstate, we don't check for it.
2799 * It respects the cur_rflag_num in pstate. That is, it doesn't start over
2800 * at i=1, it starts at cur_rflag_num.
2802 PAT_S *
2803 first_any_pattern(PAT_STATE *pstate)
2805 PAT_LINE_S *patline = NULL;
2806 int i;
2807 long local_rflag;
2810 * The rest of pstate should be set before coming here.
2811 * In particular, the rflags should be set by a call to nonempty_patterns
2812 * or any_patterns, and cur_rflag_num should be set.
2814 pstate->patlinecurrent = NULL;
2815 pstate->patcurrent = NULL;
2818 * The order of these is important. It is the same as the order
2819 * used for next_any_pattern and opposite of the order used by
2820 * last and prev. For next_any's benefit, we allow cur_rflag_num to
2821 * start us out past the first set.
2823 for(i = pstate->cur_rflag_num; i <= PATTERN_N; i++){
2825 local_rflag = 0L;
2827 switch(i){
2828 case 1:
2829 local_rflag = ROLE_DO_SRCH & CANONICAL_RFLAGS(pstate->rflags);
2830 break;
2832 case 2:
2833 local_rflag = ROLE_DO_INCOLS & CANONICAL_RFLAGS(pstate->rflags);
2834 break;
2836 case 3:
2837 local_rflag = ROLE_DO_ROLES & CANONICAL_RFLAGS(pstate->rflags);
2838 break;
2840 case 4:
2841 local_rflag = ROLE_DO_FILTER & CANONICAL_RFLAGS(pstate->rflags);
2842 break;
2844 case 5:
2845 local_rflag = ROLE_DO_SCORES & CANONICAL_RFLAGS(pstate->rflags);
2846 break;
2848 case 6:
2849 local_rflag = ROLE_DO_OTHER & CANONICAL_RFLAGS(pstate->rflags);
2850 break;
2852 case 7:
2853 local_rflag = ROLE_OLD_FILT & CANONICAL_RFLAGS(pstate->rflags);
2854 break;
2856 case 8:
2857 local_rflag = ROLE_OLD_SCORE & CANONICAL_RFLAGS(pstate->rflags);
2858 break;
2860 case PATTERN_N:
2861 local_rflag = ROLE_OLD_PAT & CANONICAL_RFLAGS(pstate->rflags);
2862 break;
2865 if(local_rflag){
2866 SET_PATTYPE(local_rflag | (pstate->rflags & PAT_USE_MASK));
2868 if(*cur_pat_h){
2869 /* Find first patline with a pat */
2870 for(patline = (*cur_pat_h)->patlinehead;
2871 patline && !patline->first;
2872 patline = patline->next)
2876 if(patline){
2877 pstate->cur_rflag_num = i;
2878 pstate->patlinecurrent = patline;
2879 pstate->patcurrent = patline->first;
2883 if(pstate->patcurrent)
2884 break;
2887 return(pstate->patcurrent);
2892 * Return first pattern of the specified types. These types were set by a
2893 * previous call to any_patterns or nonempty_patterns.
2895 * Args -- pstate pattern state. This is set here and passed back for
2896 * use by next_pattern. Must be non-null.
2897 * It must have been initialized previously by a call to
2898 * nonempty_patterns or any_patterns.
2900 PAT_S *
2901 first_pattern(PAT_STATE *pstate)
2903 PAT_S *pat;
2904 long rflags;
2906 pstate->cur_rflag_num = 1;
2908 rflags = pstate->rflags;
2910 for(pat = first_any_pattern(pstate);
2911 pat && !((pat->action &&
2912 ((rflags & ROLE_DO_ROLES && pat->action->is_a_role) ||
2913 (rflags & (ROLE_DO_INCOLS|ROLE_INCOL) &&
2914 pat->action->is_a_incol) ||
2915 (rflags & ROLE_DO_OTHER && pat->action->is_a_other) ||
2916 (rflags & ROLE_DO_SRCH && pat->action->is_a_srch) ||
2917 (rflags & ROLE_DO_SCORES && pat->action->is_a_score) ||
2918 (rflags & ROLE_SCORE && (pat->action->scoreval
2919 || pat->action->scorevalhdrtok)) ||
2920 (rflags & ROLE_DO_FILTER && pat->action->is_a_filter) ||
2921 (rflags & ROLE_REPLY &&
2922 (pat->action->repl_type == ROLE_REPL_YES ||
2923 pat->action->repl_type == ROLE_REPL_NOCONF)) ||
2924 (rflags & ROLE_FORWARD &&
2925 (pat->action->forw_type == ROLE_FORW_YES ||
2926 pat->action->forw_type == ROLE_FORW_NOCONF)) ||
2927 (rflags & ROLE_COMPOSE &&
2928 (pat->action->comp_type == ROLE_COMP_YES ||
2929 pat->action->comp_type == ROLE_COMP_NOCONF)) ||
2930 (rflags & ROLE_OLD_FILT) ||
2931 (rflags & ROLE_OLD_SCORE) ||
2932 (rflags & ROLE_OLD_PAT)))
2934 pat->inherit);
2935 pat = next_any_pattern(pstate))
2938 return(pat);
2943 * Just like first_any_pattern.
2945 PAT_S *
2946 last_any_pattern(PAT_STATE *pstate)
2948 PAT_LINE_S *patline = NULL;
2949 int i;
2950 long local_rflag;
2953 * The rest of pstate should be set before coming here.
2954 * In particular, the rflags should be set by a call to nonempty_patterns
2955 * or any_patterns, and cur_rflag_num should be set.
2957 pstate->patlinecurrent = NULL;
2958 pstate->patcurrent = NULL;
2960 for(i = pstate->cur_rflag_num; i >= 1; i--){
2962 local_rflag = 0L;
2964 switch(i){
2965 case 1:
2966 local_rflag = ROLE_DO_SRCH & CANONICAL_RFLAGS(pstate->rflags);
2967 break;
2969 case 2:
2970 local_rflag = ROLE_DO_INCOLS & CANONICAL_RFLAGS(pstate->rflags);
2971 break;
2973 case 3:
2974 local_rflag = ROLE_DO_ROLES & CANONICAL_RFLAGS(pstate->rflags);
2975 break;
2977 case 4:
2978 local_rflag = ROLE_DO_FILTER & CANONICAL_RFLAGS(pstate->rflags);
2979 break;
2981 case 5:
2982 local_rflag = ROLE_DO_SCORES & CANONICAL_RFLAGS(pstate->rflags);
2983 break;
2985 case 6:
2986 local_rflag = ROLE_DO_OTHER & CANONICAL_RFLAGS(pstate->rflags);
2987 break;
2989 case 7:
2990 local_rflag = ROLE_OLD_FILT & CANONICAL_RFLAGS(pstate->rflags);
2991 break;
2993 case 8:
2994 local_rflag = ROLE_OLD_SCORE & CANONICAL_RFLAGS(pstate->rflags);
2995 break;
2997 case PATTERN_N:
2998 local_rflag = ROLE_OLD_PAT & CANONICAL_RFLAGS(pstate->rflags);
2999 break;
3002 if(local_rflag){
3003 SET_PATTYPE(local_rflag | (pstate->rflags & PAT_USE_MASK));
3005 pstate->patlinecurrent = NULL;
3006 pstate->patcurrent = NULL;
3008 if(*cur_pat_h){
3009 /* Find last patline with a pat */
3010 for(patline = (*cur_pat_h)->patlinehead;
3011 patline;
3012 patline = patline->next)
3013 if(patline->last)
3014 pstate->patlinecurrent = patline;
3016 if(pstate->patlinecurrent)
3017 pstate->patcurrent = pstate->patlinecurrent->last;
3020 if(pstate->patcurrent)
3021 pstate->cur_rflag_num = i;
3023 if(pstate->patcurrent)
3024 break;
3028 return(pstate->patcurrent);
3033 * Return last pattern of the specified types. These types were set by a
3034 * previous call to any_patterns or nonempty_patterns.
3036 * Args -- pstate pattern state. This is set here and passed back for
3037 * use by prev_pattern. Must be non-null.
3038 * It must have been initialized previously by a call to
3039 * nonempty_patterns or any_patterns.
3041 PAT_S *
3042 last_pattern(PAT_STATE *pstate)
3044 PAT_S *pat;
3045 long rflags;
3047 pstate->cur_rflag_num = PATTERN_N;
3049 rflags = pstate->rflags;
3051 for(pat = last_any_pattern(pstate);
3052 pat && !((pat->action &&
3053 ((rflags & ROLE_DO_ROLES && pat->action->is_a_role) ||
3054 (rflags & (ROLE_DO_INCOLS|ROLE_INCOL) &&
3055 pat->action->is_a_incol) ||
3056 (rflags & ROLE_DO_OTHER && pat->action->is_a_other) ||
3057 (rflags & ROLE_DO_SRCH && pat->action->is_a_srch) ||
3058 (rflags & ROLE_DO_SCORES && pat->action->is_a_score) ||
3059 (rflags & ROLE_SCORE && (pat->action->scoreval
3060 || pat->action->scorevalhdrtok)) ||
3061 (rflags & ROLE_DO_FILTER && pat->action->is_a_filter) ||
3062 (rflags & ROLE_REPLY &&
3063 (pat->action->repl_type == ROLE_REPL_YES ||
3064 pat->action->repl_type == ROLE_REPL_NOCONF)) ||
3065 (rflags & ROLE_FORWARD &&
3066 (pat->action->forw_type == ROLE_FORW_YES ||
3067 pat->action->forw_type == ROLE_FORW_NOCONF)) ||
3068 (rflags & ROLE_COMPOSE &&
3069 (pat->action->comp_type == ROLE_COMP_YES ||
3070 pat->action->comp_type == ROLE_COMP_NOCONF)) ||
3071 (rflags & ROLE_OLD_FILT) ||
3072 (rflags & ROLE_OLD_SCORE) ||
3073 (rflags & ROLE_OLD_PAT)))
3075 pat->inherit);
3076 pat = prev_any_pattern(pstate))
3079 return(pat);
3084 * This assumes that pstate is valid.
3086 PAT_S *
3087 next_any_pattern(PAT_STATE *pstate)
3089 PAT_LINE_S *patline;
3091 if(pstate->patlinecurrent){
3092 if(pstate->patcurrent && pstate->patcurrent->next)
3093 pstate->patcurrent = pstate->patcurrent->next;
3094 else{
3095 /* Find next patline with a pat */
3096 for(patline = pstate->patlinecurrent->next;
3097 patline && !patline->first;
3098 patline = patline->next)
3101 if(patline){
3102 pstate->patlinecurrent = patline;
3103 pstate->patcurrent = patline->first;
3105 else{
3106 pstate->patlinecurrent = NULL;
3107 pstate->patcurrent = NULL;
3112 /* we've reached the last, try the next rflag_num (the next pattern type) */
3113 if(!pstate->patcurrent){
3114 pstate->cur_rflag_num++;
3115 pstate->patcurrent = first_any_pattern(pstate);
3118 return(pstate->patcurrent);
3123 * Return next pattern of the specified types. These types were set by a
3124 * previous call to any_patterns or nonempty_patterns.
3126 * Args -- pstate pattern state. This is set by first_pattern or last_pattern.
3128 PAT_S *
3129 next_pattern(PAT_STATE *pstate)
3131 PAT_S *pat;
3132 long rflags;
3134 rflags = pstate->rflags;
3136 for(pat = next_any_pattern(pstate);
3137 pat && !((pat->action &&
3138 ((rflags & ROLE_DO_ROLES && pat->action->is_a_role) ||
3139 (rflags & (ROLE_DO_INCOLS|ROLE_INCOL) &&
3140 pat->action->is_a_incol) ||
3141 (rflags & ROLE_DO_OTHER && pat->action->is_a_other) ||
3142 (rflags & ROLE_DO_SRCH && pat->action->is_a_srch) ||
3143 (rflags & ROLE_DO_SCORES && pat->action->is_a_score) ||
3144 (rflags & ROLE_SCORE && (pat->action->scoreval
3145 || pat->action->scorevalhdrtok)) ||
3146 (rflags & ROLE_DO_FILTER && pat->action->is_a_filter) ||
3147 (rflags & ROLE_REPLY &&
3148 (pat->action->repl_type == ROLE_REPL_YES ||
3149 pat->action->repl_type == ROLE_REPL_NOCONF)) ||
3150 (rflags & ROLE_FORWARD &&
3151 (pat->action->forw_type == ROLE_FORW_YES ||
3152 pat->action->forw_type == ROLE_FORW_NOCONF)) ||
3153 (rflags & ROLE_COMPOSE &&
3154 (pat->action->comp_type == ROLE_COMP_YES ||
3155 pat->action->comp_type == ROLE_COMP_NOCONF)) ||
3156 (rflags & ROLE_OLD_FILT) ||
3157 (rflags & ROLE_OLD_SCORE) ||
3158 (rflags & ROLE_OLD_PAT)))
3160 pat->inherit);
3161 pat = next_any_pattern(pstate))
3164 return(pat);
3169 * This assumes that pstate is valid.
3171 PAT_S *
3172 prev_any_pattern(PAT_STATE *pstate)
3174 PAT_LINE_S *patline;
3176 if(pstate->patlinecurrent){
3177 if(pstate->patcurrent && pstate->patcurrent->prev)
3178 pstate->patcurrent = pstate->patcurrent->prev;
3179 else{
3180 /* Find prev patline with a pat */
3181 for(patline = pstate->patlinecurrent->prev;
3182 patline && !patline->last;
3183 patline = patline->prev)
3186 if(patline){
3187 pstate->patlinecurrent = patline;
3188 pstate->patcurrent = patline->last;
3190 else{
3191 pstate->patlinecurrent = NULL;
3192 pstate->patcurrent = NULL;
3197 if(!pstate->patcurrent){
3198 pstate->cur_rflag_num--;
3199 pstate->patcurrent = last_any_pattern(pstate);
3202 return(pstate->patcurrent);
3207 * Return prev pattern of the specified types. These types were set by a
3208 * previous call to any_patterns or nonempty_patterns.
3210 * Args -- pstate pattern state. This is set by first_pattern or last_pattern.
3212 PAT_S *
3213 prev_pattern(PAT_STATE *pstate)
3215 PAT_S *pat;
3216 long rflags;
3218 rflags = pstate->rflags;
3220 for(pat = prev_any_pattern(pstate);
3221 pat && !((pat->action &&
3222 ((rflags & ROLE_DO_ROLES && pat->action->is_a_role) ||
3223 (rflags & (ROLE_DO_INCOLS|ROLE_INCOL) &&
3224 pat->action->is_a_incol) ||
3225 (rflags & ROLE_DO_OTHER && pat->action->is_a_other) ||
3226 (rflags & ROLE_DO_SRCH && pat->action->is_a_srch) ||
3227 (rflags & ROLE_DO_SCORES && pat->action->is_a_score) ||
3228 (rflags & ROLE_SCORE && (pat->action->scoreval
3229 || pat->action->scorevalhdrtok)) ||
3230 (rflags & ROLE_DO_FILTER && pat->action->is_a_filter) ||
3231 (rflags & ROLE_REPLY &&
3232 (pat->action->repl_type == ROLE_REPL_YES ||
3233 pat->action->repl_type == ROLE_REPL_NOCONF)) ||
3234 (rflags & ROLE_FORWARD &&
3235 (pat->action->forw_type == ROLE_FORW_YES ||
3236 pat->action->forw_type == ROLE_FORW_NOCONF)) ||
3237 (rflags & ROLE_COMPOSE &&
3238 (pat->action->comp_type == ROLE_COMP_YES ||
3239 pat->action->comp_type == ROLE_COMP_NOCONF)) ||
3240 (rflags & ROLE_OLD_FILT) ||
3241 (rflags & ROLE_OLD_SCORE) ||
3242 (rflags & ROLE_OLD_PAT)))
3244 pat->inherit);
3245 pat = prev_any_pattern(pstate))
3248 return(pat);
3253 * Rflags may be more than one pattern type OR'd together.
3256 write_patterns(long int rflags)
3258 int canon_rflags;
3259 int err = 0;
3261 dprint((7, "write_patterns(0x%x)\n", rflags));
3263 canon_rflags = CANONICAL_RFLAGS(rflags);
3265 if(canon_rflags & ROLE_DO_INCOLS)
3266 err += sub_write_patterns(ROLE_DO_INCOLS | (rflags & PAT_USE_MASK));
3267 if(!err && canon_rflags & ROLE_DO_OTHER)
3268 err += sub_write_patterns(ROLE_DO_OTHER | (rflags & PAT_USE_MASK));
3269 if(!err && canon_rflags & ROLE_DO_FILTER)
3270 err += sub_write_patterns(ROLE_DO_FILTER | (rflags & PAT_USE_MASK));
3271 if(!err && canon_rflags & ROLE_DO_SCORES)
3272 err += sub_write_patterns(ROLE_DO_SCORES | (rflags & PAT_USE_MASK));
3273 if(!err && canon_rflags & ROLE_DO_ROLES)
3274 err += sub_write_patterns(ROLE_DO_ROLES | (rflags & PAT_USE_MASK));
3275 if(!err && canon_rflags & ROLE_DO_SRCH)
3276 err += sub_write_patterns(ROLE_DO_SRCH | (rflags & PAT_USE_MASK));
3278 if(!err && !(rflags & PAT_USE_CHANGED))
3279 write_pinerc(ps_global, (rflags & PAT_USE_MAIN) ? Main : Post, WRP_NONE);
3281 return(err);
3286 sub_write_patterns(long int rflags)
3288 int err = 0, lineno = 0;
3289 char **lvalue = NULL;
3290 PAT_LINE_S *patline;
3292 SET_PATTYPE(rflags);
3294 if(!(*cur_pat_h)){
3295 q_status_message(SM_ORDER | SM_DING, 3, 4,
3296 "Unknown error saving patterns");
3297 return(-1);
3300 if((*cur_pat_h)->dirtypinerc){
3301 /* Count how many lines will be in patterns variable */
3302 for(patline = (*cur_pat_h)->patlinehead;
3303 patline;
3304 patline = patline->next)
3305 lineno++;
3307 lvalue = (char **)fs_get((lineno+1)*sizeof(char *));
3308 memset(lvalue, 0, (lineno+1) * sizeof(char *));
3311 for(patline = (*cur_pat_h)->patlinehead, lineno = 0;
3312 !err && patline;
3313 patline = patline->next, lineno++){
3314 if(patline->type == File)
3315 err = write_pattern_file((*cur_pat_h)->dirtypinerc
3316 ? &lvalue[lineno] : NULL, patline);
3317 else if(patline->type == Literal && (*cur_pat_h)->dirtypinerc)
3318 err = write_pattern_lit(&lvalue[lineno], patline);
3319 else if(patline->type == Inherit)
3320 err = write_pattern_inherit((*cur_pat_h)->dirtypinerc
3321 ? &lvalue[lineno] : NULL, patline);
3324 if((*cur_pat_h)->dirtypinerc){
3325 if(err)
3326 free_list_array(&lvalue);
3327 else{
3328 char ***alval;
3329 struct variable *var = NULL;
3331 if(rflags & ROLE_DO_ROLES)
3332 var = &ps_global->vars[V_PAT_ROLES];
3333 else if(rflags & ROLE_DO_OTHER)
3334 var = &ps_global->vars[V_PAT_OTHER];
3335 else if(rflags & ROLE_DO_FILTER)
3336 var = &ps_global->vars[V_PAT_FILTS];
3337 else if(rflags & ROLE_DO_SCORES)
3338 var = &ps_global->vars[V_PAT_SCORES];
3339 else if(rflags & ROLE_DO_INCOLS)
3340 var = &ps_global->vars[V_PAT_INCOLS];
3341 else if(rflags & ROLE_DO_SRCH)
3342 var = &ps_global->vars[V_PAT_SRCH];
3344 alval = (rflags & PAT_USE_CHANGED) ? &(var->changed_val.l)
3345 : ALVAL(var, (rflags & PAT_USE_MAIN) ? Main : Post);
3346 if(*alval)
3347 free_list_array(alval);
3349 if(rflags & PAT_USE_CHANGED) var->is_changed_val = 1;
3351 *alval = lvalue;
3353 if(!(rflags & PAT_USE_CHANGED))
3354 set_current_val(var, TRUE, TRUE);
3358 if(!err)
3359 (*cur_pat_h)->dirtypinerc = 0;
3361 return(err);
3366 * Write pattern lines into a file.
3368 * Args lvalue -- Pointer to char * to fill in variable value
3369 * patline --
3371 * Returns 0 -- all is ok, lvalue has been filled in, file has been written
3372 * else -- error, lvalue untouched, file not written
3375 write_pattern_file(char **lvalue, PAT_LINE_S *patline)
3377 char *p, *tfile;
3378 int fd = -1, err = 0;
3379 FILE *fp_new;
3380 PAT_S *pat;
3382 dprint((7, "write_pattern_file(%s)\n",
3383 (patline && patline->filepath) ? patline->filepath : "?"));
3385 if(lvalue){
3386 size_t l;
3388 l = strlen(patline->filename) + 5;
3389 p = (char *) fs_get((l+1) * sizeof(char));
3390 strncpy(p, "FILE:", l+1);
3391 p[l] = '\0';
3392 strncat(p, patline->filename, l+1-1-strlen(p));
3393 p[l] = '\0';
3394 *lvalue = p;
3397 if(patline->readonly || !patline->dirty) /* doesn't need writing */
3398 return(err);
3400 /* Get a tempfile to write the patterns into */
3401 if(((tfile = tempfile_in_same_dir(patline->filepath, ".pt", NULL)) == NULL)
3402 || ((fd = our_open(tfile, O_TRUNC|O_WRONLY|O_CREAT|O_BINARY, 0600)) < 0)
3403 || ((fp_new = fdopen(fd, "w")) == NULL)){
3404 q_status_message1(SM_ORDER | SM_DING, 3, 4,
3405 "Can't write in directory containing file \"%.200s\"",
3406 patline->filepath);
3407 if(tfile){
3408 our_unlink(tfile);
3409 fs_give((void **)&tfile);
3412 if(fd >= 0)
3413 close(fd);
3415 return(-1);
3418 dprint((9, "write_pattern_file: writing into %s\n",
3419 tfile ? tfile : "?"));
3421 if(fprintf(fp_new, "%s %s\n", PATTERN_MAGIC, PATTERN_FILE_VERS) == EOF)
3422 err--;
3424 for(pat = patline->first; !err && pat; pat = pat->next){
3425 if((p = data_for_patline(pat)) != NULL){
3426 if(fprintf(fp_new, "%s\n", p) == EOF)
3427 err--;
3429 fs_give((void **)&p);
3433 if(err || fclose(fp_new) == EOF){
3434 if(err)
3435 (void)fclose(fp_new);
3437 err--;
3438 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3439 "I/O error: \"%.200s\": %.200s",
3440 tfile, error_description(errno));
3443 if(!err && rename_file(tfile, patline->filepath) < 0){
3444 err--;
3445 q_status_message3(SM_ORDER | SM_DING, 3, 4,
3446 _("Error renaming \"%s\" to \"%s\": %s"),
3447 tfile, patline->filepath, error_description(errno));
3448 dprint((2,
3449 "write_pattern_file: Error renaming (%s,%s): %s\n",
3450 tfile ? tfile : "?",
3451 (patline && patline->filepath) ? patline->filepath : "?",
3452 error_description(errno)));
3455 if(tfile){
3456 our_unlink(tfile);
3457 fs_give((void **)&tfile);
3460 if(!err)
3461 patline->dirty = 0;
3463 return(err);
3468 * Write literal pattern lines into lvalue (pinerc variable).
3470 * Args lvalue -- Pointer to char * to fill in variable value
3471 * patline --
3473 * Returns 0 -- all is ok, lvalue has been filled in, file has been written
3474 * else -- error, lvalue untouched, file not written
3477 write_pattern_lit(char **lvalue, PAT_LINE_S *patline)
3479 char *p = NULL;
3480 int err = 0;
3481 PAT_S *pat;
3483 pat = patline ? patline->first : NULL;
3485 if(pat && lvalue && (p = data_for_patline(pat)) != NULL){
3486 size_t l;
3488 l = strlen(p) + 4;
3489 *lvalue = (char *) fs_get((l+1) * sizeof(char));
3490 strncpy(*lvalue, "LIT:", l+1);
3491 (*lvalue)[l] = '\0';
3492 strncat(*lvalue, p, l+1-1-strlen(*lvalue));
3493 (*lvalue)[l] = '\0';
3495 else{
3496 q_status_message(SM_ORDER | SM_DING, 3, 4,
3497 _("Unknown error saving pattern variable"));
3498 err--;
3501 if(p)
3502 fs_give((void **)&p);
3504 return(err);
3509 write_pattern_inherit(char **lvalue, PAT_LINE_S *patline)
3511 int err = 0;
3513 if(patline && patline->type == Inherit && lvalue)
3514 *lvalue = cpystr(INHERIT);
3515 else
3516 err--;
3518 return(err);
3523 char *
3524 data_for_patline(PAT_S *pat)
3526 char *p = NULL, *q, *to_pat = NULL,
3527 *news_pat = NULL, *from_pat = NULL,
3528 *sender_pat = NULL, *cc_pat = NULL, *subj_pat = NULL,
3529 *arb_pat = NULL, *fldr_type_pat = NULL, *fldr_pat = NULL,
3530 *afrom_type_pat = NULL, *abooks_pat = NULL,
3531 *alltext_pat = NULL, *scorei_pat = NULL, *recip_pat = NULL,
3532 *keyword_pat = NULL, *charset_pat = NULL,
3533 *bodytext_pat = NULL, *age_pat = NULL, *sentdate = NULL,
3534 *size_pat = NULL,
3535 *category_cmd = NULL, *category_pat = NULL,
3536 *category_lim = NULL,
3537 *partic_pat = NULL, *stat_new_val = NULL,
3538 *stat_rec_val = NULL,
3539 *stat_imp_val = NULL, *stat_del_val = NULL,
3540 *stat_ans_val = NULL, *stat_8bit_val = NULL,
3541 *stat_bom_val = NULL, *stat_boy_val = NULL,
3542 *from_act = NULL, *replyto_act = NULL, *fcc_act = NULL,
3543 *sig_act = NULL, *nick = NULL, *templ_act = NULL,
3544 *litsig_act = NULL, *cstm_act = NULL, *smtp_act = NULL,
3545 *nntp_act = NULL, *comment = NULL,
3546 *repl_val = NULL, *forw_val = NULL, *comp_val = NULL,
3547 *incol_act = NULL, *inherit_nick = NULL,
3548 *score_act = NULL, *hdrtok_act = NULL,
3549 *sort_act = NULL, *iform_act = NULL, *start_act = NULL,
3550 *folder_act = NULL, *filt_ifnotdel = NULL,
3551 *filt_nokill = NULL, *filt_del_val = NULL,
3552 *filt_imp_val = NULL, *filt_ans_val = NULL,
3553 *filt_new_val = NULL, *filt_nonterm = NULL,
3554 *keyword_set = NULL, *keyword_clr = NULL;
3555 int to_not = 0, news_not = 0, from_not = 0,
3556 sender_not = 0, cc_not = 0, subj_not = 0,
3557 partic_not = 0, recip_not = 0, alltext_not = 0, bodytext_not = 0,
3558 keyword_not = 0, charset_not = 0;
3559 size_t l;
3560 ACTION_S *action = NULL;
3561 NAMEVAL_S *f;
3563 if(!pat)
3564 return(p);
3566 if((pat->patgrp && pat->patgrp->bogus)
3567 || (pat->action && pat->action->bogus)){
3568 if(pat->raw)
3569 p = cpystr(pat->raw);
3571 return(p);
3574 if(pat->patgrp){
3575 if(pat->patgrp->nick)
3576 if((nick = add_pat_escapes(pat->patgrp->nick)) && !*nick)
3577 fs_give((void **) &nick);
3579 if(pat->patgrp->comment)
3580 if((comment = add_pat_escapes(pat->patgrp->comment)) && !*comment)
3581 fs_give((void **) &comment);
3583 if(pat->patgrp->to){
3584 to_pat = pattern_to_config(pat->patgrp->to);
3585 to_not = pat->patgrp->to->not;
3588 if(pat->patgrp->from){
3589 from_pat = pattern_to_config(pat->patgrp->from);
3590 from_not = pat->patgrp->from->not;
3593 if(pat->patgrp->sender){
3594 sender_pat = pattern_to_config(pat->patgrp->sender);
3595 sender_not = pat->patgrp->sender->not;
3598 if(pat->patgrp->cc){
3599 cc_pat = pattern_to_config(pat->patgrp->cc);
3600 cc_not = pat->patgrp->cc->not;
3603 if(pat->patgrp->recip){
3604 recip_pat = pattern_to_config(pat->patgrp->recip);
3605 recip_not = pat->patgrp->recip->not;
3608 if(pat->patgrp->partic){
3609 partic_pat = pattern_to_config(pat->patgrp->partic);
3610 partic_not = pat->patgrp->partic->not;
3613 if(pat->patgrp->news){
3614 news_pat = pattern_to_config(pat->patgrp->news);
3615 news_not = pat->patgrp->news->not;
3618 if(pat->patgrp->subj){
3619 subj_pat = pattern_to_config(pat->patgrp->subj);
3620 subj_not = pat->patgrp->subj->not;
3623 if(pat->patgrp->alltext){
3624 alltext_pat = pattern_to_config(pat->patgrp->alltext);
3625 alltext_not = pat->patgrp->alltext->not;
3628 if(pat->patgrp->bodytext){
3629 bodytext_pat = pattern_to_config(pat->patgrp->bodytext);
3630 bodytext_not = pat->patgrp->bodytext->not;
3633 if(pat->patgrp->keyword){
3634 keyword_pat = pattern_to_config(pat->patgrp->keyword);
3635 keyword_not = pat->patgrp->keyword->not;
3638 if(pat->patgrp->charsets){
3639 charset_pat = pattern_to_config(pat->patgrp->charsets);
3640 charset_not = pat->patgrp->charsets->not;
3643 if(pat->patgrp->arbhdr){
3644 ARBHDR_S *a;
3645 char *p1 = NULL, *p2 = NULL, *p3 = NULL, *p4 = NULL;
3646 int len = 0;
3648 /* This is brute force dumb, but who cares? */
3649 for(a = pat->patgrp->arbhdr; a; a = a->next){
3650 if(a->field && a->field[0]){
3651 p1 = pattern_to_string(a->p);
3652 p1 = p1 ? p1 : cpystr("");
3653 l = strlen(a->field)+strlen(p1)+1;
3654 p2 = (char *) fs_get((l+1) * sizeof(char));
3655 snprintf(p2, l+1, "%s=%s", a->field, p1);
3656 p3 = add_pat_escapes(p2);
3657 l = strlen(p3)+6;
3658 p4 = (char *) fs_get((l+1) * sizeof(char));
3659 snprintf(p4, l+1, "/%s%sARB%s",
3660 (a->p && a->p->not) ? "!" : "",
3661 a->isemptyval ? "E" : "", p3);
3662 len += strlen(p4);
3664 if(p1)
3665 fs_give((void **)&p1);
3666 if(p2)
3667 fs_give((void **)&p2);
3668 if(p3)
3669 fs_give((void **)&p3);
3670 if(p4)
3671 fs_give((void **)&p4);
3675 p = arb_pat = (char *)fs_get((len + 1) * sizeof(char));
3677 for(a = pat->patgrp->arbhdr; a; a = a->next){
3678 if(a->field && a->field[0]){
3679 p1 = pattern_to_string(a->p);
3680 p1 = p1 ? p1 : cpystr("");
3681 l = strlen(a->field)+strlen(p1)+1;
3682 p2 = (char *) fs_get((l+1) * sizeof(char));
3683 snprintf(p2, l+1, "%s=%s", a->field, p1);
3684 p3 = add_pat_escapes(p2);
3685 l = strlen(p3)+6;
3686 p4 = (char *) fs_get((l+1) * sizeof(char));
3687 snprintf(p4, l+1, "/%s%sARB%s",
3688 (a->p && a->p->not) ? "!" : "",
3689 a->isemptyval ? "E" : "", p3);
3690 sstrncpy(&p, p4, len+1-(p-arb_pat));
3692 if(p1)
3693 fs_give((void **)&p1);
3694 if(p2)
3695 fs_give((void **)&p2);
3696 if(p3)
3697 fs_give((void **)&p3);
3698 if(p4)
3699 fs_give((void **)&p4);
3703 arb_pat[len] = '\0';
3706 if(pat->patgrp->age_uses_sentdate)
3707 sentdate = cpystr("/SENTDATE=1");
3709 if(pat->patgrp->do_score){
3710 p = stringform_of_intvl(pat->patgrp->score);
3711 if(p){
3712 scorei_pat = add_pat_escapes(p);
3713 fs_give((void **)&p);
3717 if(pat->patgrp->do_age){
3718 p = stringform_of_intvl(pat->patgrp->age);
3719 if(p){
3720 age_pat = add_pat_escapes(p);
3721 fs_give((void **)&p);
3725 if(pat->patgrp->do_size){
3726 p = stringform_of_intvl(pat->patgrp->size);
3727 if(p){
3728 size_pat = add_pat_escapes(p);
3729 fs_give((void **)&p);
3733 if(pat->patgrp->category_cmd && pat->patgrp->category_cmd[0]){
3734 size_t sz;
3735 char **l, *q;
3737 /* concatenate into string with commas first */
3738 sz = 0;
3739 for(l = pat->patgrp->category_cmd; l[0] && l[0][0]; l++)
3740 sz += strlen(l[0]) + 1;
3742 if(sz){
3743 char *p;
3744 int first_one = 1;
3746 q = (char *)fs_get(sz);
3747 memset(q, 0, sz);
3748 p = q;
3749 for(l = pat->patgrp->category_cmd; l[0] && l[0][0]; l++){
3750 if(!first_one)
3751 sstrncpy(&p, ",", sz-(p-q));
3753 first_one = 0;
3754 sstrncpy(&p, l[0], sz-(p-q));
3757 q[sz-1] = '\0';
3759 category_cmd = add_pat_escapes(q);
3760 fs_give((void **)&q);
3764 if(pat->patgrp->do_cat){
3765 p = stringform_of_intvl(pat->patgrp->cat);
3766 if(p){
3767 category_pat = add_pat_escapes(p);
3768 fs_give((void **)&p);
3772 if(pat->patgrp->cat_lim != -1L){
3773 category_lim = (char *) fs_get(20 * sizeof(char));
3774 snprintf(category_lim, 20, "%ld", pat->patgrp->cat_lim);
3777 if((f = pat_fldr_types(pat->patgrp->fldr_type)) != NULL)
3778 fldr_type_pat = f->shortname;
3780 if(pat->patgrp->folder)
3781 fldr_pat = pattern_to_config(pat->patgrp->folder);
3783 if((f = inabook_fldr_types(pat->patgrp->inabook)) != NULL
3784 && f->value != IAB_DEFL)
3785 afrom_type_pat = f->shortname;
3787 if(pat->patgrp->abooks)
3788 abooks_pat = pattern_to_config(pat->patgrp->abooks);
3790 if(pat->patgrp->stat_new != PAT_STAT_EITHER &&
3791 (f = role_status_types(pat->patgrp->stat_new)) != NULL)
3792 stat_new_val = f->shortname;
3794 if(pat->patgrp->stat_rec != PAT_STAT_EITHER &&
3795 (f = role_status_types(pat->patgrp->stat_rec)) != NULL)
3796 stat_rec_val = f->shortname;
3798 if(pat->patgrp->stat_del != PAT_STAT_EITHER &&
3799 (f = role_status_types(pat->patgrp->stat_del)) != NULL)
3800 stat_del_val = f->shortname;
3802 if(pat->patgrp->stat_ans != PAT_STAT_EITHER &&
3803 (f = role_status_types(pat->patgrp->stat_ans)) != NULL)
3804 stat_ans_val = f->shortname;
3806 if(pat->patgrp->stat_imp != PAT_STAT_EITHER &&
3807 (f = role_status_types(pat->patgrp->stat_imp)) != NULL)
3808 stat_imp_val = f->shortname;
3810 if(pat->patgrp->stat_8bitsubj != PAT_STAT_EITHER &&
3811 (f = role_status_types(pat->patgrp->stat_8bitsubj)) != NULL)
3812 stat_8bit_val = f->shortname;
3814 if(pat->patgrp->stat_bom != PAT_STAT_EITHER &&
3815 (f = role_status_types(pat->patgrp->stat_bom)) != NULL)
3816 stat_bom_val = f->shortname;
3818 if(pat->patgrp->stat_boy != PAT_STAT_EITHER &&
3819 (f = role_status_types(pat->patgrp->stat_boy)) != NULL)
3820 stat_boy_val = f->shortname;
3823 if(pat->action){
3824 action = pat->action;
3826 if(action->is_a_score){
3827 if(action->scoreval != 0L &&
3828 action->scoreval >= SCORE_MIN && action->scoreval <= SCORE_MAX){
3829 score_act = (char *) fs_get(5 * sizeof(char));
3830 snprintf(score_act, 5, "%ld", pat->action->scoreval);
3833 if(action->scorevalhdrtok)
3834 hdrtok_act = hdrtok_to_config(action->scorevalhdrtok);
3837 if(action->is_a_role){
3838 if(action->inherit_nick)
3839 inherit_nick = add_pat_escapes(action->inherit_nick);
3840 if(action->fcc)
3841 fcc_act = add_pat_escapes(action->fcc);
3842 if(action->litsig)
3843 litsig_act = add_pat_escapes(action->litsig);
3844 if(action->sig)
3845 sig_act = add_pat_escapes(action->sig);
3846 if(action->template)
3847 templ_act = add_pat_escapes(action->template);
3849 if(action->cstm){
3850 size_t sz;
3851 char **l, *q;
3853 /* concatenate into string with commas first */
3854 sz = 0;
3855 for(l = action->cstm; l[0] && l[0][0]; l++)
3856 sz += strlen(l[0]) + 1;
3858 if(sz){
3859 char *p;
3860 int first_one = 1;
3862 q = (char *)fs_get(sz);
3863 memset(q, 0, sz);
3864 p = q;
3865 for(l = action->cstm; l[0] && l[0][0]; l++){
3866 if((!struncmp(l[0], "from", 4) &&
3867 (l[0][4] == ':' || l[0][4] == '\0')) ||
3868 (!struncmp(l[0], "reply-to", 8) &&
3869 (l[0][8] == ':' || l[0][8] == '\0')))
3870 continue;
3872 if(!first_one)
3873 sstrncpy(&p, ",", sz-(p-q));
3875 first_one = 0;
3876 sstrncpy(&p, l[0], sz-(p-q));
3879 q[sz-1] = '\0';
3881 cstm_act = add_pat_escapes(q);
3882 fs_give((void **)&q);
3886 if(action->smtp){
3887 size_t sz;
3888 char **l, *q;
3890 /* concatenate into string with commas first */
3891 sz = 0;
3892 for(l = action->smtp; l[0] && l[0][0]; l++)
3893 sz += strlen(l[0]) + 1;
3895 if(sz){
3896 char *p;
3897 int first_one = 1;
3899 q = (char *)fs_get(sz);
3900 memset(q, 0, sz);
3901 p = q;
3902 for(l = action->smtp; l[0] && l[0][0]; l++){
3903 if(!first_one)
3904 sstrncpy(&p, ",", sz-(p-q));
3906 first_one = 0;
3907 sstrncpy(&p, l[0], sz-(p-q));
3910 q[sz-1] = '\0';
3912 smtp_act = add_pat_escapes(q);
3913 fs_give((void **)&q);
3917 if(action->nntp){
3918 size_t sz;
3919 char **l, *q;
3921 /* concatenate into string with commas first */
3922 sz = 0;
3923 for(l = action->nntp; l[0] && l[0][0]; l++)
3924 sz += strlen(l[0]) + 1;
3926 if(sz){
3927 char *p;
3928 int first_one = 1;
3930 q = (char *)fs_get(sz);
3931 memset(q, 0, sz);
3932 p = q;
3933 for(l = action->nntp; l[0] && l[0][0]; l++){
3934 if(!first_one)
3935 sstrncpy(&p, ",", sz-(p-q));
3937 first_one = 0;
3938 sstrncpy(&p, l[0], sz-(p-q));
3941 q[sz-1] = '\0';
3943 nntp_act = add_pat_escapes(q);
3944 fs_give((void **)&q);
3948 if((f = role_repl_types(action->repl_type)) != NULL)
3949 repl_val = f->shortname;
3951 if((f = role_forw_types(action->forw_type)) != NULL)
3952 forw_val = f->shortname;
3954 if((f = role_comp_types(action->comp_type)) != NULL)
3955 comp_val = f->shortname;
3958 if(action->is_a_incol && action->incol){
3959 char *ptr, buf[256], *p1, *p2;
3961 ptr = buf;
3962 memset(buf, 0, sizeof(buf));
3963 sstrncpy(&ptr, "/FG=", sizeof(buf)-(ptr-buf));
3964 sstrncpy(&ptr, (p1=add_pat_escapes(action->incol->fg)), sizeof(buf)-(ptr-buf));
3965 sstrncpy(&ptr, "/BG=", sizeof(buf)-(ptr-buf));
3966 sstrncpy(&ptr, (p2=add_pat_escapes(action->incol->bg)), sizeof(buf)-(ptr-buf));
3967 buf[sizeof(buf)-1] = '\0';
3968 /* the colors will be doubly escaped */
3969 incol_act = add_pat_escapes(buf);
3970 if(p1)
3971 fs_give((void **)&p1);
3973 if(p2)
3974 fs_give((void **)&p2);
3977 if(action->is_a_other){
3978 char buf[256];
3980 if(action->sort_is_set){
3981 snprintf(buf, sizeof(buf), "%.50s%.50s",
3982 sort_name(action->sortorder),
3983 action->revsort ? "/Reverse" : "");
3984 sort_act = add_pat_escapes(buf);
3987 if(action->index_format)
3988 iform_act = add_pat_escapes(action->index_format);
3990 if(action->startup_rule != IS_NOTSET &&
3991 (f = startup_rules(action->startup_rule)) != NULL)
3992 start_act = S_OR_L(f);
3995 if(action->is_a_role && action->from){
3996 char *bufp;
3997 size_t len;
3999 len = est_size(action->from);
4000 bufp = (char *) fs_get(len * sizeof(char));
4001 p = addr_string_mult(action->from, bufp, len);
4002 if(p){
4003 from_act = add_pat_escapes(p);
4004 fs_give((void **)&p);
4008 if(action->is_a_role && action->replyto){
4009 char *bufp;
4010 size_t len;
4012 len = est_size(action->replyto);
4013 bufp = (char *) fs_get(len * sizeof(char));
4014 p = addr_string_mult(action->replyto, bufp, len);
4015 if(p){
4016 replyto_act = add_pat_escapes(p);
4017 fs_give((void **)&p);
4021 if(action->is_a_filter){
4022 if(action->folder){
4023 if((folder_act = pattern_to_config(action->folder)) != NULL){
4024 if(action->move_only_if_not_deleted)
4025 filt_ifnotdel = cpystr("/NOTDEL=1");
4029 if(action->keyword_set)
4030 keyword_set = pattern_to_config(action->keyword_set);
4032 if(action->keyword_clr)
4033 keyword_clr = pattern_to_config(action->keyword_clr);
4035 if(!action->kill)
4036 filt_nokill = cpystr("/NOKILL=1");
4038 if(action->non_terminating)
4039 filt_nonterm = cpystr("/NONTERM=1");
4041 if(action->state_setting_bits){
4042 char buf[256];
4043 int dval, nval, ival, aval;
4045 buf[0] = '\0';
4046 p = buf;
4048 convert_statebits_to_vals(action->state_setting_bits,
4049 &dval, &aval, &ival, &nval);
4050 if(dval != ACT_STAT_LEAVE &&
4051 (f = msg_state_types(dval)) != NULL)
4052 filt_del_val = f->shortname;
4054 if(aval != ACT_STAT_LEAVE &&
4055 (f = msg_state_types(aval)) != NULL)
4056 filt_ans_val = f->shortname;
4058 if(ival != ACT_STAT_LEAVE &&
4059 (f = msg_state_types(ival)) != NULL)
4060 filt_imp_val = f->shortname;
4062 if(nval != ACT_STAT_LEAVE &&
4063 (f = msg_state_types(nval)) != NULL)
4064 filt_new_val = f->shortname;
4069 l = strlen(nick ? nick : "Alternate Role") +
4070 strlen(comment ? comment : "") +
4071 strlen(to_pat ? to_pat : "") +
4072 strlen(from_pat ? from_pat : "") +
4073 strlen(sender_pat ? sender_pat : "") +
4074 strlen(cc_pat ? cc_pat : "") +
4075 strlen(recip_pat ? recip_pat : "") +
4076 strlen(partic_pat ? partic_pat : "") +
4077 strlen(news_pat ? news_pat : "") +
4078 strlen(subj_pat ? subj_pat : "") +
4079 strlen(alltext_pat ? alltext_pat : "") +
4080 strlen(bodytext_pat ? bodytext_pat : "") +
4081 strlen(arb_pat ? arb_pat : "") +
4082 strlen(scorei_pat ? scorei_pat : "") +
4083 strlen(keyword_pat ? keyword_pat : "") +
4084 strlen(charset_pat ? charset_pat : "") +
4085 strlen(age_pat ? age_pat : "") +
4086 strlen(size_pat ? size_pat : "") +
4087 strlen(category_cmd ? category_cmd : "") +
4088 strlen(category_pat ? category_pat : "") +
4089 strlen(category_lim ? category_lim : "") +
4090 strlen(fldr_pat ? fldr_pat : "") +
4091 strlen(abooks_pat ? abooks_pat : "") +
4092 strlen(sentdate ? sentdate : "") +
4093 strlen(inherit_nick ? inherit_nick : "") +
4094 strlen(score_act ? score_act : "") +
4095 strlen(hdrtok_act ? hdrtok_act : "") +
4096 strlen(from_act ? from_act : "") +
4097 strlen(replyto_act ? replyto_act : "") +
4098 strlen(fcc_act ? fcc_act : "") +
4099 strlen(litsig_act ? litsig_act : "") +
4100 strlen(cstm_act ? cstm_act : "") +
4101 strlen(smtp_act ? smtp_act : "") +
4102 strlen(nntp_act ? nntp_act : "") +
4103 strlen(sig_act ? sig_act : "") +
4104 strlen(incol_act ? incol_act : "") +
4105 strlen(sort_act ? sort_act : "") +
4106 strlen(iform_act ? iform_act : "") +
4107 strlen(start_act ? start_act : "") +
4108 strlen(filt_ifnotdel ? filt_ifnotdel : "") +
4109 strlen(filt_nokill ? filt_nokill : "") +
4110 strlen(filt_nonterm ? filt_nonterm : "") +
4111 (folder_act ? (strlen(folder_act) + 8) : 0) +
4112 strlen(keyword_set ? keyword_set : "") +
4113 strlen(keyword_clr ? keyword_clr : "") +
4114 strlen(templ_act ? templ_act : "") + 540;
4116 * The +540 above is larger than needed but not everything is accounted
4117 * for with the strlens.
4119 p = (char *) fs_get(l * sizeof(char));
4121 q = p;
4122 sstrncpy(&q, "pattern=\"/NICK=", l-(q-p));
4124 if(nick){
4125 sstrncpy(&q, nick, l-(q-p));
4126 fs_give((void **) &nick);
4128 else
4129 sstrncpy(&q, "Alternate Role", l-(q-p));
4131 if(comment){
4132 sstrncpy(&q, "/", l-(q-p));
4133 sstrncpy(&q, "COMM=", l-(q-p));
4134 sstrncpy(&q, comment, l-(q-p));
4135 fs_give((void **) &comment);
4138 if(to_pat){
4139 sstrncpy(&q, "/", l-(q-p));
4140 if(to_not)
4141 sstrncpy(&q, "!", l-(q-p));
4143 sstrncpy(&q, "TO=", l-(q-p));
4144 sstrncpy(&q, to_pat, l-(q-p));
4145 fs_give((void **) &to_pat);
4148 if(from_pat){
4149 sstrncpy(&q, "/", l-(q-p));
4150 if(from_not)
4151 sstrncpy(&q, "!", l-(q-p));
4153 sstrncpy(&q, "FROM=", l-(q-p));
4154 sstrncpy(&q, from_pat, l-(q-p));
4155 fs_give((void **) &from_pat);
4158 if(sender_pat){
4159 sstrncpy(&q, "/", l-(q-p));
4160 if(sender_not)
4161 sstrncpy(&q, "!", l-(q-p));
4163 sstrncpy(&q, "SENDER=", l-(q-p));
4164 sstrncpy(&q, sender_pat, l-(q-p));
4165 fs_give((void **) &sender_pat);
4168 if(cc_pat){
4169 sstrncpy(&q,"/", l-(q-p));
4170 if(cc_not)
4171 sstrncpy(&q, "!", l-(q-p));
4173 sstrncpy(&q,"CC=", l-(q-p));
4174 sstrncpy(&q, cc_pat, l-(q-p));
4175 fs_give((void **) &cc_pat);
4178 if(recip_pat){
4179 sstrncpy(&q, "/", l-(q-p));
4180 if(recip_not)
4181 sstrncpy(&q, "!", l-(q-p));
4183 sstrncpy(&q, "RECIP=", l-(q-p));
4184 sstrncpy(&q, recip_pat, l-(q-p));
4185 fs_give((void **) &recip_pat);
4188 if(partic_pat){
4189 sstrncpy(&q, "/", l-(q-p));
4190 if(partic_not)
4191 sstrncpy(&q, "!", l-(q-p));
4193 sstrncpy(&q, "PARTIC=", l-(q-p));
4194 sstrncpy(&q, partic_pat, l-(q-p));
4195 fs_give((void **) &partic_pat);
4198 if(news_pat){
4199 sstrncpy(&q, "/", l-(q-p));
4200 if(news_not)
4201 sstrncpy(&q, "!", l-(q-p));
4203 sstrncpy(&q, "NEWS=", l-(q-p));
4204 sstrncpy(&q, news_pat, l-(q-p));
4205 fs_give((void **) &news_pat);
4208 if(subj_pat){
4209 sstrncpy(&q, "/", l-(q-p));
4210 if(subj_not)
4211 sstrncpy(&q, "!", l-(q-p));
4213 sstrncpy(&q, "SUBJ=", l-(q-p));
4214 sstrncpy(&q, subj_pat, l-(q-p));
4215 fs_give((void **)&subj_pat);
4218 if(alltext_pat){
4219 sstrncpy(&q, "/", l-(q-p));
4220 if(alltext_not)
4221 sstrncpy(&q, "!", l-(q-p));
4223 sstrncpy(&q, "ALL=", l-(q-p));
4224 sstrncpy(&q, alltext_pat, l-(q-p));
4225 fs_give((void **) &alltext_pat);
4228 if(bodytext_pat){
4229 sstrncpy(&q, "/", l-(q-p));
4230 if(bodytext_not)
4231 sstrncpy(&q, "!", l-(q-p));
4233 sstrncpy(&q, "BODY=", l-(q-p));
4234 sstrncpy(&q, bodytext_pat, l-(q-p));
4235 fs_give((void **) &bodytext_pat);
4238 if(keyword_pat){
4239 sstrncpy(&q, "/", l-(q-p));
4240 if(keyword_not)
4241 sstrncpy(&q, "!", l-(q-p));
4243 sstrncpy(&q, "KEY=", l-(q-p));
4244 sstrncpy(&q, keyword_pat, l-(q-p));
4245 fs_give((void **) &keyword_pat);
4248 if(charset_pat){
4249 sstrncpy(&q, "/", l-(q-p));
4250 if(charset_not)
4251 sstrncpy(&q, "!", l-(q-p));
4253 sstrncpy(&q, "CHAR=", l-(q-p));
4254 sstrncpy(&q, charset_pat, l-(q-p));
4255 fs_give((void **) &charset_pat);
4258 if(arb_pat){
4259 sstrncpy(&q, arb_pat, l-(q-p));
4260 fs_give((void **)&arb_pat);
4263 if(scorei_pat){
4264 sstrncpy(&q, "/SCOREI=", l-(q-p));
4265 sstrncpy(&q, scorei_pat, l-(q-p));
4266 fs_give((void **) &scorei_pat);
4269 if(age_pat){
4270 sstrncpy(&q, "/AGE=", l-(q-p));
4271 sstrncpy(&q, age_pat, l-(q-p));
4272 fs_give((void **) &age_pat);
4275 if(size_pat){
4276 sstrncpy(&q, "/SIZE=", l-(q-p));
4277 sstrncpy(&q, size_pat, l-(q-p));
4278 fs_give((void **) &size_pat);
4281 if(category_cmd){
4282 sstrncpy(&q, "/CATCMD=", l-(q-p));
4283 sstrncpy(&q, category_cmd, l-(q-p));
4284 fs_give((void **) &category_cmd);
4287 if(category_pat){
4288 sstrncpy(&q, "/CATVAL=", l-(q-p));
4289 sstrncpy(&q, category_pat, l-(q-p));
4290 fs_give((void **) &category_pat);
4293 if(category_lim){
4294 sstrncpy(&q, "/CATLIM=", l-(q-p));
4295 sstrncpy(&q, category_lim, l-(q-p));
4296 fs_give((void **) &category_lim);
4299 if(sentdate){
4300 sstrncpy(&q, sentdate, l-(q-p));
4301 fs_give((void **) &sentdate);
4304 if(fldr_type_pat){
4305 sstrncpy(&q, "/FLDTYPE=", l-(q-p));
4306 sstrncpy(&q, fldr_type_pat, l-(q-p));
4309 if(fldr_pat){
4310 sstrncpy(&q, "/FOLDER=", l-(q-p));
4311 sstrncpy(&q, fldr_pat, l-(q-p));
4312 fs_give((void **) &fldr_pat);
4315 if(afrom_type_pat){
4316 sstrncpy(&q, "/AFROM=", l-(q-p));
4317 sstrncpy(&q, afrom_type_pat, l-(q-p));
4320 * Add address types. If it is From or Reply-to
4321 * leave this out so it will still work with pine.
4323 if((pat->patgrp->inabook & IAB_FROM
4324 && pat->patgrp->inabook & IAB_REPLYTO
4325 && !(pat->patgrp->inabook & IAB_SENDER)
4326 && !(pat->patgrp->inabook & IAB_TO)
4327 && !(pat->patgrp->inabook & IAB_CC))
4329 (!(pat->patgrp->inabook & IAB_FROM)
4330 && !(pat->patgrp->inabook & IAB_REPLYTO)
4331 && !(pat->patgrp->inabook & IAB_SENDER)
4332 && !(pat->patgrp->inabook & IAB_TO)
4333 && !(pat->patgrp->inabook & IAB_CC))){
4334 ; /* leave it out */
4336 else{
4337 sstrncpy(&q, "/AFROMA=", l-(q-p));
4338 if(pat->patgrp->inabook & IAB_FROM)
4339 sstrncpy(&q, "F", l-(q-p));
4341 if(pat->patgrp->inabook & IAB_REPLYTO)
4342 sstrncpy(&q, "R", l-(q-p));
4344 if(pat->patgrp->inabook & IAB_SENDER)
4345 sstrncpy(&q, "S", l-(q-p));
4347 if(pat->patgrp->inabook & IAB_TO)
4348 sstrncpy(&q, "T", l-(q-p));
4350 if(pat->patgrp->inabook & IAB_CC)
4351 sstrncpy(&q, "C", l-(q-p));
4355 if(abooks_pat){
4356 sstrncpy(&q, "/ABOOKS=", l-(q-p));
4357 sstrncpy(&q, abooks_pat, l-(q-p));
4358 fs_give((void **) &abooks_pat);
4361 if(stat_new_val){
4362 sstrncpy(&q, "/STATN=", l-(q-p));
4363 sstrncpy(&q, stat_new_val, l-(q-p));
4366 if(stat_rec_val){
4367 sstrncpy(&q, "/STATR=", l-(q-p));
4368 sstrncpy(&q, stat_rec_val, l-(q-p));
4371 if(stat_del_val){
4372 sstrncpy(&q, "/STATD=", l-(q-p));
4373 sstrncpy(&q, stat_del_val, l-(q-p));
4376 if(stat_imp_val){
4377 sstrncpy(&q, "/STATI=", l-(q-p));
4378 sstrncpy(&q, stat_imp_val, l-(q-p));
4381 if(stat_ans_val){
4382 sstrncpy(&q, "/STATA=", l-(q-p));
4383 sstrncpy(&q, stat_ans_val, l-(q-p));
4386 if(stat_8bit_val){
4387 sstrncpy(&q, "/8BITS=", l-(q-p));
4388 sstrncpy(&q, stat_8bit_val, l-(q-p));
4391 if(stat_bom_val){
4392 sstrncpy(&q, "/BOM=", l-(q-p));
4393 sstrncpy(&q, stat_bom_val, l-(q-p));
4396 if(stat_boy_val){
4397 sstrncpy(&q, "/BOY=", l-(q-p));
4398 sstrncpy(&q, stat_boy_val, l-(q-p));
4401 sstrncpy(&q, "\" action=\"", l-(q-p));
4403 if(inherit_nick && *inherit_nick){
4404 sstrncpy(&q, "/INICK=", l-(q-p));
4405 sstrncpy(&q, inherit_nick, l-(q-p));
4406 fs_give((void **)&inherit_nick);
4409 if(action){
4410 if(action->is_a_role)
4411 sstrncpy(&q, "/ROLE=1", l-(q-p));
4413 if(action->is_a_incol)
4414 sstrncpy(&q, "/ISINCOL=1", l-(q-p));
4416 if(action->is_a_srch)
4417 sstrncpy(&q, "/ISSRCH=1", l-(q-p));
4419 if(action->is_a_score)
4420 sstrncpy(&q, "/ISSCORE=1", l-(q-p));
4422 if(action->is_a_filter){
4424 * Older pine will interpret a filter that has no folder
4425 * as a Delete, even if we set it up here to be a Just Set
4426 * State filter. Disable the filter for older versions in that
4427 * case. If kill is set then Delete is what is supposed to
4428 * happen, so that's ok. If folder is set then Move is what is
4429 * supposed to happen, so ok.
4431 if(!action->kill && !action->folder)
4432 sstrncpy(&q, "/FILTER=2", l-(q-p));
4433 else
4434 sstrncpy(&q, "/FILTER=1", l-(q-p));
4437 if(action->is_a_other)
4438 sstrncpy(&q, "/OTHER=1", l-(q-p));
4441 if(score_act){
4442 sstrncpy(&q, "/SCORE=", l-(q-p));
4443 sstrncpy(&q, score_act, l-(q-p));
4444 fs_give((void **)&score_act);
4447 if(hdrtok_act){
4448 sstrncpy(&q, "/SCOREHDRTOK=", l-(q-p));
4449 sstrncpy(&q, hdrtok_act, l-(q-p));
4450 fs_give((void **)&hdrtok_act);
4453 if(from_act){
4454 sstrncpy(&q, "/FROM=", l-(q-p));
4455 sstrncpy(&q, from_act, l-(q-p));
4456 fs_give((void **) &from_act);
4459 if(replyto_act){
4460 sstrncpy(&q, "/REPL=", l-(q-p));
4461 sstrncpy(&q, replyto_act, l-(q-p));
4462 fs_give((void **)&replyto_act);
4465 if(fcc_act){
4466 sstrncpy(&q, "/FCC=", l-(q-p));
4467 sstrncpy(&q, fcc_act, l-(q-p));
4468 fs_give((void **)&fcc_act);
4471 if(litsig_act){
4472 sstrncpy(&q, "/LSIG=", l-(q-p));
4473 sstrncpy(&q, litsig_act, l-(q-p));
4474 fs_give((void **)&litsig_act);
4477 if(sig_act){
4478 sstrncpy(&q, "/SIG=", l-(q-p));
4479 sstrncpy(&q, sig_act, l-(q-p));
4480 fs_give((void **)&sig_act);
4483 if(templ_act){
4484 sstrncpy(&q, "/TEMPLATE=", l-(q-p));
4485 sstrncpy(&q, templ_act, l-(q-p));
4486 fs_give((void **)&templ_act);
4489 if(cstm_act){
4490 sstrncpy(&q, "/CSTM=", l-(q-p));
4491 sstrncpy(&q, cstm_act, l-(q-p));
4492 fs_give((void **)&cstm_act);
4495 if(smtp_act){
4496 sstrncpy(&q, "/SMTP=", l-(q-p));
4497 sstrncpy(&q, smtp_act, l-(q-p));
4498 fs_give((void **)&smtp_act);
4501 if(nntp_act){
4502 sstrncpy(&q, "/NNTP=", l-(q-p));
4503 sstrncpy(&q, nntp_act, l-(q-p));
4504 fs_give((void **)&nntp_act);
4507 if(repl_val){
4508 sstrncpy(&q, "/RTYPE=", l-(q-p));
4509 sstrncpy(&q, repl_val, l-(q-p));
4512 if(forw_val){
4513 sstrncpy(&q, "/FTYPE=", l-(q-p));
4514 sstrncpy(&q, forw_val, l-(q-p));
4517 if(comp_val){
4518 sstrncpy(&q, "/CTYPE=", l-(q-p));
4519 sstrncpy(&q, comp_val, l-(q-p));
4522 if(incol_act){
4523 sstrncpy(&q, "/INCOL=", l-(q-p));
4524 sstrncpy(&q, incol_act, l-(q-p));
4525 fs_give((void **)&incol_act);
4528 if(sort_act){
4529 sstrncpy(&q, "/SORT=", l-(q-p));
4530 sstrncpy(&q, sort_act, l-(q-p));
4531 fs_give((void **)&sort_act);
4534 if(iform_act){
4535 sstrncpy(&q, "/IFORM=", l-(q-p));
4536 sstrncpy(&q, iform_act, l-(q-p));
4537 fs_give((void **)&iform_act);
4540 if(start_act){
4541 sstrncpy(&q, "/START=", l-(q-p));
4542 sstrncpy(&q, start_act, l-(q-p));
4545 if(folder_act){
4546 sstrncpy(&q, "/FOLDER=", l-(q-p));
4547 sstrncpy(&q, folder_act, l-(q-p));
4548 fs_give((void **) &folder_act);
4551 if(filt_ifnotdel){
4552 sstrncpy(&q, filt_ifnotdel, l-(q-p));
4553 fs_give((void **) &filt_ifnotdel);
4556 if(filt_nonterm){
4557 sstrncpy(&q, filt_nonterm, l-(q-p));
4558 fs_give((void **) &filt_nonterm);
4561 if(filt_nokill){
4562 sstrncpy(&q, filt_nokill, l-(q-p));
4563 fs_give((void **) &filt_nokill);
4566 if(filt_new_val){
4567 sstrncpy(&q, "/STATN=", l-(q-p));
4568 sstrncpy(&q, filt_new_val, l-(q-p));
4571 if(filt_del_val){
4572 sstrncpy(&q, "/STATD=", l-(q-p));
4573 sstrncpy(&q, filt_del_val, l-(q-p));
4576 if(filt_imp_val){
4577 sstrncpy(&q, "/STATI=", l-(q-p));
4578 sstrncpy(&q, filt_imp_val, l-(q-p));
4581 if(filt_ans_val){
4582 sstrncpy(&q, "/STATA=", l-(q-p));
4583 sstrncpy(&q, filt_ans_val, l-(q-p));
4586 if(keyword_set){
4587 sstrncpy(&q, "/KEYSET=", l-(q-p));
4588 sstrncpy(&q, keyword_set, l-(q-p));
4589 fs_give((void **) &keyword_set);
4592 if(keyword_clr){
4593 sstrncpy(&q, "/KEYCLR=", l-(q-p));
4594 sstrncpy(&q, keyword_clr, l-(q-p));
4595 fs_give((void **) &keyword_clr);
4598 if(q-p < l)
4599 *q++ = '\"';
4601 if(q-p < l)
4602 *q = '\0';
4604 p[l-1] = '\0';
4606 return(p);
4610 void
4611 convert_statebits_to_vals(long int bits, int *dval, int *aval, int *ival, int *nval)
4613 if(dval)
4614 *dval = ACT_STAT_LEAVE;
4615 if(aval)
4616 *aval = ACT_STAT_LEAVE;
4617 if(ival)
4618 *ival = ACT_STAT_LEAVE;
4619 if(nval)
4620 *nval = ACT_STAT_LEAVE;
4622 if(ival){
4623 if(bits & F_FLAG)
4624 *ival = ACT_STAT_SET;
4625 else if(bits & F_UNFLAG)
4626 *ival = ACT_STAT_CLEAR;
4629 if(aval){
4630 if(bits & F_ANS)
4631 *aval = ACT_STAT_SET;
4632 else if(bits & F_UNANS)
4633 *aval = ACT_STAT_CLEAR;
4636 if(dval){
4637 if(bits & F_DEL)
4638 *dval = ACT_STAT_SET;
4639 else if(bits & F_UNDEL)
4640 *dval = ACT_STAT_CLEAR;
4643 if(nval){
4644 if(bits & F_UNSEEN)
4645 *nval = ACT_STAT_SET;
4646 else if(bits & F_SEEN)
4647 *nval = ACT_STAT_CLEAR;
4653 * The "searched" bit will be set for each message which matches.
4655 * Args: patgrp -- Pattern to search with
4656 * stream --
4657 * searchset -- Restrict search to this set
4658 * section -- Searching a section of the message, not the whole thing
4659 * get_score -- Function to return the score for a message
4660 * flags -- Most of these are flags to mail_search_full. However, we
4661 * overload the flags namespace and pass some flags of our
4662 * own in here that we pick off before calling mail_search.
4663 * Danger, danger, don't overlap with flag values defined
4664 * for c-client (that we want to use). Flags that we will
4665 * use here are:
4666 * MP_IN_CCLIENT_CB
4667 * If this is set we are in a callback from c-client
4668 * because some imap data arrived. We don't want to
4669 * call c-client again because it isn't re-entrant safe.
4670 * This is only a problem if we need to get the text of
4671 * a message to do the search, the envelope is cached
4672 * already.
4673 * MP_NOT
4674 * We want a ! of the patgrp in the search.
4675 * We also throw in SE_FREE for free, since we create
4676 * the search program here.
4678 * Returns: 1 if any message in the searchset matches this pattern
4679 * 0 if no matches
4680 * -1 if couldn't perform search because of no_fetch restriction
4683 match_pattern(PATGRP_S *patgrp, MAILSTREAM *stream, SEARCHSET *searchset,
4684 char *section, long int (*get_score)(MAILSTREAM *, long int),
4685 long int flags)
4687 SEARCHPGM *pgm;
4688 SEARCHSET *s;
4689 MESSAGECACHE *mc;
4690 long i, msgno = 0L;
4691 int in_client_callback = 0, not = 0;
4693 dprint((7, "match_pattern\n"));
4696 * Is the current folder the right type and possibly the right specific
4697 * folder for a match?
4699 if(!(patgrp && !patgrp->bogus && match_pattern_folder(patgrp, stream)))
4700 return(0);
4703 * NULL searchset means that there is no message to compare against.
4704 * This is a match if the folder type matches above (that gets
4705 * us here), and there are no patterns to match against.
4707 * It is not totally clear what should be done in the case of an empty
4708 * search set. If there is search criteria, and someone does something
4709 * that is not specific to any messages (composing from scratch,
4710 * forwarding an attachment), then we can't be sure what a user would
4711 * expect. The original way was to just use the role, which we'll
4712 * preserve here.
4714 if(!searchset)
4715 return(1);
4718 * change by sderr : match_pattern_folder will sometimes
4719 * accept NULL streams, but if we are not in a folder-type-only
4720 * match test, we don't
4722 if(!stream)
4723 return(0);
4725 if(flags & MP_IN_CCLIENT_CB){
4726 in_client_callback++;
4727 flags &= ~MP_IN_CCLIENT_CB;
4730 if(flags & MP_NOT){
4731 not++;
4732 flags &= ~MP_NOT;
4735 flags |= SE_FREE;
4737 if(patgrp->stat_bom != PAT_STAT_EITHER){
4738 if(patgrp->stat_bom == PAT_STAT_YES){
4739 if(!ps_global->beginning_of_month){
4740 return(0);
4743 else if(patgrp->stat_bom == PAT_STAT_NO){
4744 if(ps_global->beginning_of_month){
4745 return(0);
4750 if(patgrp->stat_boy != PAT_STAT_EITHER){
4751 if(patgrp->stat_boy == PAT_STAT_YES){
4752 if(!ps_global->beginning_of_year){
4753 return(0);
4756 else if(patgrp->stat_boy == PAT_STAT_NO){
4757 if(ps_global->beginning_of_year){
4758 return(0);
4763 if(in_client_callback && is_imap_stream(stream)
4764 && (patgrp->alltext || patgrp->bodytext
4765 || (patgrp->inabook != IAB_EITHER
4766 && any_addressbook_in_remote_stream(stream))))
4767 return(-1);
4769 pgm = match_pattern_srchpgm(patgrp, stream, searchset);
4770 if(not && !(is_imap_stream(stream) && !modern_imap_stream(stream))){
4771 SEARCHPGM *srchpgm;
4773 srchpgm = pgm;
4774 pgm = mail_newsearchpgm();
4775 pgm->not = mail_newsearchpgmlist();
4776 pgm->not->pgm = srchpgm;
4779 if((patgrp->alltext || patgrp->bodytext)
4780 && (!is_imap_stream(stream) || modern_imap_stream(stream)))
4782 * Cache isn't going to work. Search on server.
4783 * Except that is likely to not work on an old imap server because
4784 * the OR criteria won't work and we are likely to have some ORs.
4785 * So turn off the NOSERVER flag (and search on server if remote)
4786 * unless the server is an old server. It doesn't matter if we
4787 * turn if off if it's not an imap stream, but we do it anyway.
4789 flags &= ~SE_NOSERVER;
4791 if(section){
4793 * Mail_search_full only searches the top-level msg. We want to
4794 * search an attached msg instead. First do the stuff
4795 * that mail_search_full would have done before calling
4796 * mail_search_msg, then call mail_search_msg with a section number.
4797 * Mail_search_msg does take a section number even though
4798 * mail_search_full doesn't.
4802 * We'll only ever set section if the searchset is a single message.
4804 if(pgm->msgno->next == NULL && pgm->msgno->first == pgm->msgno->last)
4805 msgno = pgm->msgno->first;
4807 for(i = 1L; i <= stream->nmsgs; i++)
4808 if((mc = mail_elt(stream, i)) != NULL)
4809 mc->searched = NIL;
4811 if(mail_search_msg(stream,msgno,section,pgm)
4812 && msgno > 0L && msgno <= stream->nmsgs
4813 && (mc = mail_elt(stream, msgno)))
4814 mc->searched = T;
4816 if(flags & SE_FREE)
4817 mail_free_searchpgm(&pgm);
4819 else{
4821 * Here we could be checking on the return value to see if
4822 * the search was "successful" or not. It may be the case
4823 * that we'd want to stop trying filtering if we got some
4824 * sort of error, but for now we would just continue on
4825 * to the next filter.
4827 pine_mail_search_full(stream, "UTF-8", pgm, flags);
4830 /* we searched without the not, reverse it */
4831 if(not && is_imap_stream(stream) && !modern_imap_stream(stream)){
4832 for(msgno = 1L; msgno < mn_get_total(sp_msgmap(stream)); msgno++)
4833 if(stream && msgno && msgno <= stream->nmsgs){
4834 if((mc=mail_elt(stream,msgno)) != NULL){
4835 if(mc->searched)
4836 mc->searched = NIL;
4837 else
4838 mc->searched = T;
4843 /* check scores */
4844 if(get_score && scores_are_used(SCOREUSE_GET) && patgrp->do_score){
4845 char *savebits;
4846 SEARCHSET *ss;
4849 * Get_score may call build_header_line recursively (we may
4850 * be in build_header_line now) so we have to preserve and
4851 * restore the sequence bits.
4853 savebits = (char *)fs_get((stream->nmsgs+1) * sizeof(char));
4855 for(i = 1L; i <= stream->nmsgs; i++){
4856 if((mc = mail_elt(stream, i)) != NULL){
4857 savebits[i] = mc->sequence;
4858 mc->sequence = 0;
4863 * Build a searchset which will get all the scores that we
4864 * need but not more.
4866 for(s = searchset; s; s = s->next)
4867 for(msgno = s->first; msgno <= s->last; msgno++)
4868 if(msgno > 0L && msgno <= stream->nmsgs
4869 && (mc = mail_elt(stream, msgno)) && mc->searched
4870 && get_msg_score(stream, msgno) == SCORE_UNDEF)
4871 mc->sequence = 1;
4873 if((ss = build_searchset(stream)) != NULL){
4874 (void)calculate_some_scores(stream, ss, in_client_callback);
4875 mail_free_searchset(&ss);
4879 * Now check the scores versus the score intervals to see if
4880 * any of the messages which have matched up to this point can
4881 * be tossed because they don't match the score interval.
4883 for(s = searchset; s; s = s->next)
4884 for(msgno = s->first; msgno <= s->last; msgno++)
4885 if(msgno > 0L && msgno <= stream->nmsgs
4886 && (mc = mail_elt(stream, msgno)) && mc->searched){
4887 long score;
4889 score = (*get_score)(stream, msgno);
4892 * If the score is outside all of the intervals,
4893 * turn off the searched bit.
4894 * So that means we check each interval and if
4895 * it is inside any interval we stop and leave
4896 * the bit set. If it is outside we keep checking.
4898 if(score != SCORE_UNDEF){
4899 INTVL_S *iv;
4901 for(iv = patgrp->score; iv; iv = iv->next)
4902 if(score >= iv->imin && score <= iv->imax)
4903 break;
4905 if(!iv)
4906 mc->searched = NIL;
4910 for(i = 1L; i <= stream->nmsgs; i++)
4911 if((mc = mail_elt(stream, i)) != NULL)
4912 mc->sequence = savebits[i];
4914 fs_give((void **)&savebits);
4917 /* if there are still matches, check for 8bit subject match */
4918 if(patgrp->stat_8bitsubj != PAT_STAT_EITHER)
4919 find_8bitsubj_in_messages(stream, searchset, patgrp->stat_8bitsubj, 1);
4921 /* if there are still matches, check for charset matches */
4922 if(patgrp->charsets)
4923 find_charsets_in_messages(stream, searchset, patgrp, 1);
4925 /* Still matches, check addrbook */
4926 if(patgrp->inabook != IAB_EITHER)
4927 address_in_abook(stream, searchset, patgrp->inabook, patgrp->abooks);
4929 /* Still matches? Run the categorization command on each msg. */
4930 if(pith_opt_filter_pattern_cmd)
4931 (*pith_opt_filter_pattern_cmd)(patgrp->category_cmd, searchset, stream, patgrp->cat_lim, patgrp->cat);
4933 for(s = searchset; s; s = s->next)
4934 for(msgno = s->first; msgno > 0L && msgno <= s->last; msgno++)
4935 if(msgno > 0L && msgno <= stream->nmsgs
4936 && (mc = mail_elt(stream, msgno)) && mc->searched)
4937 return(1);
4939 return(0);
4944 * Look through messages in searchset to see if they contain 8bit
4945 * characters in their subjects. All of the messages in
4946 * searchset should initially have the searched bit set. Turn off the
4947 * searched bit where appropriate.
4949 void
4950 find_8bitsubj_in_messages(MAILSTREAM *stream, SEARCHSET *searchset,
4951 int stat_8bitsubj, int saveseqbits)
4953 char *savebits = NULL;
4954 SEARCHSET *s, *ss = NULL;
4955 MESSAGECACHE *mc;
4956 long count = 0L;
4957 unsigned long msgno;
4960 * If we are being called while in build_header_line we may
4961 * call build_header_line recursively. So save and restore the
4962 * sequence bits.
4964 if(saveseqbits)
4965 savebits = (char *) fs_get((stream->nmsgs+1) * sizeof(char));
4967 for(msgno = 1L; msgno <= stream->nmsgs; msgno++){
4968 if((mc = mail_elt(stream, msgno)) != NULL){
4969 if(savebits)
4970 savebits[msgno] = mc->sequence;
4972 mc->sequence = 0;
4977 * Build a searchset so we can look at all the envelopes
4978 * we need to look at but only those we need to look at.
4979 * Everything with the searched bit set is still a
4980 * possibility, so restrict to that set.
4983 for(s = searchset; s; s = s->next)
4984 for(msgno = s->first; msgno <= s->last; msgno++)
4985 if(msgno > 0L && msgno <= stream->nmsgs
4986 && (mc = mail_elt(stream, msgno)) && mc->searched){
4987 mc->sequence = 1;
4988 count++;
4991 ss = build_searchset(stream);
4993 if(count){
4994 SEARCHSET **sset;
4996 mail_parameters(NULL, SET_FETCHLOOKAHEADLIMIT, (void *) count);
4999 * This causes the lookahead to fetch precisely
5000 * the messages we want (in the searchset) instead
5001 * of just fetching the next 20 sequential
5002 * messages. If the searching so far has caused
5003 * a sparse searchset in a large mailbox, the
5004 * difference can be substantial.
5005 * This resets automatically after the first fetch.
5007 sset = (SEARCHSET **) mail_parameters(stream,
5008 GET_FETCHLOOKAHEAD,
5009 (void *) stream);
5010 if(sset)
5011 *sset = ss;
5014 for(s = ss; s; s = s->next){
5015 for(msgno = s->first; msgno <= s->last; msgno++){
5016 ENVELOPE *e;
5018 if(!stream || msgno <= 0L || msgno > stream->nmsgs)
5019 continue;
5021 e = pine_mail_fetchenvelope(stream, msgno);
5022 if(stat_8bitsubj == PAT_STAT_YES){
5023 if(e && e->subject){
5024 char *p;
5026 for(p = e->subject; *p; p++)
5027 if(*p & 0x80)
5028 break;
5030 if(!*p && msgno > 0L && msgno <= stream->nmsgs
5031 && (mc = mail_elt(stream, msgno)))
5032 mc->searched = NIL;
5034 else if(msgno > 0L && msgno <= stream->nmsgs
5035 && (mc = mail_elt(stream, msgno)))
5036 mc->searched = NIL;
5038 else if(stat_8bitsubj == PAT_STAT_NO){
5039 if(e && e->subject){
5040 char *p;
5042 for(p = e->subject; *p; p++)
5043 if(*p & 0x80)
5044 break;
5046 if(*p && msgno > 0L && msgno <= stream->nmsgs
5047 && (mc = mail_elt(stream, msgno)))
5048 mc->searched = NIL;
5054 if(savebits){
5055 for(msgno = 1L; msgno <= stream->nmsgs; msgno++)
5056 if((mc = mail_elt(stream, msgno)) != NULL)
5057 mc->sequence = savebits[msgno];
5059 fs_give((void **) &savebits);
5062 if(ss)
5063 mail_free_searchset(&ss);
5068 * Look through messages in searchset to see if they contain any of the
5069 * charsets or scripts listed in charsets pattern. All of the messages in
5070 * searchset should initially have the searched bit set. Turn off the
5071 * searched bit where appropriate.
5073 void
5074 find_charsets_in_messages(MAILSTREAM *stream, SEARCHSET *searchset,
5075 PATGRP_S *patgrp, int saveseqbits)
5077 char *savebits = NULL;
5078 unsigned long msgno;
5079 long count = 0L;
5080 MESSAGECACHE *mc;
5081 SEARCHSET *s, *ss;
5083 if(!stream || !patgrp)
5084 return;
5087 * When we actually want to use charsets, we convert it into a list
5088 * of charsets instead of the mixed list of scripts and charsets and
5089 * we eliminate duplicates. This is more efficient when we actually
5090 * do the lookups and compares.
5092 if(!patgrp->charsets_list){
5093 PATTERN_S *cs;
5094 const CHARSET *cset;
5095 STRLIST_S *sl = NULL, *newsl;
5096 unsigned long scripts = 0L;
5097 SCRIPT *script;
5099 for(cs = patgrp->charsets; cs; cs = cs->next){
5101 * Run through the charsets pattern looking for
5102 * scripts and set the corresponding script bits.
5103 * If it isn't a script, it is a character set.
5105 if(cs->substring && (script = utf8_script(cs->substring)))
5106 scripts |= script->script;
5107 else{
5108 /* add it to list as a specific character set */
5109 newsl = new_strlist(cs->substring);
5110 if(compare_strlists_for_match(sl, newsl)) /* already in list */
5111 free_strlist(&newsl);
5112 else{
5113 newsl->next = sl;
5114 sl = newsl;
5120 * Now scripts has a bit set for each script the user
5121 * specified in the charsets pattern. Go through all of
5122 * the known charsets and include ones in these scripts.
5124 if(scripts){
5125 for(cset = utf8_charset(NIL); cset && cset->name; cset++){
5126 if(cset->script & scripts){
5128 /* filter this out of each script, not very useful */
5129 if(!strucmp("ISO-2022-JP-2", cset->name)
5130 || !strucmp("UTF-7", cset->name)
5131 || !strucmp("UTF-8", cset->name))
5132 continue;
5134 /* add cset->name to the list */
5135 newsl = new_strlist(cset->name);
5136 if(compare_strlists_for_match(sl, newsl))
5137 free_strlist(&newsl);
5138 else{
5139 newsl->next = sl;
5140 sl = newsl;
5146 patgrp->charsets_list = sl;
5150 * This may call build_header_line recursively because we may be in
5151 * build_header_line now. So we have to preserve and restore the
5152 * sequence bits since we want to use them here.
5154 if(saveseqbits)
5155 savebits = (char *) fs_get((stream->nmsgs+1) * sizeof(char));
5157 for(msgno = 1L; msgno <= stream->nmsgs; msgno++){
5158 if((mc = mail_elt(stream, msgno)) != NULL){
5159 if(savebits)
5160 savebits[msgno] = mc->sequence;
5162 mc->sequence = 0;
5168 * Build a searchset so we can look at all the bodies
5169 * we need to look at but only those we need to look at.
5170 * Everything with the searched bit set is still a
5171 * possibility, so restrict to that set.
5174 for(s = searchset; s; s = s->next)
5175 for(msgno = s->first; msgno <= s->last; msgno++)
5176 if(msgno > 0L && msgno <= stream->nmsgs
5177 && (mc = mail_elt(stream, msgno)) && mc->searched){
5178 mc->sequence = 1;
5179 count++;
5182 ss = build_searchset(stream);
5184 if(count){
5185 SEARCHSET **sset;
5187 mail_parameters(NULL, SET_FETCHLOOKAHEADLIMIT, (void *) count);
5190 * This causes the lookahead to fetch precisely
5191 * the messages we want (in the searchset) instead
5192 * of just fetching the next 20 sequential
5193 * messages. If the searching so far has caused
5194 * a sparse searchset in a large mailbox, the
5195 * difference can be substantial.
5196 * This resets automatically after the first fetch.
5198 sset = (SEARCHSET **) mail_parameters(stream,
5199 GET_FETCHLOOKAHEAD,
5200 (void *) stream);
5201 if(sset)
5202 *sset = ss;
5205 for(s = ss; s; s = s->next){
5206 for(msgno = s->first; msgno <= s->last; msgno++){
5208 if(msgno <= 0L || msgno > stream->nmsgs)
5209 continue;
5211 if(patgrp->charsets_list
5212 && charsets_present_in_msg(stream,msgno,patgrp->charsets_list)){
5213 if(patgrp->charsets->not){
5214 if((mc = mail_elt(stream, msgno)))
5215 mc->searched = NIL;
5217 /* else leave it */
5219 else{ /* charset isn't in message */
5220 if(!patgrp->charsets->not){
5221 if((mc = mail_elt(stream, msgno)))
5222 mc->searched = NIL;
5224 /* else leave it */
5229 if(savebits){
5230 for(msgno = 1L; msgno <= stream->nmsgs; msgno++)
5231 if((mc = mail_elt(stream, msgno)) != NULL)
5232 mc->sequence = savebits[msgno];
5234 fs_give((void **) &savebits);
5237 if(ss)
5238 mail_free_searchset(&ss);
5243 * Look for any of the charsets in this particular message.
5245 * Returns 1 if there is a match, 0 otherwise.
5248 charsets_present_in_msg(MAILSTREAM *stream, long unsigned int rawmsgno, STRLIST_S *charsets)
5250 BODY *body = NULL;
5251 ENVELOPE *env = NULL;
5252 STRLIST_S *msg_charsets = NULL;
5253 int ret = 0;
5255 if(charsets && stream && rawmsgno > 0L && rawmsgno <= stream->nmsgs){
5256 env = pine_mail_fetchstructure(stream, rawmsgno, &body);
5257 collect_charsets_from_subj(env, &msg_charsets);
5258 collect_charsets_from_body(body, &msg_charsets);
5259 if(msg_charsets){
5260 ret = compare_strlists_for_match(msg_charsets, charsets);
5261 free_strlist(&msg_charsets);
5265 return(ret);
5269 void
5270 collect_charsets_from_subj(ENVELOPE *env, STRLIST_S **listptr)
5272 STRLIST_S *newsl;
5273 char *text, *e;
5275 if(listptr && env && env->subject){
5276 /* find encoded word */
5277 for(text = env->subject; *text; text++){
5278 if((*text == '=') && (text[1] == '?') && isalpha(text[2]) &&
5279 (e = strchr(text+2,'?'))){
5280 *e = '\0'; /* tie off charset name */
5282 newsl = new_strlist(text+2);
5283 *e = '?';
5285 if(compare_strlists_for_match(*listptr, newsl))
5286 free_strlist(&newsl);
5287 else{
5288 newsl->next = *listptr;
5289 *listptr = newsl;
5298 * Check for any of the charsets in any of the charset params in
5299 * any of the text parts of the body of a message. Put them in the list
5300 * pointed to by listptr.
5302 void
5303 collect_charsets_from_body(struct mail_bodystruct *body, STRLIST_S **listptr)
5305 PART *part;
5306 char *cset;
5308 if(listptr && body){
5309 switch(body->type){
5310 case TYPEMULTIPART:
5311 for(part = body->nested.part; part; part = part->next)
5312 collect_charsets_from_body(&part->body, listptr);
5314 break;
5316 case TYPEMESSAGE:
5317 if(!strucmp(body->subtype, "RFC822")){
5318 collect_charsets_from_subj(body->nested.msg->env, listptr);
5319 collect_charsets_from_body(body->nested.msg->body, listptr);
5320 break;
5322 /* else fall through to text case */
5324 case TYPETEXT:
5325 cset = parameter_val(body->parameter, "charset");
5326 if(cset){
5327 STRLIST_S *newsl;
5329 newsl = new_strlist(cset);
5331 if(compare_strlists_for_match(*listptr, newsl))
5332 free_strlist(&newsl);
5333 else{
5334 newsl->next = *listptr;
5335 *listptr = newsl;
5338 fs_give((void **) &cset);
5341 break;
5343 default: /* non-text terminal mode */
5344 break;
5351 * If any of the names in list1 is the same as any of the names in list2
5352 * then return 1, else return 0. Comparison is case independent.
5355 compare_strlists_for_match(STRLIST_S *list1, STRLIST_S *list2)
5357 int ret = 0;
5358 STRLIST_S *cs1, *cs2;
5360 for(cs1 = list1; !ret && cs1; cs1 = cs1->next)
5361 for(cs2 = list2; !ret && cs2; cs2 = cs2->next)
5362 if(cs1->name && cs2->name && !strucmp(cs1->name, cs2->name))
5363 ret = 1;
5365 return(ret);
5370 match_pattern_folder(PATGRP_S *patgrp, MAILSTREAM *stream)
5372 int is_news;
5374 /* change by sderr : we match FLDR_ANY even if stream is NULL */
5375 return((patgrp->fldr_type == FLDR_ANY)
5376 || (stream
5377 && (((is_news = IS_NEWS(stream))
5378 && patgrp->fldr_type == FLDR_NEWS)
5379 || (!is_news && patgrp->fldr_type == FLDR_EMAIL)
5380 || (patgrp->fldr_type == FLDR_SPECIFIC
5381 && match_pattern_folder_specific(patgrp->folder,
5382 stream, FOR_PATTERN)))));
5387 * Returns positive if this stream is open on one of the folders in the
5388 * folders argument, 0 otherwise.
5390 * If FOR_PATTERN is set, this interprets simple names as nicknames in
5391 * the incoming collection, otherwise it treats simple names as being in
5392 * the primary collection.
5393 * If FOR_FILT is set, the folder names are detokenized before being used.
5396 match_pattern_folder_specific(PATTERN_S *folders, MAILSTREAM *stream, int flags)
5398 PATTERN_S *p;
5399 int match = 0;
5400 char *patfolder, *free_this = NULL;
5402 dprint((8, "match_pattern_folder_specific\n"));
5404 if(!(stream && stream->mailbox && stream->mailbox[0]))
5405 return(0);
5408 * For each of the folders in the pattern, see if we get
5409 * a match. We're just looking for any match. If none match,
5410 * we return 0, otherwise we fall through and check the rest
5411 * of the pattern. The fact that the string is called "substring"
5412 * is not meaningful. We're just using the convenient pattern
5413 * structure to store a list of folder names. They aren't
5414 * substrings of names, they are the whole name.
5416 for(p = folders; !match && p; p = p->next){
5417 free_this = NULL;
5418 if(flags & FOR_FILTER)
5419 patfolder = free_this = detoken_src(p->substring, FOR_FILT, NULL,
5420 NULL, NULL, NULL);
5421 else
5422 patfolder = p->substring;
5424 if(patfolder
5425 && (!strucmp(patfolder, ps_global->inbox_name)
5426 || !strcmp(patfolder, ps_global->VAR_INBOX_PATH))){
5427 if(sp_flagged(stream, SP_INBOX))
5428 match++;
5430 else{
5431 char *fname;
5432 char *t, *streamfolder;
5433 char tmp1[MAILTMPLEN], tmp2[MAX(MAILTMPLEN,NETMAXMBX)];
5434 CONTEXT_S *cntxt = NULL;
5436 if(flags & FOR_PATTERN){
5438 * See if patfolder is a nickname in the incoming collection.
5439 * If so, use its real name instead.
5441 if(patfolder[0] &&
5442 (ps_global->context_list->use & CNTXT_INCMNG) &&
5443 (fname = (folder_is_nick(patfolder,
5444 FOLDERS(ps_global->context_list),
5445 0))))
5446 patfolder = fname;
5448 else{
5449 char *save_ref = NULL;
5452 * If it's an absolute pathname, we treat is as a local file
5453 * instead of interpreting it in the primary context.
5455 if(!is_absolute_path(patfolder)
5456 && !(cntxt = default_save_context(ps_global->context_list)))
5457 cntxt = ps_global->context_list;
5460 * Because this check is independent of where the user is
5461 * in the folder hierarchy and has nothing to do with that,
5462 * we want to ignore the reference field built into the
5463 * context. Zero it out temporarily here.
5465 if(cntxt && cntxt->dir){
5466 save_ref = cntxt->dir->ref;
5467 cntxt->dir->ref = NULL;
5470 patfolder = context_apply(tmp1, cntxt, patfolder, sizeof(tmp1));
5471 if(save_ref)
5472 cntxt->dir->ref = save_ref;
5475 switch(patfolder[0]){
5476 case '{':
5477 if(stream->mailbox[0] == '{' &&
5478 same_stream(patfolder, stream) &&
5479 (streamfolder = strindex(&stream->mailbox[1], '}')) &&
5480 (t = strindex(&patfolder[1], '}')) &&
5481 (!strcmp(t+1, streamfolder+1) ||
5482 (*(t+1) == '\0' && !strcmp("INBOX", streamfolder+1))))
5483 match++;
5485 break;
5487 case '#':
5488 if(!strcmp(patfolder, stream->mailbox))
5489 match++;
5491 break;
5493 default:
5494 t = (strlen(patfolder) < (MAILTMPLEN/2))
5495 ? mailboxfile(tmp2, patfolder) : NULL;
5496 if(t && *t && !strcmp(t, stream->mailbox))
5497 match++;
5499 break;
5503 if(free_this)
5504 fs_give((void **) &free_this);
5507 return(match);
5512 * generate a search program corresponding to the provided patgrp
5514 SEARCHPGM *
5515 match_pattern_srchpgm(PATGRP_S *patgrp, MAILSTREAM *stream, SEARCHSET *searchset)
5517 SEARCHPGM *pgm, *tmppgm;
5518 SEARCHOR *or;
5519 SEARCHSET **sp;
5521 pgm = mail_newsearchpgm();
5523 sp = &pgm->msgno;
5524 /* copy the searchset */
5525 while(searchset){
5526 SEARCHSET *s;
5528 s = mail_newsearchset();
5529 s->first = searchset->first;
5530 s->last = searchset->last;
5531 searchset = searchset->next;
5532 *sp = s;
5533 sp = &s->next;
5536 if(!patgrp)
5537 return(pgm);
5539 if(patgrp->subj){
5540 if(patgrp->subj->not)
5541 tmppgm = next_not(pgm);
5542 else
5543 tmppgm = pgm;
5545 set_up_search_pgm("subject", patgrp->subj, tmppgm);
5548 if(patgrp->cc){
5549 if(patgrp->cc->not)
5550 tmppgm = next_not(pgm);
5551 else
5552 tmppgm = pgm;
5554 set_up_search_pgm("cc", patgrp->cc, tmppgm);
5557 if(patgrp->from){
5558 if(patgrp->from->not)
5559 tmppgm = next_not(pgm);
5560 else
5561 tmppgm = pgm;
5563 set_up_search_pgm("from", patgrp->from, tmppgm);
5566 if(patgrp->to){
5567 if(patgrp->to->not)
5568 tmppgm = next_not(pgm);
5569 else
5570 tmppgm = pgm;
5572 set_up_search_pgm("to", patgrp->to, tmppgm);
5575 if(patgrp->sender){
5576 if(patgrp->sender->not)
5577 tmppgm = next_not(pgm);
5578 else
5579 tmppgm = pgm;
5581 set_up_search_pgm("sender", patgrp->sender, tmppgm);
5584 if(patgrp->news){
5585 if(patgrp->news->not)
5586 tmppgm = next_not(pgm);
5587 else
5588 tmppgm = pgm;
5590 set_up_search_pgm("newsgroups", patgrp->news, tmppgm);
5593 /* To OR Cc */
5594 if(patgrp->recip){
5595 if(patgrp->recip->not)
5596 tmppgm = next_not(pgm);
5597 else
5598 tmppgm = pgm;
5600 or = next_or(&tmppgm->or);
5602 set_up_search_pgm("to", patgrp->recip, or->first);
5603 set_up_search_pgm("cc", patgrp->recip, or->second);
5606 /* To OR Cc OR From */
5607 if(patgrp->partic){
5608 if(patgrp->partic->not)
5609 tmppgm = next_not(pgm);
5610 else
5611 tmppgm = pgm;
5613 or = next_or(&tmppgm->or);
5615 set_up_search_pgm("to", patgrp->partic, or->first);
5617 or->second->or = mail_newsearchor();
5618 set_up_search_pgm("cc", patgrp->partic, or->second->or->first);
5619 set_up_search_pgm("from", patgrp->partic, or->second->or->second);
5622 if(patgrp->arbhdr){
5623 ARBHDR_S *a;
5625 for(a = patgrp->arbhdr; a; a = a->next)
5626 if(a->field && a->field[0] && a->p){
5627 if(a->p->not)
5628 tmppgm = next_not(pgm);
5629 else
5630 tmppgm = pgm;
5632 set_up_search_pgm(a->field, a->p, tmppgm);
5636 if(patgrp->alltext){
5637 if(patgrp->alltext->not)
5638 tmppgm = next_not(pgm);
5639 else
5640 tmppgm = pgm;
5642 set_up_search_pgm("alltext", patgrp->alltext, tmppgm);
5645 if(patgrp->bodytext){
5646 if(patgrp->bodytext->not)
5647 tmppgm = next_not(pgm);
5648 else
5649 tmppgm = pgm;
5651 set_up_search_pgm("bodytext", patgrp->bodytext, tmppgm);
5654 if(patgrp->keyword){
5655 PATTERN_S *p_old, *p_new, *new_pattern = NULL, **nextp;
5656 char *q;
5658 if(patgrp->keyword->not)
5659 tmppgm = next_not(pgm);
5660 else
5661 tmppgm = pgm;
5664 * The keyword entries may be nicknames instead of the actual
5665 * keywords, so those need to be converted to actual keywords.
5667 * If we search for keywords that are not defined for a folder
5668 * we may get error messages back that we don't want instead of
5669 * just no match. We will build a replacement pattern here which
5670 * contains only the defined subset of the keywords.
5673 nextp = &new_pattern;
5675 for(p_old = patgrp->keyword; p_old; p_old = p_old->next){
5676 q = nick_to_keyword(p_old->substring);
5677 if(user_flag_index(stream, q) >= 0){
5678 p_new = (PATTERN_S *) fs_get(sizeof(*p_new));
5679 memset(p_new, 0, sizeof(*p_new));
5680 p_new->substring = cpystr(q);
5681 *nextp = p_new;
5682 nextp = &p_new->next;
5687 * If there are some matching keywords that are defined in
5688 * the folder, then we are ok because we will match only if
5689 * we match one of those. However, if the list is empty, then
5690 * we can't just leave this part of the search program empty.
5691 * That would result in a match instead of not a match.
5692 * We can fake our way around the problem with NOT. If the
5693 * list is empty we want the opposite, so we insert a NOT in
5694 * front of an empty program. We may end up with NOT NOT if
5695 * this was already NOT'd, but that's ok, too. Alternatively,
5696 * we could undo the first NOT instead.
5699 if(new_pattern){
5700 set_up_search_pgm("keyword", new_pattern, tmppgm);
5701 free_pattern(&new_pattern);
5703 else
5704 (void) next_not(tmppgm); /* add NOT of something that matches,
5705 so the NOT thing doesn't match */
5708 if(patgrp->do_age && patgrp->age){
5709 INTVL_S *iv;
5710 SEARCHOR *or;
5712 tmppgm = pgm;
5714 for(iv = patgrp->age; iv; iv = iv->next){
5715 if(iv->next){
5716 or = next_or(&tmppgm->or);
5717 set_search_by_age(iv, or->first, patgrp->age_uses_sentdate);
5718 tmppgm = or->second;
5720 else
5721 set_search_by_age(iv, tmppgm, patgrp->age_uses_sentdate);
5725 if(patgrp->do_size && patgrp->size){
5726 INTVL_S *iv;
5727 SEARCHOR *or;
5729 tmppgm = pgm;
5731 for(iv = patgrp->size; iv; iv = iv->next){
5732 if(iv->next){
5733 or = next_or(&tmppgm->or);
5734 set_search_by_size(iv, or->first);
5735 tmppgm = or->second;
5737 else
5738 set_search_by_size(iv, tmppgm);
5742 SETPGMSTATUS(patgrp->stat_new,pgm->unseen,pgm->seen);
5743 SETPGMSTATUS(patgrp->stat_rec,pgm->recent,pgm->old);
5744 SETPGMSTATUS(patgrp->stat_del,pgm->deleted,pgm->undeleted);
5745 SETPGMSTATUS(patgrp->stat_imp,pgm->flagged,pgm->unflagged);
5746 SETPGMSTATUS(patgrp->stat_ans,pgm->answered,pgm->unanswered);
5748 return(pgm);
5752 SEARCHPGM *
5753 next_not(SEARCHPGM *pgm)
5755 SEARCHPGMLIST *not, **not_ptr;
5757 if(!pgm)
5758 return(NULL);
5760 /* find next unused not slot */
5761 for(not = pgm->not; not && not->next; not = not->next)
5764 if(not)
5765 not_ptr = &not->next;
5766 else
5767 not_ptr = &pgm->not;
5769 /* allocate */
5770 *not_ptr = mail_newsearchpgmlist();
5772 return((*not_ptr)->pgm);
5776 SEARCHOR *
5777 next_or(struct search_or **startingor)
5779 SEARCHOR *or, **or_ptr;
5781 /* find next unused or slot */
5782 for(or = (*startingor); or && or->next; or = or->next)
5785 if(or)
5786 or_ptr = &or->next;
5787 else
5788 or_ptr = startingor;
5790 /* allocate */
5791 *or_ptr = mail_newsearchor();
5793 return(*or_ptr);
5797 void
5798 set_up_search_pgm(char *field, PATTERN_S *pattern, SEARCHPGM *pgm)
5800 SEARCHOR *or;
5802 if(field && pattern && pgm){
5805 * To is special because we want to use the ReSent-To header instead
5806 * of the To header if it exists. We set up something like:
5808 * if((resent-to matches pat1 or pat2...)
5809 * OR
5810 * (<resent-to doesn't exist> AND (to matches pat1 or pat2...)))
5812 * Some servers (Exchange, apparently) seem to have trouble with
5813 * the search for the empty string to decide if the header exists
5814 * or not. So, we will search for either the empty string OR the
5815 * header with a SPACE in it. Some still have trouble with this
5816 * so we are changing it to be off by default.
5818 if(!strucmp(field, "to") && F_ON(F_USE_RESENTTO, ps_global)){
5819 or = next_or(&pgm->or);
5821 add_type_to_pgm("resent-to", pattern, or->first);
5823 /* check for resent-to doesn't exist */
5824 or->second->not = mail_newsearchpgmlist();
5826 or->second->not->pgm->or = mail_newsearchor();
5827 set_srch("resent-to", " ", or->second->not->pgm->or->first);
5828 set_srch("resent-to", "", or->second->not->pgm->or->second);
5830 /* now add the real To search to second */
5831 add_type_to_pgm(field, pattern, or->second);
5833 else
5834 add_type_to_pgm(field, pattern, pgm);
5839 void
5840 add_type_to_pgm(char *field, PATTERN_S *pattern, SEARCHPGM *pgm)
5842 PATTERN_S *p;
5843 SEARCHOR *or;
5844 SEARCHPGM *notpgm, *tpgm;
5845 int cnt = 0;
5847 if(field && pattern && pgm){
5849 * Here is a weird bit of logic. What we want here is simply
5850 * A or B or C or D
5851 * for all of the elements of pattern. Ors are a bit complicated.
5852 * The list of ORs in the SEARCHPGM structure are ANDed together,
5853 * not ORd together. It's for things like
5854 * Subject A or B AND From C or D
5855 * The Subject part would be one member of the OR list and the From
5856 * part would be another member of the OR list. Instead we want
5857 * a big OR which may have more than two members (first and second)
5858 * but the structure just has two members. So we have to build an
5859 * OR tree and we build it by going down one branch of the tree
5860 * instead of by balancing the branches.
5862 * or
5863 * / \
5864 * first==A second
5865 * / \
5866 * first==B second
5867 * / \
5868 * first==C second==D
5870 * There is an additional problem. Some servers don't like deeply
5871 * nested logic in the SEARCH command. The tree above produces a
5872 * fairly deeply nested command if the user wants to match on
5873 * several different From addresses or Subjects...
5874 * We use the tried and true equation
5876 * (A or B) == !(!A and !B)
5878 * to change the deeply nested OR tree into ANDs which aren't nested.
5879 * Right now we're only doing that if the nesting is fairly deep.
5880 * We can think of some reasons to do that. First, we know that the
5881 * OR thing works, that's what we've been using for a while and the
5882 * only problem is the deep nesting. 2nd, it is easier to understand.
5883 * 3rd, it looks dumb to use NOT NOT A instead of A.
5884 * It is probably dumb to mix the two, but what the heck.
5885 * Hubert 2003-04-02
5887 for(p = pattern; p; p = p->next)
5888 cnt++;
5890 if(cnt < 10){ /* use ORs if count is low */
5891 for(p = pattern; p; p = p->next){
5892 if(p->next){
5893 or = next_or(&pgm->or);
5895 set_srch(field, p->substring ? p->substring : "", or->first);
5896 pgm = or->second;
5898 else
5899 set_srch(field, p->substring ? p->substring : "", pgm);
5902 else{ /* else use ANDs */
5903 /* ( A or B or C ) <=> ! ( !A and !B and !C ) */
5905 /* first, NOT of the whole thing */
5906 notpgm = next_not(pgm);
5908 /* then the not list is ANDed together */
5909 for(p = pattern; p; p = p->next){
5910 tpgm = next_not(notpgm);
5911 set_srch(field, p->substring ? p->substring : "", tpgm);
5918 void
5919 set_srch(char *field, char *value, SEARCHPGM *pgm)
5921 char *decoded;
5922 STRINGLIST **list;
5924 if(!(field && value && pgm))
5925 return;
5927 if(!strucmp(field, "subject"))
5928 list = &pgm->subject;
5929 else if(!strucmp(field, "from"))
5930 list = &pgm->from;
5931 else if(!strucmp(field, "to"))
5932 list = &pgm->to;
5933 else if(!strucmp(field, "cc"))
5934 list = &pgm->cc;
5935 else if(!strucmp(field, "sender"))
5936 list = &pgm->sender;
5937 else if(!strucmp(field, "reply-to"))
5938 list = &pgm->reply_to;
5939 else if(!strucmp(field, "in-reply-to"))
5940 list = &pgm->in_reply_to;
5941 else if(!strucmp(field, "message-id"))
5942 list = &pgm->message_id;
5943 else if(!strucmp(field, "newsgroups"))
5944 list = &pgm->newsgroups;
5945 else if(!strucmp(field, "followup-to"))
5946 list = &pgm->followup_to;
5947 else if(!strucmp(field, "alltext"))
5948 list = &pgm->text;
5949 else if(!strucmp(field, "bodytext"))
5950 list = &pgm->body;
5951 else if(!strucmp(field, "keyword"))
5952 list = &pgm->keyword;
5953 else{
5954 set_srch_hdr(field, value, pgm);
5955 return;
5958 if(!list)
5959 return;
5961 *list = mail_newstringlist();
5962 decoded = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, value);
5964 (*list)->text.data = (unsigned char *)cpystr(decoded);
5965 (*list)->text.size = strlen(decoded);
5969 void
5970 set_srch_hdr(char *field, char *value, SEARCHPGM *pgm)
5972 char *decoded;
5973 SEARCHHEADER **hdr;
5975 if(!(field && value && pgm))
5976 return;
5978 hdr = &pgm->header;
5979 if(!hdr)
5980 return;
5982 decoded = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
5983 SIZEOF_20KBUF, value);
5984 while(*hdr && (*hdr)->next)
5985 *hdr = (*hdr)->next;
5987 if(*hdr)
5988 (*hdr)->next = mail_newsearchheader(field, decoded);
5989 else
5990 *hdr = mail_newsearchheader(field, decoded);
5994 void
5995 set_search_by_age(INTVL_S *age, SEARCHPGM *pgm, int age_uses_sentdate)
5997 time_t now, comparetime;
5998 struct tm *tm;
5999 unsigned short i;
6001 if(!(age && pgm))
6002 return;
6004 now = time(0);
6006 if(age->imin >= 0L && age->imin == age->imax){
6007 comparetime = now;
6008 comparetime -= (age->imin * 86400L);
6009 tm = localtime(&comparetime);
6010 if(tm && tm->tm_year >= 70){
6011 i = mail_shortdate(tm->tm_year - 70, tm->tm_mon + 1,
6012 tm->tm_mday);
6013 if(age_uses_sentdate)
6014 pgm->senton = i;
6015 else
6016 pgm->on = i;
6019 else{
6021 * The 20000's are just protecting against overflows.
6022 * That's back past the start of email time, anyway.
6024 if(age->imin > 0L && age->imin < 20000L){
6025 comparetime = now;
6026 comparetime -= ((age->imin - 1L) * 86400L);
6027 tm = localtime(&comparetime);
6028 if(tm && tm->tm_year >= 70){
6029 i = mail_shortdate(tm->tm_year - 70, tm->tm_mon + 1,
6030 tm->tm_mday);
6031 if(age_uses_sentdate)
6032 pgm->sentbefore = i;
6033 else
6034 pgm->before = i;
6038 if(age->imax >= 0L && age->imax < 20000L){
6039 comparetime = now;
6040 comparetime -= (age->imax * 86400L);
6041 tm = localtime(&comparetime);
6042 if(tm && tm->tm_year >= 70){
6043 i = mail_shortdate(tm->tm_year - 70, tm->tm_mon + 1,
6044 tm->tm_mday);
6045 if(age_uses_sentdate)
6046 pgm->sentsince = i;
6047 else
6048 pgm->since = i;
6055 void
6056 set_search_by_size(INTVL_S *size, SEARCHPGM *pgm)
6058 if(!(size && pgm))
6059 return;
6062 * INTVL_S intervals include the endpoints, pgm larger and smaller
6063 * do not include the endpoints.
6065 if(size->imin != INTVL_UNDEF && size->imin > 0L)
6066 pgm->larger = size->imin - 1L;
6068 if(size->imax != INTVL_UNDEF && size->imax >= 0L && size->imax != INTVL_INF)
6069 pgm->smaller = size->imax + 1L;
6073 static char *extra_hdrs;
6076 * Run through the patterns and note which headers we'll need to ask for
6077 * which aren't normally asked for and so won't be cached.
6079 void
6080 calc_extra_hdrs(void)
6082 PAT_S *pat = NULL;
6083 int alloced_size;
6084 long type = (ROLE_INCOL | ROLE_SCORE);
6085 ARBHDR_S *a;
6086 PAT_STATE pstate;
6087 char *q, *p = NULL, *hdrs[MLCMD_COUNT + 1], **pp;
6088 INDEX_COL_S *cdesc;
6089 #define INITIALSIZE 1000
6091 q = (char *)fs_get((INITIALSIZE+1) * sizeof(char));
6092 q[0] = '\0';
6093 alloced_size = INITIALSIZE;
6094 p = q;
6097 * *ALWAYS* make sure Resent-To is in the set of
6098 * extra headers getting fetched.
6100 * This is because we *will* reference it when we're
6101 * building header lines and thus want it fetched with
6102 * the standard envelope data. Worse, in the IMAP case
6103 * we're called back from c-client with the envelope data
6104 * so we can format and display the index lines as they
6105 * arrive, so we have to ensure the resent-to field
6106 * is in the cache so we don't reenter c-client
6107 * to look for it from the callback. Yeouch.
6109 add_eh(&q, &p, "resent-to", &alloced_size);
6110 add_eh(&q, &p, "resent-date", &alloced_size);
6111 add_eh(&q, &p, "resent-from", &alloced_size);
6112 add_eh(&q, &p, "resent-cc", &alloced_size);
6113 add_eh(&q, &p, "resent-subject", &alloced_size);
6116 * Sniff at viewer-hdrs too so we can include them
6117 * if there are any...
6119 for(pp = ps_global->VAR_VIEW_HEADERS; pp && *pp; pp++)
6120 if(non_eh(*pp))
6121 add_eh(&q, &p, *pp, &alloced_size);
6124 * Be sure to ask for List management headers too
6125 * since we'll offer their use in the message view
6127 for(pp = rfc2369_hdrs(hdrs); *pp; pp++)
6128 add_eh(&q, &p, *pp, &alloced_size);
6130 if(nonempty_patterns(type, &pstate))
6131 for(pat = first_pattern(&pstate);
6132 pat;
6133 pat = next_pattern(&pstate)){
6135 * This section wouldn't be necessary if sender was retrieved
6136 * from the envelope. But if not, we do need to add it.
6138 if(pat->patgrp && pat->patgrp->sender)
6139 add_eh(&q, &p, "sender", &alloced_size);
6141 if(pat->patgrp && pat->patgrp->arbhdr)
6142 for(a = pat->patgrp->arbhdr; a; a = a->next)
6143 if(a->field && a->field[0] && a->p && non_eh(a->field))
6144 add_eh(&q, &p, a->field, &alloced_size);
6148 * Check for use of HEADER or X-Priority in index-format.
6150 for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing; cdesc++){
6151 if(cdesc->ctype == iHeader && cdesc->hdrtok && cdesc->hdrtok->hdrname
6152 && cdesc->hdrtok->hdrname[0] && non_eh(cdesc->hdrtok->hdrname))
6153 add_eh(&q, &p, cdesc->hdrtok->hdrname, &alloced_size);
6154 else if(cdesc->ctype == iPrio
6155 || cdesc->ctype == iPrioAlpha
6156 || cdesc->ctype == iPrioBang)
6157 add_eh(&q, &p, PRIORITYNAME, &alloced_size);
6161 * Check for use of scorevalhdrtok in scoring patterns.
6163 type = ROLE_SCORE;
6164 if(nonempty_patterns(type, &pstate))
6165 for(pat = first_pattern(&pstate);
6166 pat;
6167 pat = next_pattern(&pstate)){
6169 * This section wouldn't be necessary if sender was retrieved
6170 * from the envelope. But if not, we do need to add it.
6172 if(pat->action && pat->action->scorevalhdrtok
6173 && pat->action->scorevalhdrtok->hdrname
6174 && pat->action->scorevalhdrtok->hdrname[0]
6175 && non_eh(pat->action->scorevalhdrtok->hdrname))
6176 add_eh(&q, &p, pat->action->scorevalhdrtok->hdrname, &alloced_size);
6179 set_extra_hdrs(q);
6180 if(q)
6181 fs_give((void **)&q);
6186 non_eh(char *field)
6188 char **t;
6189 static char *existing[] = {"subject", "from", "to", "cc", "sender",
6190 "reply-to", "in-reply-to", "message-id",
6191 "path", "newsgroups", "followup-to",
6192 "references", NULL};
6195 * If it is one of these, we should already have it
6196 * from the envelope or from the extra headers c-client
6197 * already adds to the list (hdrheader and hdrtrailer
6198 * in imap4r1.c, Aug 99, slh).
6200 for(t = existing; *t; t++)
6201 if(!strucmp(field, *t))
6202 return(FALSE);
6204 return(TRUE);
6209 * Add field to extra headers string if not already there.
6211 void
6212 add_eh(char **start, char **ptr, char *field, int *asize)
6214 char *s;
6216 /* already there? */
6217 for(s = *start; (s = srchstr(s, field)) != NULL; s++)
6218 if(s[strlen(field)] == SPACE || s[strlen(field)] == '\0')
6219 return;
6221 /* enough space for it? */
6222 while(strlen(field) + (*ptr - *start) + 1 > *asize){
6223 (*asize) *= 2;
6224 fs_resize((void **)start, (*asize)+1);
6225 *ptr = *start + strlen(*start);
6228 if(*ptr > *start)
6229 sstrncpy(ptr, " ", *asize-(*ptr - *start));
6231 sstrncpy(ptr, field, *asize-(*ptr - *start));
6233 (*start)[*asize] = '\0';
6237 void
6238 set_extra_hdrs(char *hdrs)
6240 free_extra_hdrs();
6241 if(hdrs && *hdrs)
6242 extra_hdrs = cpystr(hdrs);
6246 char *
6247 get_extra_hdrs(void)
6249 return(extra_hdrs);
6253 void
6254 free_extra_hdrs(void)
6256 if(extra_hdrs)
6257 fs_give((void **)&extra_hdrs);
6262 is_ascii_string(char *str)
6264 if(!str)
6265 return(0);
6267 while(*str && isascii(*str))
6268 str++;
6270 return(*str == '\0');
6274 void
6275 free_patline(PAT_LINE_S **patline)
6277 if(patline && *patline){
6278 free_patline(&(*patline)->next);
6279 if((*patline)->filename)
6280 fs_give((void **)&(*patline)->filename);
6281 if((*patline)->filepath)
6282 fs_give((void **)&(*patline)->filepath);
6283 free_pat(&(*patline)->first);
6284 fs_give((void **)patline);
6289 void
6290 free_pat(PAT_S **pat)
6292 if(pat && *pat){
6293 free_pat(&(*pat)->next);
6294 free_patgrp(&(*pat)->patgrp);
6295 free_action(&(*pat)->action);
6296 if((*pat)->raw)
6297 fs_give((void **)&(*pat)->raw);
6299 fs_give((void **)pat);
6304 void
6305 free_patgrp(PATGRP_S **patgrp)
6307 if(patgrp && *patgrp){
6308 if((*patgrp)->nick)
6309 fs_give((void **) &(*patgrp)->nick);
6311 if((*patgrp)->comment)
6312 fs_give((void **) &(*patgrp)->comment);
6314 if((*patgrp)->category_cmd)
6315 free_list_array(&(*patgrp)->category_cmd);
6317 if((*patgrp)->charsets_list)
6318 free_strlist(&(*patgrp)->charsets_list);
6320 free_pattern(&(*patgrp)->to);
6321 free_pattern(&(*patgrp)->cc);
6322 free_pattern(&(*patgrp)->recip);
6323 free_pattern(&(*patgrp)->partic);
6324 free_pattern(&(*patgrp)->from);
6325 free_pattern(&(*patgrp)->sender);
6326 free_pattern(&(*patgrp)->news);
6327 free_pattern(&(*patgrp)->subj);
6328 free_pattern(&(*patgrp)->alltext);
6329 free_pattern(&(*patgrp)->bodytext);
6330 free_pattern(&(*patgrp)->keyword);
6331 free_pattern(&(*patgrp)->charsets);
6332 free_pattern(&(*patgrp)->folder);
6333 free_arbhdr(&(*patgrp)->arbhdr);
6334 free_intvl(&(*patgrp)->score);
6335 free_intvl(&(*patgrp)->age);
6336 fs_give((void **) patgrp);
6341 void
6342 free_pattern(PATTERN_S **pattern)
6344 if(pattern && *pattern){
6345 free_pattern(&(*pattern)->next);
6346 if((*pattern)->substring)
6347 fs_give((void **)&(*pattern)->substring);
6348 fs_give((void **)pattern);
6353 void
6354 free_arbhdr(ARBHDR_S **arbhdr)
6356 if(arbhdr && *arbhdr){
6357 free_arbhdr(&(*arbhdr)->next);
6358 if((*arbhdr)->field)
6359 fs_give((void **)&(*arbhdr)->field);
6360 free_pattern(&(*arbhdr)->p);
6361 fs_give((void **)arbhdr);
6366 void
6367 free_intvl(INTVL_S **intvl)
6369 if(intvl && *intvl){
6370 free_intvl(&(*intvl)->next);
6371 fs_give((void **) intvl);
6376 void
6377 free_action(ACTION_S **action)
6379 if(action && *action){
6380 if((*action)->from)
6381 mail_free_address(&(*action)->from);
6382 if((*action)->replyto)
6383 mail_free_address(&(*action)->replyto);
6384 if((*action)->fcc)
6385 fs_give((void **)&(*action)->fcc);
6386 if((*action)->litsig)
6387 fs_give((void **)&(*action)->litsig);
6388 if((*action)->sig)
6389 fs_give((void **)&(*action)->sig);
6390 if((*action)->template)
6391 fs_give((void **)&(*action)->template);
6392 if((*action)->scorevalhdrtok)
6393 free_hdrtok(&(*action)->scorevalhdrtok);
6394 if((*action)->cstm)
6395 free_list_array(&(*action)->cstm);
6396 if((*action)->smtp)
6397 free_list_array(&(*action)->smtp);
6398 if((*action)->nntp)
6399 free_list_array(&(*action)->nntp);
6400 if((*action)->nick)
6401 fs_give((void **)&(*action)->nick);
6402 if((*action)->inherit_nick)
6403 fs_give((void **)&(*action)->inherit_nick);
6404 if((*action)->incol)
6405 free_color_pair(&(*action)->incol);
6406 if((*action)->folder)
6407 free_pattern(&(*action)->folder);
6408 if((*action)->index_format)
6409 fs_give((void **)&(*action)->index_format);
6410 if((*action)->keyword_set)
6411 free_pattern(&(*action)->keyword_set);
6412 if((*action)->keyword_clr)
6413 free_pattern(&(*action)->keyword_clr);
6415 fs_give((void **)action);
6421 * Returns an allocated copy of the pat.
6423 * Args pat -- the source pat
6425 * Returns a copy of pat.
6427 PAT_S *
6428 copy_pat(PAT_S *pat)
6430 PAT_S *new_pat = NULL;
6432 if(pat){
6433 new_pat = (PAT_S *)fs_get(sizeof(*new_pat));
6434 memset((void *)new_pat, 0, sizeof(*new_pat));
6436 new_pat->patgrp = copy_patgrp(pat->patgrp);
6437 new_pat->action = copy_action(pat->action);
6440 return(new_pat);
6445 * Returns an allocated copy of the patgrp.
6447 * Args patgrp -- the source patgrp
6449 * Returns a copy of patgrp.
6451 PATGRP_S *
6452 copy_patgrp(PATGRP_S *patgrp)
6454 char *p;
6455 PATGRP_S *new_patgrp = NULL;
6457 if(patgrp){
6458 new_patgrp = (PATGRP_S *)fs_get(sizeof(*new_patgrp));
6459 memset((void *)new_patgrp, 0, sizeof(*new_patgrp));
6461 if(patgrp->nick)
6462 new_patgrp->nick = cpystr(patgrp->nick);
6464 if(patgrp->comment)
6465 new_patgrp->comment = cpystr(patgrp->comment);
6467 if(patgrp->to){
6468 p = pattern_to_string(patgrp->to);
6469 new_patgrp->to = string_to_pattern(p);
6470 fs_give((void **)&p);
6471 new_patgrp->to->not = patgrp->to->not;
6474 if(patgrp->from){
6475 p = pattern_to_string(patgrp->from);
6476 new_patgrp->from = string_to_pattern(p);
6477 fs_give((void **)&p);
6478 new_patgrp->from->not = patgrp->from->not;
6481 if(patgrp->sender){
6482 p = pattern_to_string(patgrp->sender);
6483 new_patgrp->sender = string_to_pattern(p);
6484 fs_give((void **)&p);
6485 new_patgrp->sender->not = patgrp->sender->not;
6488 if(patgrp->cc){
6489 p = pattern_to_string(patgrp->cc);
6490 new_patgrp->cc = string_to_pattern(p);
6491 fs_give((void **)&p);
6492 new_patgrp->cc->not = patgrp->cc->not;
6495 if(patgrp->recip){
6496 p = pattern_to_string(patgrp->recip);
6497 new_patgrp->recip = string_to_pattern(p);
6498 fs_give((void **)&p);
6499 new_patgrp->recip->not = patgrp->recip->not;
6502 if(patgrp->partic){
6503 p = pattern_to_string(patgrp->partic);
6504 new_patgrp->partic = string_to_pattern(p);
6505 fs_give((void **)&p);
6506 new_patgrp->partic->not = patgrp->partic->not;
6509 if(patgrp->news){
6510 p = pattern_to_string(patgrp->news);
6511 new_patgrp->news = string_to_pattern(p);
6512 fs_give((void **)&p);
6513 new_patgrp->news->not = patgrp->news->not;
6516 if(patgrp->subj){
6517 p = pattern_to_string(patgrp->subj);
6518 new_patgrp->subj = string_to_pattern(p);
6519 fs_give((void **)&p);
6520 new_patgrp->subj->not = patgrp->subj->not;
6523 if(patgrp->alltext){
6524 p = pattern_to_string(patgrp->alltext);
6525 new_patgrp->alltext = string_to_pattern(p);
6526 fs_give((void **)&p);
6527 new_patgrp->alltext->not = patgrp->alltext->not;
6530 if(patgrp->bodytext){
6531 p = pattern_to_string(patgrp->bodytext);
6532 new_patgrp->bodytext = string_to_pattern(p);
6533 fs_give((void **)&p);
6534 new_patgrp->bodytext->not = patgrp->bodytext->not;
6537 if(patgrp->keyword){
6538 p = pattern_to_string(patgrp->keyword);
6539 new_patgrp->keyword = string_to_pattern(p);
6540 fs_give((void **)&p);
6541 new_patgrp->keyword->not = patgrp->keyword->not;
6544 if(patgrp->charsets){
6545 p = pattern_to_string(patgrp->charsets);
6546 new_patgrp->charsets = string_to_pattern(p);
6547 fs_give((void **)&p);
6548 new_patgrp->charsets->not = patgrp->charsets->not;
6551 if(patgrp->charsets_list)
6552 new_patgrp->charsets_list = copy_strlist(patgrp->charsets_list);
6554 if(patgrp->arbhdr){
6555 ARBHDR_S *aa, *a, *new_a;
6557 aa = NULL;
6558 for(a = patgrp->arbhdr; a; a = a->next){
6559 new_a = (ARBHDR_S *)fs_get(sizeof(*new_a));
6560 memset((void *)new_a, 0, sizeof(*new_a));
6562 if(a->field)
6563 new_a->field = cpystr(a->field);
6565 if(a->p){
6566 p = pattern_to_string(a->p);
6567 new_a->p = string_to_pattern(p);
6568 fs_give((void **)&p);
6569 new_a->p->not = a->p->not;
6572 new_a->isemptyval = a->isemptyval;
6574 if(aa){
6575 aa->next = new_a;
6576 aa = aa->next;
6578 else{
6579 new_patgrp->arbhdr = new_a;
6580 aa = new_patgrp->arbhdr;
6585 new_patgrp->fldr_type = patgrp->fldr_type;
6587 if(patgrp->folder){
6588 p = pattern_to_string(patgrp->folder);
6589 new_patgrp->folder = string_to_pattern(p);
6590 fs_give((void **)&p);
6593 new_patgrp->inabook = patgrp->inabook;
6595 if(patgrp->abooks){
6596 p = pattern_to_string(patgrp->abooks);
6597 new_patgrp->abooks = string_to_pattern(p);
6598 fs_give((void **)&p);
6601 new_patgrp->do_score = patgrp->do_score;
6602 if(patgrp->score){
6603 INTVL_S *intvl, *iv, *new_iv;
6605 intvl = NULL;
6606 for(iv = patgrp->score; iv; iv = iv->next){
6607 new_iv = (INTVL_S *) fs_get(sizeof(*new_iv));
6608 memset((void *) new_iv, 0, sizeof(*new_iv));
6610 new_iv->imin = iv->imin;
6611 new_iv->imax = iv->imax;
6613 if(intvl){
6614 intvl->next = new_iv;
6615 intvl = intvl->next;
6617 else{
6618 new_patgrp->score = new_iv;
6619 intvl = new_patgrp->score;
6624 new_patgrp->do_age = patgrp->do_age;
6625 if(patgrp->age){
6626 INTVL_S *intvl, *iv, *new_iv;
6628 intvl = NULL;
6629 for(iv = patgrp->age; iv; iv = iv->next){
6630 new_iv = (INTVL_S *) fs_get(sizeof(*new_iv));
6631 memset((void *) new_iv, 0, sizeof(*new_iv));
6633 new_iv->imin = iv->imin;
6634 new_iv->imax = iv->imax;
6636 if(intvl){
6637 intvl->next = new_iv;
6638 intvl = intvl->next;
6640 else{
6641 new_patgrp->age = new_iv;
6642 intvl = new_patgrp->age;
6647 new_patgrp->age_uses_sentdate = patgrp->age_uses_sentdate;
6649 new_patgrp->do_size = patgrp->do_size;
6650 if(patgrp->size){
6651 INTVL_S *intvl, *iv, *new_iv;
6653 intvl = NULL;
6654 for(iv = patgrp->size; iv; iv = iv->next){
6655 new_iv = (INTVL_S *) fs_get(sizeof(*new_iv));
6656 memset((void *) new_iv, 0, sizeof(*new_iv));
6658 new_iv->imin = iv->imin;
6659 new_iv->imax = iv->imax;
6661 if(intvl){
6662 intvl->next = new_iv;
6663 intvl = intvl->next;
6665 else{
6666 new_patgrp->size = new_iv;
6667 intvl = new_patgrp->size;
6672 new_patgrp->stat_new = patgrp->stat_new;
6673 new_patgrp->stat_rec = patgrp->stat_rec;
6674 new_patgrp->stat_del = patgrp->stat_del;
6675 new_patgrp->stat_imp = patgrp->stat_imp;
6676 new_patgrp->stat_ans = patgrp->stat_ans;
6678 new_patgrp->stat_8bitsubj = patgrp->stat_8bitsubj;
6679 new_patgrp->stat_bom = patgrp->stat_bom;
6680 new_patgrp->stat_boy = patgrp->stat_boy;
6682 new_patgrp->do_cat = patgrp->do_cat;
6683 if(patgrp->cat){
6684 INTVL_S *intvl, *iv, *new_iv;
6686 intvl = NULL;
6687 for(iv = patgrp->cat; iv; iv = iv->next){
6688 new_iv = (INTVL_S *) fs_get(sizeof(*new_iv));
6689 memset((void *) new_iv, 0, sizeof(*new_iv));
6691 new_iv->imin = iv->imin;
6692 new_iv->imax = iv->imax;
6694 if(intvl){
6695 intvl->next = new_iv;
6696 intvl = intvl->next;
6698 else{
6699 new_patgrp->cat = new_iv;
6700 intvl = new_patgrp->cat;
6705 if(patgrp->category_cmd)
6706 new_patgrp->category_cmd = copy_list_array(patgrp->category_cmd);
6709 return(new_patgrp);
6714 * Returns an allocated copy of the action.
6716 * Args action -- the source action
6718 * Returns a copy of action.
6720 ACTION_S *
6721 copy_action(ACTION_S *action)
6723 ACTION_S *newaction = NULL;
6724 char *p;
6726 if(action){
6727 newaction = (ACTION_S *)fs_get(sizeof(*newaction));
6728 memset((void *)newaction, 0, sizeof(*newaction));
6730 newaction->is_a_role = action->is_a_role;
6731 newaction->is_a_incol = action->is_a_incol;
6732 newaction->is_a_score = action->is_a_score;
6733 newaction->is_a_filter = action->is_a_filter;
6734 newaction->is_a_other = action->is_a_other;
6735 newaction->is_a_srch = action->is_a_srch;
6736 newaction->repl_type = action->repl_type;
6737 newaction->forw_type = action->forw_type;
6738 newaction->comp_type = action->comp_type;
6739 newaction->scoreval = action->scoreval;
6740 newaction->kill = action->kill;
6741 newaction->state_setting_bits = action->state_setting_bits;
6742 newaction->move_only_if_not_deleted = action->move_only_if_not_deleted;
6743 newaction->non_terminating = action->non_terminating;
6744 newaction->sort_is_set = action->sort_is_set;
6745 newaction->sortorder = action->sortorder;
6746 newaction->revsort = action->revsort;
6747 newaction->startup_rule = action->startup_rule;
6749 if(action->from)
6750 newaction->from = copyaddrlist(action->from);
6751 if(action->replyto)
6752 newaction->replyto = copyaddrlist(action->replyto);
6753 if(action->cstm)
6754 newaction->cstm = copy_list_array(action->cstm);
6755 if(action->smtp)
6756 newaction->smtp = copy_list_array(action->smtp);
6757 if(action->nntp)
6758 newaction->nntp = copy_list_array(action->nntp);
6759 if(action->fcc)
6760 newaction->fcc = cpystr(action->fcc);
6761 if(action->litsig)
6762 newaction->litsig = cpystr(action->litsig);
6763 if(action->sig)
6764 newaction->sig = cpystr(action->sig);
6765 if(action->template)
6766 newaction->template = cpystr(action->template);
6767 if(action->nick)
6768 newaction->nick = cpystr(action->nick);
6769 if(action->inherit_nick)
6770 newaction->inherit_nick = cpystr(action->inherit_nick);
6771 if(action->incol)
6772 newaction->incol = new_color_pair(action->incol->fg,
6773 action->incol->bg);
6774 if(action->scorevalhdrtok){
6775 newaction->scorevalhdrtok = new_hdrtok(action->scorevalhdrtok->hdrname);
6776 if(action->scorevalhdrtok && action->scorevalhdrtok->fieldseps){
6777 if(newaction->scorevalhdrtok->fieldseps)
6778 fs_give((void **) &newaction->scorevalhdrtok->fieldseps);
6780 newaction->scorevalhdrtok->fieldseps = cpystr(action->scorevalhdrtok->fieldseps);
6784 if(action->folder){
6785 p = pattern_to_string(action->folder);
6786 newaction->folder = string_to_pattern(p);
6787 fs_give((void **) &p);
6790 if(action->keyword_set){
6791 p = pattern_to_string(action->keyword_set);
6792 newaction->keyword_set = string_to_pattern(p);
6793 fs_give((void **) &p);
6796 if(action->keyword_clr){
6797 p = pattern_to_string(action->keyword_clr);
6798 newaction->keyword_clr = string_to_pattern(p);
6799 fs_give((void **) &p);
6802 if(action->index_format)
6803 newaction->index_format = cpystr(action->index_format);
6806 return(newaction);
6811 * Given a role, return an allocated role. If this role inherits from
6812 * another role, then do the correct inheriting so that the result is
6813 * the role we want to use. The inheriting that is done is just the set
6814 * of set- actions. This is for role stuff, no inheriting happens for scores
6815 * or for colors.
6817 * Args role -- The source role
6819 * Returns a role.
6821 ACTION_S *
6822 combine_inherited_role(ACTION_S *role)
6824 PAT_STATE pstate;
6825 PAT_S *pat;
6828 * Protect against loops in the role inheritance.
6830 if(role && role->is_a_role && nonempty_patterns(ROLE_DO_ROLES, &pstate))
6831 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
6832 if(pat->action){
6833 if(pat->action == role)
6834 pat->action->been_here_before = 1;
6835 else
6836 pat->action->been_here_before = 0;
6839 return(combine_inherited_role_guts(role));
6843 ACTION_S *
6844 combine_inherited_role_guts(ACTION_S *role)
6846 ACTION_S *newrole = NULL, *inherit_role = NULL;
6847 PAT_STATE pstate;
6849 if(role && role->is_a_role){
6850 newrole = (ACTION_S *)fs_get(sizeof(*newrole));
6851 memset((void *)newrole, 0, sizeof(*newrole));
6853 newrole->repl_type = role->repl_type;
6854 newrole->forw_type = role->forw_type;
6855 newrole->comp_type = role->comp_type;
6856 newrole->is_a_role = role->is_a_role;
6858 if(role->inherit_nick && role->inherit_nick[0] &&
6859 nonempty_patterns(ROLE_DO_ROLES, &pstate)){
6860 PAT_S *pat;
6862 /* find the inherit_nick pattern */
6863 for(pat = first_pattern(&pstate);
6864 pat;
6865 pat = next_pattern(&pstate)){
6866 if(pat->patgrp &&
6867 pat->patgrp->nick &&
6868 !strucmp(role->inherit_nick, pat->patgrp->nick)){
6869 /* found it, if it has a role, use it */
6870 if(!pat->action->been_here_before){
6871 pat->action->been_here_before = 1;
6872 inherit_role = pat->action;
6875 break;
6880 * inherit_role might inherit further from other roles.
6881 * In any case, we copy it so that we'll consistently have
6882 * an allocated copy.
6884 if(inherit_role){
6885 if(inherit_role->inherit_nick && inherit_role->inherit_nick[0])
6886 inherit_role = combine_inherited_role_guts(inherit_role);
6887 else
6888 inherit_role = copy_action(inherit_role);
6892 if(role->from)
6893 newrole->from = copyaddrlist(role->from);
6894 else if(inherit_role && inherit_role->from)
6895 newrole->from = copyaddrlist(inherit_role->from);
6897 if(role->replyto)
6898 newrole->replyto = copyaddrlist(role->replyto);
6899 else if(inherit_role && inherit_role->replyto)
6900 newrole->replyto = copyaddrlist(inherit_role->replyto);
6902 if(role->fcc)
6903 newrole->fcc = cpystr(role->fcc);
6904 else if(inherit_role && inherit_role->fcc)
6905 newrole->fcc = cpystr(inherit_role->fcc);
6907 if(role->litsig)
6908 newrole->litsig = cpystr(role->litsig);
6909 else if(inherit_role && inherit_role->litsig)
6910 newrole->litsig = cpystr(inherit_role->litsig);
6912 if(role->sig)
6913 newrole->sig = cpystr(role->sig);
6914 else if(inherit_role && inherit_role->sig)
6915 newrole->sig = cpystr(inherit_role->sig);
6917 if(role->template)
6918 newrole->template = cpystr(role->template);
6919 else if(inherit_role && inherit_role->template)
6920 newrole->template = cpystr(inherit_role->template);
6922 if(role->cstm)
6923 newrole->cstm = copy_list_array(role->cstm);
6924 else if(inherit_role && inherit_role->cstm)
6925 newrole->cstm = copy_list_array(inherit_role->cstm);
6927 if(role->smtp)
6928 newrole->smtp = copy_list_array(role->smtp);
6929 else if(inherit_role && inherit_role->smtp)
6930 newrole->smtp = copy_list_array(inherit_role->smtp);
6932 if(role->nntp)
6933 newrole->nntp = copy_list_array(role->nntp);
6934 else if(inherit_role && inherit_role->nntp)
6935 newrole->nntp = copy_list_array(inherit_role->nntp);
6937 if(role->nick)
6938 newrole->nick = cpystr(role->nick);
6940 if(inherit_role)
6941 free_action(&inherit_role);
6944 return(newrole);
6948 void
6949 mail_expunge_prefilter(MAILSTREAM *stream, int flags)
6951 int sfdo_state = 0, /* Some Filter Depends On or Sets State */
6952 sfdo_scores = 0, /* Some Filter Depends On Scores */
6953 ssdo_state = 0; /* Some Score Depends On State */
6955 if(!stream || !sp_flagged(stream, SP_LOCKED))
6956 return;
6959 * An Expunge causes a re-examination of the filters to
6960 * see if any state changes have caused new matches.
6963 sfdo_scores = (scores_are_used(SCOREUSE_GET) & SCOREUSE_FILTERS);
6964 if(sfdo_scores)
6965 ssdo_state = (scores_are_used(SCOREUSE_GET) & SCOREUSE_STATEDEP);
6967 if(!(sfdo_scores && ssdo_state))
6968 sfdo_state = some_filter_depends_on_active_state();
6971 if(sfdo_state || (sfdo_scores && ssdo_state)){
6972 if(sfdo_scores && ssdo_state)
6973 clear_folder_scores(stream);
6975 reprocess_filter_patterns(stream, sp_msgmap(stream),
6976 (flags & MI_CLOSING) |
6977 MI_REFILTERING | MI_STATECHGONLY);
6982 /*----------------------------------------------------------------------
6983 Dispatch messages matching FILTER patterns.
6985 Args:
6986 stream -- mail stream serving messages
6987 msgmap -- sequence to msgno mapping table
6988 recent -- number of recent messages to check (but really only its
6989 nonzeroness is used)
6991 When we're done, any filtered messages are filtered and the message
6992 mapping table has any filtered messages removed.
6993 ---*/
6994 void
6995 process_filter_patterns(MAILSTREAM *stream, MSGNO_S *msgmap, long int recent)
6997 long i, n, raw;
6998 imapuid_t uid;
6999 int we_cancel = 0, any_msgs = 0, any_to_filter = 0;
7000 int exbits, nt = 0, pending_actions = 0, for_debugging = 0;
7001 int cleared_index_cache = 0;
7002 long rflags = ROLE_DO_FILTER;
7003 char *nick = NULL;
7004 char busymsg[80];
7005 MSGNO_S *tmpmap = NULL;
7006 MESSAGECACHE *mc;
7007 PAT_S *pat, *nextpat = NULL;
7008 SEARCHPGM *pgm = NULL;
7009 SEARCHSET *srchset = NULL;
7010 long flags = (SE_NOPREFETCH|SE_FREE);
7011 PAT_STATE pstate;
7013 dprint((5, "process_filter_patterns(stream=%s, recent=%ld)\n",
7014 !stream ? "<null>" :
7015 sp_flagged(stream, SP_INBOX) ? "inbox" :
7016 stream->original_mailbox ? stream->original_mailbox :
7017 stream->mailbox ? stream->mailbox :
7018 "?",
7019 recent));
7021 if(!msgmap || !stream)
7022 return;
7024 if(!recent)
7025 sp_set_flags(stream, sp_flags(stream) | SP_FILTERED);
7027 while(stream && stream->nmsgs && nonempty_patterns(rflags, &pstate)){
7029 for_debugging++;
7030 pending_actions = 0;
7031 nextpat = NULL;
7033 uid = mail_uid(stream, stream->nmsgs);
7036 * Some of the search stuff won't work on old servers so we
7037 * get the data and search locally. Big performance hit.
7039 if(is_imap_stream(stream) && !modern_imap_stream(stream))
7040 flags |= SE_NOSERVER;
7043 * ignore all previously filtered messages
7044 * and, if requested, anything not a recent
7045 * arrival...
7047 * Here we're using spare6 (MN_STMP), meaning we'll only
7048 * search the ones with spare6 marked, new messages coming
7049 * in will not be considered. There used to be orig_nmsgs,
7050 * which kept track of this, but if a message gets expunged,
7051 * then a new message could be lower than orig_nmsgs.
7053 for(i = 1; i <= stream->nmsgs; i++)
7054 if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){
7055 if(exbits & MSG_EX_FILTERED){
7056 if((mc = mail_elt(stream, i)) != NULL)
7057 mc->spare6 = 0;
7059 else if(!recent || !(exbits & MSG_EX_TESTED)){
7060 if((mc = mail_elt(stream, i)) != NULL)
7061 mc->spare6 = 1;
7063 any_to_filter++;
7065 else if((mc = mail_elt(stream, i)) != NULL)
7066 mc->spare6 = 0;
7068 else{
7069 if((mc = mail_elt(stream, i)) != NULL)
7070 mc->spare6 = !recent;
7072 any_to_filter += !recent;
7075 if(!any_to_filter){
7076 dprint((5, "No messages need filtering\n"));
7079 /* Then start searching */
7080 for(pat = first_pattern(&pstate); any_to_filter && pat; pat = nextpat){
7081 nextpat = next_pattern(&pstate);
7082 dprint((5,
7083 "Trying filter \"%s\"\n",
7084 (pat->patgrp && pat->patgrp->nick)
7085 ? pat->patgrp->nick : "?"));
7086 if(pat->patgrp && !pat->patgrp->bogus
7087 && pat->action && !pat->action->bogus
7088 && !trivial_patgrp(pat->patgrp)
7089 && match_pattern_folder(pat->patgrp, stream)
7090 && !match_pattern_folder_specific(pat->action->folder,
7091 stream, FOR_FILTER)){
7094 * We could just keep track of spare6 accurately when
7095 * we change the msgno_exceptions flags, but...
7097 for(i = 1; i <= stream->nmsgs; i++){
7098 if((mc=mail_elt(stream, i)) && mc->spare6){
7099 if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){
7100 if(exbits & MSG_EX_FILTERED)
7101 mc->sequence = 0;
7102 else if(!recent || !(exbits & MSG_EX_TESTED))
7103 mc->sequence = 1;
7104 else
7105 mc->sequence = 0;
7107 else
7108 mc->sequence = !recent;
7110 else
7111 mc->sequence = 0;
7114 if(!(srchset = build_searchset(stream))){
7115 dprint((5, "Empty searchset\n"));
7116 continue; /* nothing to search, move on */
7119 #ifdef DEBUG
7120 {SEARCHSET *s;
7121 dprint((5, "searchset="));
7122 for(s = srchset; s; s = s->next){
7123 if(s->first == s->last || s->last == 0L){
7124 dprint((5, " %ld", s->first));
7126 else{
7127 dprint((5, " %ld-%ld", s->first, s->last));
7130 dprint((5, "\n"));
7132 #endif
7133 nick = (pat && pat->patgrp && pat->patgrp->nick
7134 && pat->patgrp->nick[0]) ? pat->patgrp->nick : NULL;
7135 snprintf(busymsg, sizeof(busymsg), _("Processing filter \"%s\""),
7136 nick ? nick : "?");
7139 * The strange last argument is so that the busy message
7140 * won't come out until after a second if the user sets
7141 * the feature to quell "filtering done". That's because
7142 * they are presumably interested in the filtering actions
7143 * themselves more than what is happening, so they'd
7144 * rather see the action messages instead of the processing
7145 * message. That's my theory anyway.
7147 if(F_OFF(F_QUELL_FILTER_MSGS, ps_global))
7148 any_msgs = we_cancel = busy_cue(busymsg, NULL,
7149 F_ON(F_QUELL_FILTER_DONE_MSG, ps_global)
7150 ? 1 : 0);
7152 if(pat->patgrp->stat_bom != PAT_STAT_EITHER){
7153 if(pat->patgrp->stat_bom == PAT_STAT_YES){
7154 if(!ps_global->beginning_of_month){
7155 dprint((5,
7156 "Filter %s wants beginning of month and it isn't bom\n",
7157 nick ? nick : "?"));
7158 continue;
7161 else if(pat->patgrp->stat_bom == PAT_STAT_NO){
7162 if(ps_global->beginning_of_month){
7163 dprint((5,
7164 "Filter %s does not want beginning of month and it is bom\n",
7165 nick ? nick : "?"));
7166 continue;
7171 if(pat->patgrp->stat_boy != PAT_STAT_EITHER){
7172 if(pat->patgrp->stat_boy == PAT_STAT_YES){
7173 if(!ps_global->beginning_of_year){
7174 dprint((5,
7175 "Filter %s wants beginning of year and it isn't boy\n",
7176 nick ? nick : "?"));
7177 continue;
7180 else if(pat->patgrp->stat_boy == PAT_STAT_NO){
7181 if(ps_global->beginning_of_year){
7182 dprint((5,
7183 "Filter %s does not want beginning of year and it is boy\n",
7184 nick ? nick : "?"));
7185 continue;
7190 pgm = match_pattern_srchpgm(pat->patgrp, stream, srchset);
7192 pine_mail_search_full(stream, "UTF-8", pgm, flags);
7194 /* check scores */
7195 if(scores_are_used(SCOREUSE_GET) & SCOREUSE_FILTERS &&
7196 pat->patgrp->do_score){
7197 SEARCHSET *s, *ss;
7200 * Build a searchset so we can get all the scores we
7201 * need and only the scores we need efficiently.
7204 for(i = 1; i <= stream->nmsgs; i++)
7205 if((mc = mail_elt(stream, i)) != NULL)
7206 mc->sequence = 0;
7208 for(s = srchset; s; s = s->next)
7209 for(i = s->first; i <= s->last; i++)
7210 if(i > 0L && stream && i <= stream->nmsgs
7211 && (mc=mail_elt(stream, i)) && mc->searched &&
7212 get_msg_score(stream, i) == SCORE_UNDEF)
7213 mc->sequence = 1;
7215 if((ss = build_searchset(stream)) != NULL){
7216 (void)calculate_some_scores(stream, ss, 0);
7217 mail_free_searchset(&ss);
7221 * Now check the patterns which have matched so far
7222 * to see if their score is in the score interval.
7224 for(s = srchset; s; s = s->next)
7225 for(i = s->first; i <= s->last; i++)
7226 if(i > 0L && stream && i <= stream->nmsgs
7227 && (mc=mail_elt(stream, i)) && mc->searched){
7228 long score;
7230 score = get_msg_score(stream, i);
7233 * If the score is outside all of the intervals,
7234 * turn off the searched bit.
7235 * So that means we check each interval and if
7236 * it is inside any interval we stop and leave
7237 * the bit set. If it is outside we keep checking.
7239 if(score != SCORE_UNDEF){
7240 INTVL_S *iv;
7242 for(iv = pat->patgrp->score; iv; iv = iv->next)
7243 if(score >= iv->imin && score <= iv->imax)
7244 break;
7246 if(!iv)
7247 mc->searched = NIL;
7252 /* check for 8bit subject match or not */
7253 if(pat->patgrp->stat_8bitsubj != PAT_STAT_EITHER)
7254 find_8bitsubj_in_messages(stream, srchset,
7255 pat->patgrp->stat_8bitsubj, 0);
7257 /* if there are still matches, check for charset matches */
7258 if(pat->patgrp->charsets)
7259 find_charsets_in_messages(stream, srchset, pat->patgrp, 0);
7261 if(pat->patgrp->inabook != IAB_EITHER)
7262 address_in_abook(stream, srchset, pat->patgrp->inabook, pat->patgrp->abooks);
7264 /* Still matches? Run the categorization command on each msg. */
7265 if(pith_opt_filter_pattern_cmd)
7266 (*pith_opt_filter_pattern_cmd)(pat->patgrp->category_cmd, srchset, stream, pat->patgrp->cat_lim, pat->patgrp->cat);
7268 if(we_cancel){
7269 cancel_busy_cue(-1);
7270 we_cancel = 0;
7273 nt = pat->action->non_terminating;
7274 pending_actions = MAX(nt, pending_actions);
7277 * Change some state bits.
7278 * This used to only happen if kill was not set, but
7279 * it can be useful to Delete a message even if killing.
7280 * That way, it will show up in another pine that isn't
7281 * running the same filter as Deleted, so the user won't
7282 * bother looking at it. Hubert 2004-11-16
7284 if(pat->action->state_setting_bits
7285 || pat->action->keyword_set
7286 || pat->action->keyword_clr){
7287 tmpmap = NULL;
7288 mn_init(&tmpmap, stream->nmsgs);
7290 for(i = 1L, n = 0L; i <= stream->nmsgs; i++)
7291 if((mc = mail_elt(stream, i)) && mc->searched
7292 && !(msgno_exceptions(stream, i, "0", &exbits, FALSE)
7293 && (exbits & MSG_EX_FILTERED))){
7294 if(!n++){
7295 mn_set_cur(tmpmap, i);
7297 else{
7298 mn_add_cur(tmpmap, i);
7302 if(n){
7303 long flagbits;
7304 char **keywords_to_set = NULL,
7305 **keywords_to_clr = NULL;
7306 PATTERN_S *pp;
7307 int cnt;
7309 flagbits = pat->action->state_setting_bits;
7311 if(pat->action->keyword_set){
7312 for(cnt = 0, pp = pat->action->keyword_set;
7313 pp; pp = pp->next)
7314 cnt++;
7316 keywords_to_set = (char **) fs_get((cnt+1) *
7317 sizeof(*keywords_to_set));
7318 memset(keywords_to_set, 0,
7319 (cnt+1) * sizeof(*keywords_to_set));
7320 for(cnt = 0, pp = pat->action->keyword_set;
7321 pp; pp = pp->next){
7322 char *q;
7324 q = nick_to_keyword(pp->substring);
7325 if(q && q[0])
7326 keywords_to_set[cnt++] = cpystr(q);
7329 flagbits |= F_KEYWORD;
7332 if(pat->action->keyword_clr){
7333 for(cnt = 0, pp = pat->action->keyword_clr;
7334 pp; pp = pp->next)
7335 cnt++;
7337 keywords_to_clr = (char **) fs_get((cnt+1) *
7338 sizeof(*keywords_to_clr));
7339 memset(keywords_to_clr, 0,
7340 (cnt+1) * sizeof(*keywords_to_clr));
7341 for(cnt = 0, pp = pat->action->keyword_clr;
7342 pp; pp = pp->next){
7343 char *q;
7345 q = nick_to_keyword(pp->substring);
7346 if(q && q[0])
7347 keywords_to_clr[cnt++] = cpystr(q);
7350 flagbits |= F_UNKEYWORD;
7353 set_some_flags(stream, tmpmap, flagbits,
7354 keywords_to_set, keywords_to_clr, 1,
7355 nick);
7358 mn_give(&tmpmap);
7362 * The two halves of the if-else are almost the same and
7363 * could probably be combined cleverly. The if clause
7364 * is simply setting the MSG_EX_FILTERED bit, and leaving
7365 * n set to zero. The msgno_exclude is not done in this case.
7366 * The else clause excludes each message (because it is
7367 * either filtered into nothing or moved to folder). The
7368 * exclude messes with the msgmap and that changes max_msgno,
7369 * so the loop control is a little tricky.
7371 if(!(pat->action->kill || pat->action->folder)){
7372 n = 0L;
7373 for(i = 1L; i <= mn_get_total(msgmap); i++)
7374 if((raw = mn_m2raw(msgmap, i)) > 0L
7375 && stream && raw <= stream->nmsgs
7376 && (mc = mail_elt(stream, raw)) && mc->searched){
7377 dprint((5,
7378 "FILTER matching \"%s\": msg %ld%s\n",
7379 nick ? nick : "unnamed",
7380 raw, nt ? " (dont stop)" : ""));
7381 if(msgno_exceptions(stream, raw, "0", &exbits, FALSE))
7382 exbits |= (nt ? MSG_EX_FILTONCE : MSG_EX_FILTERED);
7383 else
7384 exbits = (nt ? MSG_EX_FILTONCE : MSG_EX_FILTERED);
7387 * If this matched an earlier non-terminating rule
7388 * we've been keeping track of that so that we can
7389 * turn it into a permanent match at the end.
7390 * However, now we've matched another rule that is
7391 * terminating so we don't have to worry about it
7392 * anymore. Turn off the flag.
7394 if(!nt && exbits & MSG_EX_FILTONCE)
7395 exbits ^= MSG_EX_FILTONCE;
7397 exbits &= ~MSG_EX_STATECHG;
7399 msgno_exceptions(stream, raw, "0", &exbits, TRUE);
7402 else{
7403 for(i = 1L, n = 0L; i <= mn_get_total(msgmap); )
7404 if((raw = mn_m2raw(msgmap, i))
7405 && raw > 0L && stream && raw <= stream->nmsgs
7406 && (mc = mail_elt(stream, raw)) && mc->searched){
7407 dprint((5,
7408 "FILTER matching \"%s\": msg %ld %s%s\n",
7409 nick ? nick : "unnamed",
7410 raw, pat->action->folder ? "filed" : "killed",
7411 nt ? " (dont stop)" : ""));
7412 if(nt)
7413 i++;
7414 else{
7415 if(!cleared_index_cache
7416 && stream == ps_global->mail_stream){
7417 cleared_index_cache = 1;
7418 clear_index_cache(stream, 0);
7421 msgno_exclude(stream, msgmap, i, 1);
7423 * If this message is new, decrement
7424 * new_mail_count. Previously, the caller would
7425 * do this by counting MN_EXCLUDE before and after,
7426 * but the results weren't accurate in the case
7427 * where new messages arrived while filtering,
7428 * or the filtered message could have gotten
7429 * expunged.
7431 if(msgno_exceptions(stream, raw, "0", &exbits,
7432 FALSE)
7433 && (exbits & MSG_EX_RECENT)){
7434 long l, ll;
7436 l = sp_new_mail_count(stream);
7437 ll = sp_recent_since_visited(stream);
7438 dprint((5, "New message being filtered, decrement new_mail_count: %ld -> %ld\n", l, l-1L));
7439 if(l > 0L)
7440 sp_set_new_mail_count(stream, l-1L);
7441 if(ll > 0L)
7442 sp_set_recent_since_visited(stream, ll-1L);
7446 if(msgno_exceptions(stream, raw, "0", &exbits, FALSE))
7447 exbits |= (nt ? MSG_EX_FILTONCE : MSG_EX_FILTERED);
7448 else
7449 exbits = (nt ? MSG_EX_FILTONCE : MSG_EX_FILTERED);
7451 /* set pending exclusion for later */
7452 if(nt)
7453 exbits |= MSG_EX_PEND_EXLD;
7456 * If this matched an earlier non-terminating rule
7457 * we've been keeping track of that so that we can
7458 * turn it into a permanent match at the end.
7459 * However, now we've matched another rule that is
7460 * terminating so we don't have to worry about it
7461 * anymore. Turn off the flags.
7463 if(!nt && exbits & MSG_EX_FILTONCE){
7464 exbits ^= MSG_EX_FILTONCE;
7466 /* we've already excluded it, too */
7467 if(exbits & MSG_EX_PEND_EXLD)
7468 exbits ^= MSG_EX_PEND_EXLD;
7471 exbits &= ~MSG_EX_STATECHG;
7473 msgno_exceptions(stream, raw, "0", &exbits, TRUE);
7474 n++;
7476 else
7477 i++;
7480 if(n && pat->action->folder){
7481 PATTERN_S *p;
7482 int err = 0;
7484 tmpmap = NULL;
7485 mn_init(&tmpmap, stream->nmsgs);
7488 * For everything matching msg that hasn't
7489 * already been saved somewhere, do it...
7491 for(i = 1L, n = 0L; i <= stream->nmsgs; i++)
7492 if((mc = mail_elt(stream, i)) && mc->searched
7493 && !(msgno_exceptions(stream, i, "0", &exbits, FALSE)
7494 && (exbits & MSG_EX_FILED))){
7495 if(!n++){
7496 mn_set_cur(tmpmap, i);
7498 else{
7499 mn_add_cur(tmpmap, i);
7504 * Remove already deleted messages from the tmp
7505 * message map.
7506 * There is a bug with this. If a filter moves a
7507 * message to another folder _and_ sets the deleted
7508 * status, then the setting of the deleted status
7509 * will already have happened above in set_some_flags.
7510 * So if the move_only_if_not_deleted bit is set that
7511 * message will never be moved. A workaround for the
7512 * user is to not set the move-only-if-not-deleted
7513 * option.
7515 if(n && pat->action->move_only_if_not_deleted){
7516 char *seq;
7517 MSGNO_S *tmpmap2 = NULL;
7518 long nn = 0L;
7519 MESSAGECACHE *mc;
7521 mn_init(&tmpmap2, stream->nmsgs);
7524 * First, make sure elts are valid for all the
7525 * interesting messages.
7527 if((seq = invalid_elt_sequence(stream, tmpmap)) != NULL){
7528 pine_mail_fetch_flags(stream, seq, NIL);
7529 fs_give((void **) &seq);
7532 for(i = mn_first_cur(tmpmap); i > 0L;
7533 i = mn_next_cur(tmpmap)){
7534 mc = ((raw = mn_m2raw(tmpmap, i)) > 0L
7535 && stream && raw <= stream->nmsgs)
7536 ? mail_elt(stream, raw) : NULL;
7537 if(mc && !mc->deleted){
7538 if(!nn++){
7539 mn_set_cur(tmpmap2, i);
7541 else{
7542 mn_add_cur(tmpmap2, i);
7547 mn_give(&tmpmap);
7548 tmpmap = tmpmap2;
7549 n = nn;
7552 if(n){
7553 for(p = pat->action->folder; p; p = p->next){
7554 int dval;
7555 int flags_for_save;
7556 char *processed_name;
7558 /* does this filter set delete bit? ... */
7559 convert_statebits_to_vals(pat->action->state_setting_bits, &dval, NULL, NULL, NULL);
7560 /* ... if so, tell save not to fix it before copy */
7561 flags_for_save = SV_FOR_FILT | SV_INBOXWOCNTXT |
7562 (nt ? 0 : SV_DELETE) |
7563 ((dval != ACT_STAT_SET) ? SV_FIX_DELS : 0);
7564 processed_name = detoken_src(p->substring,
7565 FOR_FILT, NULL,
7566 NULL, NULL, NULL);
7567 if(move_filtered_msgs(stream, tmpmap,
7568 (processed_name && *processed_name)
7569 ? processed_name : p->substring,
7570 flags_for_save, nick)){
7572 * If we filtered into the current
7573 * folder, chuck a ping down the
7574 * stream so the user can notice it
7575 * before the next new mail check...
7577 if(ps_global->mail_stream
7578 && ps_global->mail_stream != stream
7579 && match_pattern_folder_specific(
7580 pat->action->folder,
7581 ps_global->mail_stream,
7582 FOR_FILTER)){
7583 (void) pine_mail_ping(ps_global->mail_stream);
7586 else{
7587 err = 1;
7588 break;
7591 if(processed_name)
7592 fs_give((void **) &processed_name);
7595 if(!err)
7596 for(n = mn_first_cur(tmpmap);
7597 n > 0L;
7598 n = mn_next_cur(tmpmap)){
7600 if(msgno_exceptions(stream, mn_m2raw(tmpmap, n),
7601 "0", &exbits, FALSE))
7602 exbits |= (nt ? MSG_EX_FILEONCE : MSG_EX_FILED);
7603 else
7604 exbits = (nt ? MSG_EX_FILEONCE : MSG_EX_FILED);
7606 exbits &= ~MSG_EX_STATECHG;
7608 msgno_exceptions(stream, mn_m2raw(tmpmap, n),
7609 "0", &exbits, TRUE);
7613 mn_give(&tmpmap);
7616 mail_free_searchset(&srchset);
7620 * If this is the last rule,
7621 * we make sure we delete messages that we delayed deleting
7622 * in the save. We delayed so that the deletion wouldn't have
7623 * an effect on later rules. We convert any temporary
7624 * FILED (FILEONCE) and FILTERED (FILTONCE) flags
7625 * (which were set by an earlier non-terminating rule)
7626 * to permanent. We also exclude some messages from the view.
7628 if(pending_actions && !nextpat){
7630 pending_actions = 0;
7631 tmpmap = NULL;
7632 mn_init(&tmpmap, stream->nmsgs);
7634 for(i = 1L, n = 0L; i <= mn_get_total(msgmap); i++){
7636 raw = mn_m2raw(msgmap, i);
7637 if(msgno_exceptions(stream, raw, "0", &exbits, FALSE)){
7638 if(exbits & MSG_EX_FILEONCE){
7639 if(!n++){
7640 mn_set_cur(tmpmap, raw);
7642 else{
7643 mn_add_cur(tmpmap, raw);
7649 if(n)
7650 set_some_flags(stream, tmpmap, F_DEL, NULL, NULL, 0, NULL);
7652 mn_give(&tmpmap);
7654 for(i = 1L; i <= mn_get_total(msgmap); i++){
7655 raw = mn_m2raw(msgmap, i);
7656 if(msgno_exceptions(stream, raw, "0", &exbits, FALSE)){
7657 if(exbits & MSG_EX_PEND_EXLD){
7658 if(!cleared_index_cache
7659 && stream == ps_global->mail_stream){
7660 cleared_index_cache = 1;
7661 clear_index_cache(stream, 0);
7664 msgno_exclude(stream, msgmap, i, 1);
7665 if(msgno_exceptions(stream, raw, "0",
7666 &exbits, FALSE)
7667 && (exbits & MSG_EX_RECENT)){
7668 long l, ll;
7671 * If this message is new, decrement
7672 * new_mail_count. See the above
7673 * call to msgno_exclude.
7675 l = sp_new_mail_count(stream);
7676 ll = sp_recent_since_visited(stream);
7677 dprint((5, "New message being filtered. Decrement new_mail_count: %ld -> %ld\n", l, l-1L));
7678 if(l > 0L)
7679 sp_set_new_mail_count(stream, l - 1L);
7680 if(ll > 0L)
7681 sp_set_recent_since_visited(stream, ll - 1L);
7684 i--; /* to compensate for loop's i++ */
7687 /* get rid of temporary flags */
7688 if(exbits & (MSG_EX_FILTONCE | MSG_EX_FILEONCE |
7689 MSG_EX_PEND_EXLD)){
7690 if(exbits & MSG_EX_FILTONCE){
7691 /* convert to permanent */
7692 exbits ^= MSG_EX_FILTONCE;
7693 exbits |= MSG_EX_FILTERED;
7696 /* convert to permanent */
7697 if(exbits & MSG_EX_FILEONCE){
7698 exbits ^= MSG_EX_FILEONCE;
7699 exbits |= MSG_EX_FILED;
7702 if(exbits & MSG_EX_PEND_EXLD)
7703 exbits ^= MSG_EX_PEND_EXLD;
7705 exbits &= ~MSG_EX_STATECHG;
7707 msgno_exceptions(stream, raw, "0", &exbits,TRUE);
7714 /* New mail arrival means start over */
7715 if(stream->nmsgs && mail_uid(stream, stream->nmsgs) == uid)
7716 break;
7717 /* else, go again */
7719 recent = 1; /* only check recent ones now */
7722 if(!recent){
7723 /* clear status change flags */
7724 for(i = 1; i <= stream->nmsgs; i++){
7725 if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){
7726 if(exbits & MSG_EX_STATECHG){
7727 exbits &= ~MSG_EX_STATECHG;
7728 msgno_exceptions(stream, i, "0", &exbits, TRUE);
7734 /* clear any private "recent" flags and add TESTED flag */
7735 for(i = 1; i <= stream->nmsgs; i++){
7736 if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){
7737 if(exbits & MSG_EX_RECENT
7738 || !(exbits & MSG_EX_TESTED)
7739 || (!recent && exbits & MSG_EX_STATECHG)){
7740 exbits &= ~MSG_EX_RECENT;
7741 exbits |= MSG_EX_TESTED;
7742 if(!recent)
7743 exbits &= ~MSG_EX_STATECHG;
7745 msgno_exceptions(stream, i, "0", &exbits, TRUE);
7748 else{
7749 exbits = MSG_EX_TESTED;
7750 msgno_exceptions(stream, i, "0", &exbits, TRUE);
7753 /* clear any stmp flags just in case */
7754 if((mc = mail_elt(stream, i)) != NULL)
7755 mc->spare6 = 0;
7758 msgmap->flagged_stmp = 0L;
7760 if(any_msgs && F_OFF(F_QUELL_FILTER_MSGS, ps_global)
7761 && F_OFF(F_QUELL_FILTER_DONE_MSG, ps_global)){
7762 q_status_message(SM_ORDER, 0, 1, _("filtering done"));
7763 display_message('x');
7769 * Re-check the filters for matches because a change of message state may
7770 * have changed the results.
7772 void
7773 reprocess_filter_patterns(MAILSTREAM *stream, MSGNO_S *msgmap, int flags)
7775 if(stream){
7776 long i;
7777 int exbits;
7779 if(msgno_include(stream, msgmap, flags)
7780 && stream == ps_global->mail_stream
7781 && !(flags & MI_CLOSING)){
7782 clear_index_cache(stream, 0);
7783 refresh_sort(stream, msgmap, SRT_NON);
7784 ps_global->mangled_header = 1;
7788 * Passing 1 in the last argument causes it to only look at the
7789 * messages we included above, which should be only the ones we
7790 * need to look at.
7792 process_filter_patterns(stream, msgmap,
7793 (flags & MI_STATECHGONLY) ? 1L : 0);
7795 /* clear status change flags */
7796 for(i = 1; i <= stream->nmsgs; i++){
7797 if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){
7798 if(exbits & MSG_EX_STATECHG){
7799 exbits &= ~MSG_EX_STATECHG;
7800 msgno_exceptions(stream, i, "0", &exbits, TRUE);
7809 * When killing or filtering we don't want to match by mistake. So if
7810 * a pattern has nothing set except the Current Folder Type (which is always
7811 * set to something) we'll consider it to be trivial and not a match.
7812 * match_pattern uses this to determine if there is a match, where it is
7813 * just triggered on the Current Folder Type.
7816 trivial_patgrp(PATGRP_S *patgrp)
7818 int ret = 1;
7820 if(patgrp){
7821 if(patgrp->subj || patgrp->cc || patgrp->from || patgrp->to ||
7822 patgrp->sender || patgrp->news || patgrp->recip || patgrp->partic ||
7823 patgrp->alltext || patgrp->bodytext)
7824 ret = 0;
7826 if(ret && patgrp->do_age)
7827 ret = 0;
7829 if(ret && patgrp->do_size)
7830 ret = 0;
7832 if(ret && patgrp->do_score)
7833 ret = 0;
7835 if(ret && patgrp->category_cmd && patgrp->category_cmd[0])
7836 ret = 0;
7838 if(ret && patgrp_depends_on_state(patgrp))
7839 ret = 0;
7841 if(ret && patgrp->stat_8bitsubj != PAT_STAT_EITHER)
7842 ret = 0;
7844 if(ret && patgrp->charsets)
7845 ret = 0;
7847 if(ret && patgrp->stat_bom != PAT_STAT_EITHER)
7848 ret = 0;
7850 if(ret && patgrp->stat_boy != PAT_STAT_EITHER)
7851 ret = 0;
7853 if(ret && patgrp->inabook != IAB_EITHER)
7854 ret = 0;
7856 if(ret && patgrp->arbhdr){
7857 ARBHDR_S *a;
7859 for(a = patgrp->arbhdr; a && ret; a = a->next)
7860 if(a->field && a->field[0] && a->p)
7861 ret = 0;
7865 return(ret);
7870 some_filter_depends_on_active_state(void)
7872 long rflags = ROLE_DO_FILTER;
7873 PAT_S *pat;
7874 PAT_STATE pstate;
7875 int ret = 0;
7877 if(nonempty_patterns(rflags, &pstate)){
7879 for(pat = first_pattern(&pstate);
7880 pat && !ret;
7881 pat = next_pattern(&pstate))
7882 if(patgrp_depends_on_active_state(pat->patgrp))
7883 ret++;
7886 return(ret);
7890 /*----------------------------------------------------------------------
7891 Move all messages with sequence bit lit to dstfldr
7893 Args: stream -- stream to use
7894 msgmap -- map of messages to be moved
7895 dstfldr -- folder to receive moved messages
7896 flags_for_save
7898 Returns: nonzero on success or on readonly stream
7899 ----*/
7901 move_filtered_msgs(MAILSTREAM *stream, MSGNO_S *msgmap, char *dstfldr,
7902 int flags_for_save, char *nick)
7904 long n;
7905 int we_cancel = 0, width;
7906 CONTEXT_S *save_context = NULL;
7907 char buf[MAX_SCREEN_COLS+1], sbuf[MAX_SCREEN_COLS+1];
7908 char *save_ref = NULL;
7909 #define FILTMSG_MAX 30
7911 if(!stream)
7912 return 0;
7914 if(READONLY_FOLDER(stream)){
7915 dprint((1,
7916 "Can't delete messages in readonly folder \"%s\"\n",
7917 STREAMNAME(stream)));
7918 q_status_message1(SM_ORDER, 1, 3,
7919 _("Can't delete messages in readonly folder \"%s\""),
7920 STREAMNAME(stream));
7921 return 1;
7924 buf[0] = '\0';
7926 width = MAX(10, ps_global->ttyo ? ps_global->ttyo->screen_cols : 80);
7927 snprintf(buf, sizeof(buf), "%.30s%.2sMoving %.10s filtered message%.2s to \"\"",
7928 nick ? nick : "", nick ? ": " : "",
7929 comatose(mn_total_cur(msgmap)), plural(mn_total_cur(msgmap)));
7930 /* 2 is for brackets, 5 is for " DONE" in busy alarm */
7931 width -= (strlen(buf) + 2 + 5);
7932 snprintf(buf, sizeof(buf), "%.30s%.2sMoving %.10s filtered message%.2s to \"%s\"",
7933 nick ? nick : "", nick ? ": " : "",
7934 comatose(mn_total_cur(msgmap)), plural(mn_total_cur(msgmap)),
7935 short_str(dstfldr, sbuf, sizeof(sbuf), width, FrontDots));
7937 dprint((5, "%s\n", buf));
7939 if(F_OFF(F_QUELL_FILTER_MSGS, ps_global))
7940 we_cancel = busy_cue(buf, NULL, 0);
7942 if(!is_absolute_path(dstfldr)
7943 && !(save_context = default_save_context(ps_global->context_list)))
7944 save_context = ps_global->context_list;
7947 * Because this save is happening independent of where the user is
7948 * in the folder hierarchy and has nothing to do with that, we want
7949 * to ignore the reference field built into the context. Zero it out
7950 * temporarily here so it won't affect the results of context_apply
7951 * in save.
7953 * This might be a problem elsewhere, as well. The same thing as this
7954 * is also done in match_pattern_folder_specific, which is also only
7955 * called from within process_filter_patterns. But there could be
7956 * others. We could have a separate function, something like
7957 * copy_default_save_context(), that automatically zeroes out the
7958 * reference field in the copy. However, some of the uses of
7959 * default_save_context() require that a pointer into the actual
7960 * context list is returned, so this would have to be done carefully.
7961 * Besides, we don't know of any other problems so we'll just change
7962 * these known cases for now.
7964 if(save_context && save_context->dir){
7965 save_ref = save_context->dir->ref;
7966 save_context->dir->ref = NULL;
7969 n = save(ps_global, stream, save_context, dstfldr, msgmap, flags_for_save);
7971 if(save_ref)
7972 save_context->dir->ref = save_ref;
7974 if(n != mn_total_cur(msgmap)){
7975 int exbits;
7976 long x;
7978 buf[0] = '\0';
7980 /* Clear "filtered" flags for failed messages */
7981 for(x = mn_first_cur(msgmap); x > 0L; x = mn_next_cur(msgmap))
7982 if(n-- <= 0 && msgno_exceptions(stream, mn_m2raw(msgmap, x),
7983 "0", &exbits, FALSE)){
7984 exbits &= ~(MSG_EX_FILTONCE | MSG_EX_FILEONCE |
7985 MSG_EX_FILTERED | MSG_EX_FILED);
7986 msgno_exceptions(stream, mn_m2raw(msgmap, x),
7987 "0", &exbits, TRUE);
7990 /* then re-incorporate them into folder they belong */
7991 (void) msgno_include(stream, sp_msgmap(stream), MI_NONE);
7992 clear_index_cache(stream, 0);
7993 refresh_sort(stream, sp_msgmap(stream), SRT_NON);
7994 ps_global->mangled_header = 1;
7996 else{
7997 snprintf(buf, sizeof(buf), _("Filtered all %s message to \"%s\""),
7998 comatose(n), dstfldr);
7999 dprint((5, "%s\n", buf));
8002 if(we_cancel)
8003 cancel_busy_cue(buf[0] ? 0 : -1);
8005 return(buf[0] != '\0');
8009 /*----------------------------------------------------------------------
8010 Move all messages with sequence bit lit to dstfldr
8012 Args: stream -- stream to use
8013 msgmap -- which messages to set
8014 flagbits -- which flags to set or clear
8015 kw_on -- keywords to set
8016 kw_off -- keywords to clear
8017 verbose -- 1 => busy alarm after 1 second
8018 2 => forced busy alarm
8019 ----*/
8020 void
8021 set_some_flags(MAILSTREAM *stream, MSGNO_S *msgmap, long int flagbits,
8022 char **kw_on, char **kw_off, int verbose, char *nick)
8024 long count = 0L, flipped_flags;
8025 int we_cancel = 0;
8026 char buf[150], *seq;
8028 if(!stream)
8029 return;
8031 if(READONLY_FOLDER(stream)){
8032 dprint((1, "Can't set flags in readonly folder \"%s\"\n",
8033 STREAMNAME(stream)));
8034 q_status_message1(SM_ORDER, 1, 3,
8035 _("Can't set flags in readonly folder \"%s\""),
8036 STREAMNAME(stream));
8037 return;
8040 /* use this to determine if anything needs to be done */
8041 flipped_flags = ((flagbits & F_ANS) ? F_UNANS : 0) |
8042 ((flagbits & F_UNANS) ? F_ANS : 0) |
8043 ((flagbits & F_FLAG) ? F_UNFLAG : 0) |
8044 ((flagbits & F_UNFLAG) ? F_FLAG : 0) |
8045 ((flagbits & F_DEL) ? F_UNDEL : 0) |
8046 ((flagbits & F_UNDEL) ? F_DEL : 0) |
8047 ((flagbits & F_SEEN) ? F_UNSEEN : 0) |
8048 ((flagbits & F_UNSEEN) ? F_SEEN : 0) |
8049 ((flagbits & F_KEYWORD) ? F_UNKEYWORD : 0) |
8050 ((flagbits & F_UNKEYWORD) ? F_KEYWORD : 0);
8051 if((seq = currentf_sequence(stream, msgmap, flipped_flags, &count, 0,
8052 kw_off, kw_on)) != NULL){
8053 char *sets = NULL, *clears = NULL;
8054 char *ps, *pc, **t;
8055 size_t clen, slen;
8057 /* allocate enough space for mail_flags arguments */
8058 for(slen=100, t = kw_on; t && *t; t++)
8059 slen += (strlen(*t) + 1);
8061 sets = (char *) fs_get(slen * sizeof(*sets));
8063 for(clen=100, t = kw_off; t && *t; t++)
8064 clen += (strlen(*t) + 1);
8066 clears = (char *) fs_get(clen * sizeof(*clears));
8068 sets[0] = clears[0] = '\0';
8069 ps = sets;
8070 pc = clears;
8072 snprintf(buf, sizeof(buf), "%.30s%.2sSetting flags in %.10s message%.10s",
8073 nick ? nick : "", nick ? ": " : "",
8074 comatose(count), plural(count));
8076 if(F_OFF(F_QUELL_FILTER_MSGS, ps_global))
8077 we_cancel = busy_cue(buf, NULL, verbose ? 0 : 1);
8080 * What's going on here? If we want to set more than one flag
8081 * we can do it with a single roundtrip by combining the arguments
8082 * into a single call and separating them with spaces.
8084 if(flagbits & F_ANS)
8085 sstrncpy(&ps, "\\ANSWERED", slen-(ps-sets));
8086 if(flagbits & F_FLAG){
8087 if(ps > sets)
8088 sstrncpy(&ps, " ", slen-(ps-sets));
8090 sstrncpy(&ps, "\\FLAGGED", slen-(ps-sets));
8092 if(flagbits & F_DEL){
8093 if(ps > sets)
8094 sstrncpy(&ps, " ", slen-(ps-sets));
8096 sstrncpy(&ps, "\\DELETED", slen-(ps-sets));
8098 if(flagbits & F_SEEN){
8099 if(ps > sets)
8100 sstrncpy(&ps, " ", slen-(ps-sets));
8102 sstrncpy(&ps, "\\SEEN", slen-(ps-sets));
8104 if(flagbits & F_KEYWORD){
8105 for(t = kw_on; t && *t; t++){
8106 int i;
8109 * We may be able to tell that this will fail before
8110 * we actually try it.
8112 if(stream->kwd_create ||
8113 (((i=user_flag_index(stream, *t)) >= 0) && i < NUSERFLAGS)){
8114 if(ps > sets)
8115 sstrncpy(&ps, " ", slen-(ps-sets));
8117 sstrncpy(&ps, *t, slen-(ps-sets));
8119 else{
8120 int some_defined = 0;
8121 static int msg_delivered = 0;
8123 some_defined = some_user_flags_defined(stream);
8125 if(msg_delivered++ < 2){
8126 char b[200], c[200], *p;
8127 int w;
8129 if(some_defined){
8130 snprintf(b, sizeof(b), "Can't set \"%.30s\". No more keywords in ", keyword_to_nick(*t));
8131 w = MIN((ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - strlen(b) - 1 - 2, sizeof(c)-1);
8132 p = short_str(STREAMNAME(stream), c, sizeof(c), w, FrontDots);
8133 q_status_message2(SM_ORDER, 3, 3, "%s%s!", b, p);
8135 else{
8136 snprintf(b, sizeof(b), "Can't set \"%.30s\". Can't add keywords in ", keyword_to_nick(*t));
8137 w = MIN((ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - strlen(b) - 1 - 2, sizeof(c)-1);
8138 p = short_str(STREAMNAME(stream), c, sizeof(c), w, FrontDots);
8139 q_status_message2(SM_ORDER, 3, 3, "%s%s!", b, p);
8143 if(some_defined){
8144 dprint((1, "Can't set keyword \"%s\". No more keywords allowed in %s\n", *t, stream->mailbox ? stream->mailbox : "target folder"));
8146 else{
8147 dprint((1, "Can't set keyword \"%s\". Can't add keywords in %s\n", *t, stream->mailbox ? stream->mailbox : "target folder"));
8153 /* need a separate call for the clears */
8154 if(flagbits & F_UNANS)
8155 sstrncpy(&pc, "\\ANSWERED", clen-(pc-clears));
8156 if(flagbits & F_UNFLAG){
8157 if(pc > clears)
8158 sstrncpy(&pc, " ", clen-(pc-clears));
8160 sstrncpy(&pc, "\\FLAGGED", clen-(pc-clears));
8162 if(flagbits & F_UNDEL){
8163 if(pc > clears)
8164 sstrncpy(&pc, " ", clen-(pc-clears));
8166 sstrncpy(&pc, "\\DELETED", clen-(pc-clears));
8168 if(flagbits & F_UNSEEN){
8169 if(pc > clears)
8170 sstrncpy(&pc, " ", clen-(pc-clears));
8172 sstrncpy(&pc, "\\SEEN", clen-(pc-clears));
8174 if(flagbits & F_UNKEYWORD){
8175 for(t = kw_off; t && *t; t++){
8176 if(pc > clears)
8177 sstrncpy(&pc, " ", clen-(pc-clears));
8179 sstrncpy(&pc, *t, clen-(pc-clears));
8184 if(sets[0])
8185 mail_flag(stream, seq, sets, ST_SET);
8187 if(clears[0])
8188 mail_flag(stream, seq, clears, 0L);
8190 fs_give((void **) &sets);
8191 fs_give((void **) &clears);
8192 fs_give((void **) &seq);
8194 if(we_cancel)
8195 cancel_busy_cue(buf[0] ? 0 : -1);
8201 * Delete messages which are marked FILTERED and excluded.
8202 * Messages which are FILTERED but not excluded are those that have had
8203 * their state set by a filter pattern, but are to remain in the same
8204 * folder.
8206 void
8207 delete_filtered_msgs(MAILSTREAM *stream)
8209 int exbits;
8210 long i;
8211 char *seq;
8212 MESSAGECACHE *mc;
8214 for(i = 1L; i <= stream->nmsgs; i++)
8215 if(msgno_exceptions(stream, i, "0", &exbits, FALSE)
8216 && (exbits & MSG_EX_FILTERED)
8217 && get_lflag(stream, NULL, i, MN_EXLD)){
8218 if((mc = mail_elt(stream, i)) != NULL)
8219 mc->sequence = 1;
8221 else if((mc = mail_elt(stream, i)) != NULL)
8222 mc->sequence = 0;
8224 if((seq = build_sequence(stream, NULL, NULL)) != NULL){
8225 mail_flag(stream, seq, "\\DELETED", ST_SET | ST_SILENT);
8226 fs_give((void **) &seq);