static char rcsid[] = "@(#)$Id: pattern.c,v 1.7 1998/02/11 22:02:15 wfp5p Exp $"; /******************************************************************************* * The Elm Mail System - $Revision: 1.7 $ $State: Exp $ * * Copyright (c) 1988-1995 USENET Community Trust * Copyright (c) 1986,1987 Dave Taylor ******************************************************************************* * Bug reports, patches, comments, suggestions should be sent to: * * Bill Pemberton, Elm Coordinator * flash@virginia.edu * ******************************************************************************* * $Log: pattern.c,v $ * Revision 1.7 1998/02/11 22:02:15 wfp5p * Beta 2 * * Revision 1.6 1996/05/09 15:51:24 wfp5p * Alpha 10 * * Revision 1.5 1996/03/14 17:29:45 wfp5p * Alpha 9 * * Revision 1.4 1995/09/29 17:42:21 wfp5p * Alpha 8 (Chip's big changes) * * Revision 1.3 1995/09/11 15:19:23 wfp5p * Alpha 7 * * Revision 1.2 1995/06/12 20:33:35 wfp5p * Alpha 2 clean up * * Revision 1.1.1.1 1995/04/19 20:38:37 wfp5p * Initial import of elm 2.4 PL0 as base for elm 2.5. * ******************************************************************************/ /** General pattern matching for the ELM mailer. **/ #include "elm_defs.h" #include "elm_globals.h" #include "s_elm.h" #include /* local procedures */ static void ask_clear_existing_tags P_((void)); static int from_matches(); static int subject_matches(); static int name_matches(); static int alias_matches(); static int comment_matches(); static int address_matches(); static int match_in_message(); int meta_match(function) int function; { /** Perform specific function based on whether an entered string matches either the From or Subject lines.. Return TRUE if the current message was matched, else FALSE. **/ int i, count, curtag; char *word_Action, *word_Actioned, *word_actioned; static char pat[SLEN]; switch (function) { case MATCH_TAG: word_Action = catgets(elm_msg_cat, ElmSet, ElmTag, "Tag"); word_Actioned = catgets(elm_msg_cat, ElmSet, ElmCapTagged, "Tagged"); word_actioned = catgets(elm_msg_cat, ElmSet, ElmTagged, "tagged"); break; case MATCH_DELETE: word_Action = catgets(elm_msg_cat, ElmSet, ElmDelete, "Delete"); word_Actioned = catgets(elm_msg_cat, ElmSet, ElmCapMarkDelete, "Marked for deletion"); word_actioned = catgets(elm_msg_cat, ElmSet, ElmMarkDelete, "marked for deletion"); break; case MATCH_UNDELETE: word_Action = catgets(elm_msg_cat, ElmSet, ElmUndelete, "Undelete"); word_Actioned = catgets(elm_msg_cat, ElmSet, ElmCapUndeleted, "Undeleted"); word_actioned = catgets(elm_msg_cat, ElmSet, ElmUndeleted, "undeleted"); break; default: assert(function == MATCH_TAG || function == MATCH_DELETE || function == MATCH_UNDELETE); return FALSE; } PutLine2(LINES-3, strlen(nls_Prompt), catgets(elm_msg_cat, ElmSet, ElmMessagesMatchPattern, "%s %s that match pattern..."), word_Action, nls_items); /* clear any existing tags? */ if (function == MATCH_TAG) ask_clear_existing_tags(); PutLine0(LINES-2, 0, catgets(elm_msg_cat, ElmSet, ElmEnterPattern, "Enter pattern: ")); if (enter_string(pat, sizeof(pat), -1, -1, ESTR_REPLACE) < 0 || pat[0] == '\0') { ClearLine(LINES-3); ClearLine(LINES-2); return FALSE; } strcpy(pat, shift_lower(pat)); count = 0; curtag = FALSE; if (inalias) { for (i = 0; i < num_aliases; i++) { if (!name_matches(i, pat) && !alias_matches(i, pat)) continue; if (selected && !(aliases[i]->status & VISIBLE)) continue; switch (function) { case MATCH_DELETE: if (aliases[i]->type & SYSTEM) continue; /* don't delete these!!! */ aliases[i]->status |= DELETED; break; case MATCH_UNDELETE: aliases[i]->status &= ~DELETED; break; case MATCH_TAG: aliases[i]->status |= TAGGED; break; } show_new_status(i); if (curr_alias == i+1) curtag = TRUE; count++; } } else { for (i = 0; i < curr_folder.num_mssgs; i++) { if (!from_matches(i, pat) && !subject_matches(i, pat)) continue; if (selected && !(curr_folder.headers[i]->status & VISIBLE)) continue; switch (function) { case MATCH_DELETE: curr_folder.headers[i]->status |= DELETED; break; case MATCH_UNDELETE: curr_folder.headers[i]->status &= ~DELETED; break; case MATCH_TAG: curr_folder.headers[i]->status |= TAGGED; break; } show_new_status(i); if (curr_folder.curr_mssg == i+1) curtag++; count++; } } MoveCursor(LINES-3, 0); CleartoEOS(); switch (count) { case 0: error2(catgets(elm_msg_cat, ElmSet, ElmNoMatchesNoTags, "No matches. No %s %s."), nls_items, word_actioned); break; case 1: error2(catgets(elm_msg_cat, ElmSet, ElmTaggedMessage, "%s 1 %s."), word_Actioned, nls_item); break; default: error3(catgets(elm_msg_cat, ElmSet, ElmTaggedMessages, "%s %d %s."), word_Actioned, count, nls_items); break; } return(curtag); } static void ask_clear_existing_tags() { int tagged, i; char tagmsg[SLEN], msg[SLEN]; tagged = 0; if (inalias) { for (i=0; i < num_aliases; i++) { if (aliases[i]->status & TAGGED) tagged++; } } else { for (i=0; i < curr_folder.num_mssgs; i++) { if (curr_folder.headers[i]->status & TAGGED) tagged++; } } if (tagged == 0) return; if (tagged > 1) { MCsprintf(tagmsg, catgets(elm_msg_cat, ElmSet, ElmSomeMessagesATagged, "Some %s are already tagged."), nls_items); MCsprintf(msg, catgets(elm_msg_cat, ElmSet, ElmRemoveTags, "%s Remove Tags?"), tagmsg); } else { MCsprintf(tagmsg, catgets(elm_msg_cat, ElmSet, ElmAMessageATagged, "One %s is already tagged."), nls_item); MCsprintf(msg, catgets(elm_msg_cat, ElmSet, ElmRemoveTag, "%s Remove Tag?"), tagmsg); } if (enter_yn(msg, TRUE, LINES-2, FALSE)) { if (inalias) { for (i=0; i < num_aliases; i++) { aliases[i]->status &= ~TAGGED; show_new_status(i); } } else { for (i=0; i < curr_folder.num_mssgs; i++) { curr_folder.headers[i]->status &= ~TAGGED; show_new_status(i); } } } } /* * Select message based upon pattern supplied by user. * * Normally, matching is performed on the from and subject lines. * If the user strikes "/" then an alternate pattern is selected, * and matching is performed against the entire message (headers * and body). This makes things a bit twisty, because we need * to get that first character and then decide what to do from * there. * * If a match is found, the selection is updated and TRUE is * displayed. If matching fails then an error is displayed * and FALSE is returned. If the user aborts the match then * we silently return FALSE. */ int pattern_match() { char inpbuf[SLEN], *sel_pat; int inp_line, inp_col, anywhere, matched, ch, i; static char hdr_pattern[SLEN]; static char body_pattern[SLEN]; PutLine1(LINES-3, 40, catgets(elm_msg_cat, ElmSet, ElmMatchAnywhere, "/ = Match anywhere in %s."), nls_items); PutLine0(LINES-1, 0, catgets(elm_msg_cat, ElmSet, ElmMatchPattern, "Match pattern: ")); GetCursorPos(&inp_line, &inp_col); PutLine0(-1, -1, hdr_pattern); CleartoEOLN(); ch = ReadCh(); switch (ch) { case ctrl('D'): /* abort */ ClearLine(LINES-1); return FALSE; case '\n': /* accept current pattern */ case '\r': if (hdr_pattern[0] == '\0') { ClearLine(LINES-1); return FALSE; } sel_pat = hdr_pattern; anywhere = FALSE; break; case '/': /* switch patterns and match against entire message */ MoveCursor(LINES-3, 40); CleartoEOLN(); PutLine1(LINES-1, 0, catgets(elm_msg_cat, ElmSet, ElmMatchPatternInEntire, "Match pattern (in entire %s): "), nls_item); (void) strcpy(inpbuf, body_pattern); if (enter_string(inpbuf, sizeof(inpbuf), -1, -1, ESTR_UPDATE) < 0 || inpbuf[0] == '\0') { ClearLine(LINES-1); return FALSE; } sel_pat = strcpy(body_pattern, inpbuf); anywhere = TRUE; break; default: /* edit the pattern */ UnreadCh(ch); /* push back - let enter_string() deal with it */ (void) strfcpy(inpbuf, hdr_pattern, sizeof(inpbuf)); if (enter_string(inpbuf, sizeof(inpbuf), inp_line, inp_col, ESTR_UPDATE) < 0 || inpbuf[0] == '\0') { ClearLine(LINES-1); return FALSE; } sel_pat = strcpy(hdr_pattern, inpbuf); anywhere = FALSE; break; } if (inalias) { for (i = curr_alias; i < num_aliases; i++) { if (!selected || aliases[i]->status & VISIBLE) { matched = name_matches(i, sel_pat) || alias_matches(i, sel_pat); if (! matched && anywhere) { /* Look only if no match yet */ matched = comment_matches(i, sel_pat) || address_matches(i, sel_pat); } if (matched) { curr_alias = i+1; return TRUE; } } } goto not_matched; } if (anywhere) { if (match_in_message(sel_pat)) return TRUE; goto not_matched; } for (i = curr_folder.curr_mssg; i < curr_folder.num_mssgs; i++) { if (!selected || curr_folder.headers[i]->status & VISIBLE) { if (from_matches(i, sel_pat) || subject_matches(i, sel_pat)) { curr_folder.curr_mssg = i+1; return TRUE; } } } not_matched: error(catgets(elm_msg_cat, ElmSet, ElmPatternNotFound, "pattern not found!")); return FALSE; } /* * Local Procedures */ static int from_matches(message_number, pat) int message_number; char *pat; { return patmatch(pat, curr_folder.headers[message_number]->from, PM_NOCASE|PM_WSFOLD); } static int subject_matches(message_number, pat) int message_number; char *pat; { return patmatch(pat, curr_folder.headers[message_number]->subject, PM_NOCASE|PM_WSFOLD); } static int name_matches(message_number, pat) int message_number; char *pat; { return patmatch(pat, aliases[message_number]->name, PM_NOCASE|PM_WSFOLD); } static int alias_matches(message_number, pat) int message_number; char *pat; { return patmatch(pat, aliases[message_number]->alias, PM_NOCASE|PM_WSFOLD); } static int comment_matches(message_number, pat) int message_number; char *pat; { return patmatch(pat, aliases[message_number]->comment, PM_NOCASE|PM_WSFOLD); } static int address_matches(message_number, pat) int message_number; char *pat; { char *exp; int dummy; exp = get_alias_address(aliases[message_number]->alias, TRUE, &dummy); return (exp != NULL && patmatch(pat, exp, PM_NOCASE|PM_WSFOLD)); } static int match_in_message(pat) char *pat; { /** Match a string INSIDE a message...starting at the current message read each line and try to find the pattern. As soon as we do, set current and leave! Returns 1 if found, 0 if not **/ char buffer[VERY_LONG_STRING]; int message_number, lines, line, line_len, err; message_number = curr_folder.curr_mssg-1; error(catgets(elm_msg_cat, ElmSet, ElmSearchingFolderPattern, "Searching folder for pattern...")); for ( ; message_number < curr_folder.num_mssgs; message_number++) { /* if limited, search only selected messages */ if (selected && !(curr_folder.headers[message_number]->status & VISIBLE)) continue; if (fseek(curr_folder.fp, curr_folder.headers[message_number]->offset, 0L) == -1) { err = errno; dprint(1, (debugfile, "Error: seek %ld bytes into file failed. errno %d (%s)\n", curr_folder.headers[message_number]->offset, err, "match_in_message")); error2(catgets(elm_msg_cat, ElmSet, ElmMatchSeekFailed, "ELM [match] failed looking %ld bytes into file (%s)."), curr_folder.headers[message_number]->offset, strerror(err)); return TRUE; /* fake it out to avoid replacing error message */ } line = 0; lines = curr_folder.headers[message_number]->lines; while ((line_len = mail_gets(buffer, VERY_LONG_STRING, curr_folder.fp)) && line < lines) { if(buffer[line_len - 1] == '\n') line++; if (patmatch(pat, buffer, PM_NOCASE|PM_WSFOLD)) { curr_folder.curr_mssg = message_number+1; clear_error(); return TRUE; } } } return FALSE; }