1 
2 static char rcsid[] = "@(#)$Id: pattern.c,v 1.7 1998/02/11 22:02:15 wfp5p Exp $";
3 
4 /*******************************************************************************
5  *  The Elm Mail System  -  $Revision: 1.7 $   $State: Exp $
6  *
7  *                      Copyright (c) 1988-1995 USENET Community Trust
8  *			Copyright (c) 1986,1987 Dave Taylor
9  *******************************************************************************
10  * Bug reports, patches, comments, suggestions should be sent to:
11  *
12  *      Bill Pemberton, Elm Coordinator
13  *      flash@virginia.edu
14  *
15  *******************************************************************************
16  * $Log: pattern.c,v $
17  * Revision 1.7  1998/02/11  22:02:15  wfp5p
18  * Beta 2
19  *
20  * Revision 1.6  1996/05/09  15:51:24  wfp5p
21  * Alpha 10
22  *
23  * Revision 1.5  1996/03/14  17:29:45  wfp5p
24  * Alpha 9
25  *
26  * Revision 1.4  1995/09/29  17:42:21  wfp5p
27  * Alpha 8 (Chip's big changes)
28  *
29  * Revision 1.3  1995/09/11  15:19:23  wfp5p
30  * Alpha 7
31  *
32  * Revision 1.2  1995/06/12  20:33:35  wfp5p
33  * Alpha 2 clean up
34  *
35  * Revision 1.1.1.1  1995/04/19  20:38:37  wfp5p
36  * Initial import of elm 2.4 PL0 as base for elm 2.5.
37  *
38  ******************************************************************************/
39 
40 /**    General pattern matching for the ELM mailer.
41 
42 **/
43 
44 
45 #include "elm_defs.h"
46 #include "elm_globals.h"
47 #include "s_elm.h"
48 #include <assert.h>
49 
50 /* local procedures */
51 static void ask_clear_existing_tags P_((void));
52 static int from_matches();
53 static int subject_matches();
54 static int name_matches();
55 static int alias_matches();
56 static int comment_matches();
57 static int address_matches();
58 static int match_in_message();
59 
60 int
meta_match(function)61 meta_match(function)
62 int function;
63 {
64     /** Perform specific function based on whether an entered string
65 	matches either the From or Subject lines..
66 	Return TRUE if the current message was matched, else FALSE.
67     **/
68 
69     int i, count, curtag;
70     char *word_Action, *word_Actioned, *word_actioned;
71     static char pat[SLEN];
72 
73     switch (function) {
74     case MATCH_TAG:
75 	word_Action = catgets(elm_msg_cat, ElmSet, ElmTag,
76 		    "Tag");
77 	word_Actioned = catgets(elm_msg_cat, ElmSet, ElmCapTagged,
78 		    "Tagged");
79 	word_actioned = catgets(elm_msg_cat, ElmSet, ElmTagged,
80 		    "tagged");
81 	break;
82     case MATCH_DELETE:
83 	word_Action = catgets(elm_msg_cat, ElmSet, ElmDelete,
84 		    "Delete");
85 	word_Actioned = catgets(elm_msg_cat, ElmSet, ElmCapMarkDelete,
86 		    "Marked for deletion");
87 	word_actioned = catgets(elm_msg_cat, ElmSet, ElmMarkDelete,
88 		    "marked for deletion");
89 	break;
90     case MATCH_UNDELETE:
91 	word_Action = catgets(elm_msg_cat, ElmSet, ElmUndelete,
92 		    "Undelete");
93 	word_Actioned = catgets(elm_msg_cat, ElmSet, ElmCapUndeleted,
94 		    "Undeleted");
95 	word_actioned = catgets(elm_msg_cat, ElmSet, ElmUndeleted,
96 		    "undeleted");
97 	break;
98     default:
99 	assert(function == MATCH_TAG || function == MATCH_DELETE
100 	    || function == MATCH_UNDELETE);
101 	return FALSE;
102     }
103 
104     PutLine2(LINES-3, strlen(nls_Prompt), catgets(elm_msg_cat, ElmSet,
105 	    ElmMessagesMatchPattern, "%s %s that match pattern..."),
106 	    word_Action, nls_items);
107 
108     /* clear any existing tags? */
109     if (function == MATCH_TAG)
110 	ask_clear_existing_tags();
111 
112     PutLine0(LINES-2, 0, catgets(elm_msg_cat, ElmSet, ElmEnterPattern,
113 	    "Enter pattern: "));
114     if (enter_string(pat, sizeof(pat), -1, -1, ESTR_REPLACE) < 0
115 		|| pat[0] == '\0') {
116       ClearLine(LINES-3);
117       ClearLine(LINES-2);
118       return FALSE;
119     }
120     strcpy(pat, shift_lower(pat));
121 
122     count = 0;
123     curtag = FALSE;
124     if (inalias) {
125 
126 	for (i = 0; i < num_aliases; i++) {
127 
128 	    if (!name_matches(i, pat) && !alias_matches(i, pat))
129 		continue;
130 	    if (selected && !(aliases[i]->status & VISIBLE))
131 		continue;
132 
133 	    switch (function) {
134 	    case MATCH_DELETE:
135 		if (aliases[i]->type & SYSTEM)
136 		    continue;		/* don't delete these!!! */
137 		aliases[i]->status |= DELETED;
138 		break;
139 	    case MATCH_UNDELETE:
140 		aliases[i]->status &= ~DELETED;
141 		break;
142 	    case MATCH_TAG:
143 		aliases[i]->status |= TAGGED;
144 		break;
145 	    }
146 
147 	    show_new_status(i);
148 	    if (curr_alias == i+1)
149 		curtag = TRUE;
150 	    count++;
151 
152 	}
153 
154 
155     } else {
156 
157 	for (i = 0; i < curr_folder.num_mssgs; i++) {
158 
159 	    if (!from_matches(i, pat) && !subject_matches(i, pat))
160 		continue;
161 	    if (selected && !(curr_folder.headers[i]->status & VISIBLE))
162 		continue;
163 
164 	    switch (function) {
165 	    case MATCH_DELETE:
166 		curr_folder.headers[i]->status |= DELETED;
167 		break;
168 	    case MATCH_UNDELETE:
169 		curr_folder.headers[i]->status &= ~DELETED;
170 		break;
171 	    case MATCH_TAG:
172 		curr_folder.headers[i]->status |= TAGGED;
173 		break;
174 	    }
175 
176 	    show_new_status(i);
177 	    if (curr_folder.curr_mssg == i+1)
178 		curtag++;
179 	    count++;
180 
181 	}
182 
183     }
184 
185     MoveCursor(LINES-3, 0);
186     CleartoEOS();
187     switch (count) {
188     case 0:
189 	error2(catgets(elm_msg_cat, ElmSet, ElmNoMatchesNoTags,
190 		    "No matches. No %s %s."), nls_items, word_actioned);
191 	break;
192     case 1:
193 	error2(catgets(elm_msg_cat, ElmSet, ElmTaggedMessage,
194 		    "%s 1 %s."),  word_Actioned, nls_item);
195 	break;
196     default:
197 	error3(catgets(elm_msg_cat, ElmSet, ElmTaggedMessages,
198 		    "%s %d %s."), word_Actioned, count, nls_items);
199 	break;
200     }
201 
202     return(curtag);
203 }
204 
ask_clear_existing_tags()205 static void ask_clear_existing_tags()
206 {
207     int tagged, i;
208     char tagmsg[SLEN], msg[SLEN];
209 
210     tagged = 0;
211     if (inalias) {
212 	for (i=0; i < num_aliases; i++) {
213 	    if (aliases[i]->status & TAGGED)
214 		tagged++;
215 	}
216     } else {
217 	for (i=0; i < curr_folder.num_mssgs; i++) {
218 	    if (curr_folder.headers[i]->status & TAGGED)
219 		tagged++;
220 	}
221     }
222 
223     if (tagged == 0)
224 	return;
225 
226     if (tagged > 1) {
227 	MCsprintf(tagmsg, catgets(elm_msg_cat, ElmSet, ElmSomeMessagesATagged,
228 		    "Some %s are already tagged."), nls_items);
229 	MCsprintf(msg, catgets(elm_msg_cat, ElmSet, ElmRemoveTags,
230 		    "%s Remove Tags?"), tagmsg);
231     } else {
232 	MCsprintf(tagmsg, catgets(elm_msg_cat, ElmSet, ElmAMessageATagged,
233 		    "One %s is already tagged."), nls_item);
234 	MCsprintf(msg, catgets(elm_msg_cat, ElmSet, ElmRemoveTag,
235 		    "%s Remove Tag?"), tagmsg);
236     }
237 
238     if (enter_yn(msg, TRUE, LINES-2, FALSE)) {
239 	if (inalias) {
240 	    for (i=0; i < num_aliases; i++) {
241 		aliases[i]->status &= ~TAGGED;
242 		show_new_status(i);
243 	    }
244 	} else {
245 	    for (i=0; i < curr_folder.num_mssgs; i++) {
246 		curr_folder.headers[i]->status &= ~TAGGED;
247 		show_new_status(i);
248 	    }
249 	}
250     }
251 
252 }
253 
254 
255 /*
256  * Select message based upon pattern supplied by user.
257  *
258  * Normally, matching is performed on the from and subject lines.
259  * If the user strikes "/" then an alternate pattern is selected,
260  * and matching is performed against the entire message (headers
261  * and body).  This makes things a bit twisty, because we need
262  * to get that first character and then decide what to do from
263  * there.
264  *
265  * If a match is found, the selection is updated and TRUE is
266  * displayed.  If matching fails then an error is displayed
267  * and FALSE is returned.  If the user aborts the match then
268  * we silently return FALSE.
269  */
270 int
pattern_match()271 pattern_match()
272 {
273     char inpbuf[SLEN], *sel_pat;
274     int inp_line, inp_col, anywhere, matched, ch, i;
275     static char hdr_pattern[SLEN];
276     static char body_pattern[SLEN];
277 
278     PutLine1(LINES-3, 40, catgets(elm_msg_cat, ElmSet, ElmMatchAnywhere,
279 		"/ = Match anywhere in %s."), nls_items);
280     PutLine0(LINES-1, 0, catgets(elm_msg_cat, ElmSet, ElmMatchPattern,
281 		"Match pattern: "));
282     GetCursorPos(&inp_line, &inp_col);
283     PutLine0(-1, -1, hdr_pattern);
284     CleartoEOLN();
285 
286     ch = ReadCh();
287     switch (ch) {
288 
289     case ctrl('D'):	/* abort */
290 	ClearLine(LINES-1);
291 	return FALSE;
292 
293     case '\n':		/* accept current pattern */
294     case '\r':
295 	if (hdr_pattern[0] == '\0') {
296 	    ClearLine(LINES-1);
297 	    return FALSE;
298 	}
299 	sel_pat = hdr_pattern;
300 	anywhere = FALSE;
301 	break;
302 
303     case '/':		/* switch patterns and match against entire message */
304 	MoveCursor(LINES-3, 40);
305 	CleartoEOLN();
306 	PutLine1(LINES-1, 0, catgets(elm_msg_cat, ElmSet,
307 		    ElmMatchPatternInEntire, "Match pattern (in entire %s): "),
308 		    nls_item);
309 	(void) strcpy(inpbuf, body_pattern);
310 	if (enter_string(inpbuf, sizeof(inpbuf), -1, -1, ESTR_UPDATE) < 0
311 		    || inpbuf[0] == '\0') {
312 	    ClearLine(LINES-1);
313 	    return FALSE;
314 	}
315 	sel_pat = strcpy(body_pattern, inpbuf);
316 	anywhere = TRUE;
317 	break;
318 
319     default:		/* edit the pattern */
320 	UnreadCh(ch); /* push back - let enter_string() deal with it */
321 	(void) strfcpy(inpbuf, hdr_pattern, sizeof(inpbuf));
322 	if (enter_string(inpbuf, sizeof(inpbuf), inp_line, inp_col,
323 		    ESTR_UPDATE) < 0 || inpbuf[0] == '\0') {
324 	    ClearLine(LINES-1);
325 	    return FALSE;
326 	}
327 	sel_pat = strcpy(hdr_pattern, inpbuf);
328 	anywhere = FALSE;
329 	break;
330 
331     }
332 
333     if (inalias) {
334 	for (i = curr_alias; i < num_aliases; i++) {
335 	    if (!selected || aliases[i]->status & VISIBLE) {
336 		matched = name_matches(i, sel_pat) || alias_matches(i, sel_pat);
337 		if (! matched && anywhere) {	/* Look only if no match yet */
338 		    matched = comment_matches(i, sel_pat) ||
339 			    address_matches(i, sel_pat);
340 		}
341 		if (matched) {
342 		    curr_alias = i+1;
343 		    return TRUE;
344 		}
345 	    }
346 	}
347 	goto not_matched;
348     }
349 
350     if (anywhere) {
351 	if (match_in_message(sel_pat))
352 	    return TRUE;
353 	goto not_matched;
354     }
355 
356     for (i = curr_folder.curr_mssg; i < curr_folder.num_mssgs; i++) {
357 	if (!selected || curr_folder.headers[i]->status & VISIBLE) {
358 	    if (from_matches(i, sel_pat) || subject_matches(i, sel_pat)) {
359 		curr_folder.curr_mssg = i+1;
360 		return TRUE;
361 	    }
362 	}
363     }
364 
365 not_matched:
366     error(catgets(elm_msg_cat, ElmSet, ElmPatternNotFound,
367 		"pattern not found!"));
368     return FALSE;
369 }
370 
371 
372 /*
373  * Local Procedures
374  */
375 
376 static int
from_matches(message_number,pat)377 from_matches(message_number, pat)
378 int message_number;
379 char *pat;
380 {
381     return patmatch(pat, curr_folder.headers[message_number]->from, PM_NOCASE|PM_WSFOLD);
382 }
383 
384 static int
subject_matches(message_number,pat)385 subject_matches(message_number, pat)
386 int message_number;
387 char *pat;
388 {
389     return patmatch(pat, curr_folder.headers[message_number]->subject, PM_NOCASE|PM_WSFOLD);
390 }
391 
392 static int
name_matches(message_number,pat)393 name_matches(message_number, pat)
394 int message_number;
395 char *pat;
396 {
397     return patmatch(pat, aliases[message_number]->name, PM_NOCASE|PM_WSFOLD);
398 }
399 
400 static int
alias_matches(message_number,pat)401 alias_matches(message_number, pat)
402 int message_number;
403 char *pat;
404 {
405     return patmatch(pat, aliases[message_number]->alias, PM_NOCASE|PM_WSFOLD);
406 }
407 
408 static int
comment_matches(message_number,pat)409 comment_matches(message_number, pat)
410 int message_number;
411 char *pat;
412 {
413     return patmatch(pat, aliases[message_number]->comment, PM_NOCASE|PM_WSFOLD);
414 }
415 
416 static int
address_matches(message_number,pat)417 address_matches(message_number, pat)
418 int message_number;
419 char *pat;
420 {
421     char *exp;
422     int dummy;
423 
424     exp = get_alias_address(aliases[message_number]->alias, TRUE, &dummy);
425     return (exp != NULL && patmatch(pat, exp, PM_NOCASE|PM_WSFOLD));
426 }
427 
428 static int
match_in_message(pat)429 match_in_message(pat)
430 char *pat;
431 {
432 	/** Match a string INSIDE a message...starting at the current
433 	    message read each line and try to find the pattern.  As
434 	    soon as we do, set current and leave!
435 	    Returns 1 if found, 0 if not
436 	**/
437 
438 	char buffer[VERY_LONG_STRING];
439 	int  message_number, lines, line, line_len, err;
440 
441 	message_number = curr_folder.curr_mssg-1;
442 
443 	error(catgets(elm_msg_cat, ElmSet, ElmSearchingFolderPattern,
444 		"Searching folder for pattern..."));
445 
446 	for ( ; message_number < curr_folder.num_mssgs; message_number++) {
447 
448 	  /*  if limited, search only selected messages */
449 	  if (selected &&
450 		  !(curr_folder.headers[message_number]->status & VISIBLE))
451 	    continue;
452 
453 	  if (fseek(curr_folder.fp, curr_folder.headers[message_number]->offset, 0L) == -1) {
454 
455 	    err = errno;
456 	    dprint(1, (debugfile,
457 		"Error: seek %ld bytes into file failed. errno %d (%s)\n",
458 		curr_folder.headers[message_number]->offset, err,
459 		"match_in_message"));
460 	    error2(catgets(elm_msg_cat, ElmSet, ElmMatchSeekFailed,
461 		   "ELM [match] failed looking %ld bytes into file (%s)."),
462 		   curr_folder.headers[message_number]->offset, strerror(err));
463 	    return TRUE; /* fake it out to avoid replacing error message */
464 	  }
465 
466 	  line = 0;
467 	  lines = curr_folder.headers[message_number]->lines;
468 
469 	  while ((line_len = mail_gets(buffer, VERY_LONG_STRING, curr_folder.fp)) &&
470 		line < lines) {
471 
472 	    if(buffer[line_len - 1] == '\n') line++;
473 
474 	    if (patmatch(pat, buffer, PM_NOCASE|PM_WSFOLD)) {
475 	      curr_folder.curr_mssg = message_number+1;
476 	      clear_error();
477 	      return TRUE;
478 	    }
479 	  }
480 	}
481 
482 	return FALSE;
483 }
484 
485