/*
* xmail - X window system interface to the mail program
*
* Copyright 1990,1991,1992 by National Semiconductor Corporation
*
* Permission to use, copy, modify, and distribute this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of National Semiconductor Corporation not
* be used in advertising or publicity pertaining to distribution of the
* software without specific, written prior permission.
*
* NATIONAL SEMICONDUCTOR CORPORATION MAKES NO REPRESENTATIONS ABOUT THE
* SUITABILITY OF THIS SOFTWARE FOR ANY PURPOSE. IT IS PROVIDED "AS IS"
* WITHOUT EXPRESS OR IMPLIED WARRANTY. NATIONAL SEMICONDUCTOR CORPORATION
* DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO
* EVENT SHALL NATIONAL SEMICONDUCTOR CORPORATION BE LIABLE FOR ANY SPECIAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*
* Author: Michael C. Wagnitz - National Semiconductor Corporation
*
*
* The following software modules were created and are Copyrighted by
* National Semiconductor Corporation:
*
* 1. regerr:
* 2. compile_pattern:
* 3. match: and
* 4. update_times.
*
* Author: Michael C. Wagnitz - National Semiconductor Corporation
*
*/
#define PARSER
#include "global.h"
#include "xmailregexp.h"
#include <ctype.h>
#include <utime.h>
#include "MailwatchP.h"
#if defined(sun) && defined(SVR4)
#define USE_REGEXP
#endif
extern void reset_mailbox(Widget gw, int down);
static char MailPrompt[80]; /* overkill on size, but to be safe */
#if defined(USE_REGEXP)
#define INIT register char *sp=instring; int sed=1;
#define GETC() (*sp++)
#define PEEKC() (*sp)
#define UNGETC(c) (--sp)
#define RETURN(c) return
#define ERROR(c) regerr(c)
#include <regexp.h>
int regexp_indx;
static char *regexp_error[] = {
"","","","","","","","","","","",
/* 11 */ "Range endpoint too large", "","","","",
/* 16 */ "Bad number", "","","","","","","","",
/* 25 */ "``\\digit'' out of range", "","","","","","","","","","",
/* 36 */ "Illegal or missing delimiter", "","","","",
/* 41 */ "No remembered search string",
/* 42 */ "\\(\\) imbalance",
/* 43 */ "Too many \\(",
/* 44 */ "More than 2 numbers given in \\{\\}",
/* 45 */ "} expected after \\",
/* 46 */ "First number exceeds second in \\{\\}", "", "",
/* 49 */ "[] imbalance",
/* 50 */ "Regular expression too long",
NULL
};
regerr(c)
int c;
{
(void) fprintf(stderr, "xmail: %s (item %d).\n", regexp_error[c], regexp_indx);
exit(0);
}
/*
** @(#)compile_pattern() - compile regular expression patterns in a table.
** A pattern table is an array of pattern records. Each pattern record
** consists of a regular expression, and a buffer for the compiled expression.
*/
void
compile_pattern(patternTable)
PatternRecPtr patternTable;
{
String bp;
PatternRecPtr cp;
for (regexp_indx=0, cp=patternTable; cp->pat||cp->buf; regexp_indx++, cp++) {
if (! cp->pat) continue; /* skip over grouping separations */
bp = (String) XtMalloc((unsigned) BUFSIZ);
(void) compile(cp->pat, bp, &bp[BUFSIZ], '\0');
cp->buf = bp;
}
}
#endif
/*
** @(#)match() - string against regular expressions in pattern table.
*/
int
match(PatternRecPtr patternTable, char *string)
{
PatternRecPtr cp;
char *bp;
int n, id;
if (strcmp(string, "") == 0) return -1;
for (id = 0, cp = patternTable; cp->pat || cp->buf; cp++) {
if (! cp->pat) { id++; continue; }
#if defined(USE_REGEXP)
if (advance(string, cp->buf))
return (id);
#else
if (bp = (char *)re_comp(cp->pat)) {
(void) fprintf(stderr, "xmail: re_comp: %s\n", bp);
return -1;
} else {
if ((n = re_exec(string)) == 1)
return (id);
else if (n < 0) {
(void) fprintf(stderr, "xmail: re_exec: internal error\n");
return -1;
}
}
#endif
}
return (-1);
}
/*
** @(#)parser_init()- compile command and output pattern tables.
*/
void
parser_init(void)
{
#if defined(USE_REGEXP)
(void) compile_pattern(command_pattern);
(void) compile_pattern(output_pattern);
#else
;
#endif
}
/*
** @(#)update_times - update previous folder's timestamps, so SetNewness()
** will restore no unread message flag for that folder.
*/
static
update_times(void)
{
MailwatchWidget mb = (MailwatchWidget) XtNameToWidget(toplevel,"icon.mailbox");
char *p, *q, tmp[BUFSIZ];
/*
** we need to strip the foldername from the previous title-bar contents
*/
p = strrchr(lastFolder, '"');
q = strchr(lastFolder, '"');
if (p && q && p != q) {
*p = '\0';
if (strcmp(++q, mb->mailbox.filename) != 0) { /* not system folder */
if (*q == '+') { /* the majority of the times */
initfoldir();
(void) sprintf(tmp, "%s%s", foldir, ++q);
q = tmp;
}
(void) utime(q, (struct utimbuf *) 0);
}
}
} /* end - update_times */
/*
** @(#)Reissue_Is_Valid - true/false reissue of Copy/Save to author was valid
*/
int
Reissue_Is_Valid(void)
{
int n;
String p, h, strchr(const char *, int), strrchr(const char *, int);
char buf[BUFSIZ], tmp[BUFSIZ];
if (Command[0] != 'C' && Command[0] != 'S')
return(False);
SetCursor(WATCH);
if (1 == sscanf(Command, "%*s %d", &n)) {
Command[0] = (Command[0] == 'C' ? 'c' : 's');
LASTCH(Command) = ' ';
} else {
n = SelectionNumber(False);
sprintf(buf, "%c %d ", Command[0] == 'C' ? 'c' : 's', n);
(void) strcpy(Command, buf);
}
(void) sprintf(buf, "f %d", n);
p = QueryMail(buf);
buf[0] = '\0';
if (1 != sscanf(p, "> %*d %s", buf))
(void) sscanf(p, " %*d %s", buf);
if (strchr(buf, '<')) {
if (! sscanf(buf, "%*[^<]<%[^>]>", tmp))
(void) sscanf(buf, "<%[^>]>", tmp);
(void) strcpy(buf, tmp);
} else
if (strchr(buf, '(')) {
for (h = buf; *h && strchr(" \t", *h); h++);
if (1 != sscanf(h, "%*[^)]) %s", tmp))
(void) sscanf(h, "%s", tmp);
(void) strcpy(buf, tmp);
}
if ((int)strlen(buf)) {
if (h = strrchr(buf, '!')) /* UUCP - host![host!]user[@host]... */
bcopy(h + 1, buf, (int)strlen(h));
if (h = strchr(buf, '@')) { /* ARPA address - user[%host]@host */
*h = '\0';
if (h = strchr(buf, '%'))
*h = '\0';
if (h = strrchr(buf, ':')) /* DECNet form - host::user%host@host */
bcopy(h + 1, buf, (int)strlen(h));
if (strchr("'\"", buf[0])) /* or - ["']user[@host]["']%host@host */
bcopy(buf + 1, buf, (int)strlen(buf));
}
}
if (h = GetMailEnv("outfolder")) {
XtFree((String) h);
(void) strcat(Command, "+");
}
(void) strcat(Command, buf);
(void) strcat(Command, "\n");
writeMail(Command);
return (True);
} /* end - Reissue_Is_Valid */
#include "unset.h"
char *checkOptions( char *optionStr )
{
char *holdPtr, *linePtr, *eolPtr;
int x, y;
static char optionsToUnset[128];
/* handle a NULL optionStr by returning NULL */
if (optionStr == NULL) return( NULL );
holdPtr = optionStr;
while (holdPtr) {
/* get a line, by getting the
* address of the carriage return
*/
linePtr = holdPtr;
eolPtr = strchr(holdPtr,(int)'\n');
/* advance our holdPtr to one character after
* the carriage return
*/
if (eolPtr)
holdPtr = eolPtr + 1;
else {
/* if its NULL, then its got to be the last line in the
* string and we'll set holdPtr to be NULL to break
* out of the loop
*/
eolPtr = strchr(holdPtr,(int)'\0');
/* Whoa! What's going on here .. its got to be garbage to
* get out of the loop
*/
if (!eolPtr) break;
holdPtr = NULL;
}
/* this is an optimization to get out of checking the
* options if we've already found them all
*/
for (x = 0, y = numOptions ; x < numOptions; x++ ) {
if (!options[x].found) {
if (strncmp( linePtr, options[x].name, options[x].len) == 0) {
options[x].found = TRUE;
y--;
break;
}
}
} /* end for */
if (!y) break;
} /* end while */
/* now we'll build a string of the options to unset comprised
* of the ones we found above
*/
holdPtr = optionsToUnset;
for (x = 0 ; x < numOptions; x++ ) {
if ( options[x].found ) {
sprintf( holdPtr, " %s", options[x].name );
holdPtr += options[x].len + 1;
*holdPtr = '\0';
}
}
/* if you didn't find any, then return NULL */
if (holdPtr == optionsToUnset) return(NULL);
return( optionsToUnset );
}
/*
** @(#)parse() - command and output and call appropriate handler
*/
void
parse(String msg)
{
static Boolean first_time = True;
int j, k, msgnum;
String c, s;
char tmp[128], *toUnset, *endOfLine;
Widget button, icon, iw;
j = match(command_pattern, Command);
switch (j) {
/*
** If error on startup, mail has terminated connection. Remove our input
** handler, close the file from our end, and indicate that no connection
** exists. Otherwise, establish conversation requirements and display the
** current message. If *Show_Last: resource is NOT False, show latest, if
** none are newer. We delay setting the icon event handler until now, to
** ensure mail startup completed before being interrupted by a map event,
** for the case where we have been started iconic.
*/
case XM_C_START : /*
** test to ensure we are not running binmail by mistake
*/
if (((int)strlen(msg) >= 17) &&
(! strcmp("unknown option N\n", &msg[(int)strlen(msg)-17]))) {
Bell(msg);
XCloseDisplay(XtDisplay(toplevel));
(void) fprintf(stderr,
"\007xmail: The specified (or default) mail handler is not supported.\n");
_exit(0);
}
if (XM_O_BELL == match(output_pattern, msg)) {
if (mailpid) {
XtRemoveInput(mailInputId);
(void) close(mail_fd);
mailpid = 0;
MailPrompt[0] = '\0';
}
if (strncmp(msg, "No mail for ", 12) == 0)
Bell("No mail in your system mailbox\n");
else Bell(msg);
(void) UpdateTitleBar("No current folder");
*msg = '\0';
} else {
/*
** To prevent a race condition (?) when starting the
** application iconic, (which would cause this loop to
** repeat multiple times), test the value of the mail
** environment variable ``screen''. If its value is
** 10,000 then we're done.
*/
if (c = GetMailEnv("screen")) {
j = (strcmp("10000", c) != 0) ? True : False;
XtFree((String) c);
} else j = True;
if (j) { /* if not yet set to 10000... */
(void) strcpy(tmp, "set screen=10000 toplines=100 ");
/* default action for xmail is hold (ala mailtool) */
if (c = GetMailrc("nohold")) XtFree((String) c);
else (void) strcat(tmp, "hold");
msg = QueryMail(tmp);
/* let's see what options are set. If we
* find crt, replyall or cmd in the list, then
* we will unset them
*/
*msg = '\0';
(void) strcpy(tmp, "set");
msg = QueryMail(tmp);
toUnset = checkOptions( msg );
if (toUnset) {
sprintf(tmp,"unset %s",toUnset );
msg = QueryMail(tmp);
}
*msg = '\0';
if (msgnum = file_handler()) {
(void) sprintf(tmp, "%d", msgnum);
msg = QueryMail(tmp);
}
Bell(""); /* reset any worthy-ness flag */
Bell(Default_Status_Info);
} else *msg = '\0';
}
if (first_time) { /* only need to do this once */
Waiting = FALSE;
first_time = False;
icon = XtNameToWidget(toplevel, "icon");
XtAddEventHandler(icon, StructureNotifyMask, False,
icon_handler, (XtPointer)NULL);
if (In_Bogus_Mail_File) {
(void) sprintf(tmp, "%s+", tmpName);
(void) unlink(tmp);
*msg = '\0'; /* ignore 'skipping' message */
}
}
break;
/*
** In response to normal or change folder commands, test output. If an
** error, display message in status window, and ring bell unless command
** was save, Save to author, or undelete. If Save or Copy results in an
** error, and the error response begins with 'Unknown', then this is not
** Sun mail, and we should try and compensate for these missing functions.
** If our current mail message folder is the bogus folder, erase any text
** and index and note state. If we have text of next mail message, tell
** index_handler to mark its number, else retrieve the appropriate message
** text (which also marks its number). If *Show_Last: resource is NOT False,
** file cmd shows latest, if none new.
*/
case XM_C_EXEC :
case XM_C_FILE :
/* This crass hack speeds up the error message search
* by taking advantage of the observation that the
* error messages occur on the first line of output.
* So we put in a temporary null to prevent the FSM
* from searching the whole message.
*/
if (endOfLine = strchr(msg, '\n'))
{
*endOfLine = 0;
j = match(output_pattern, msg);
*endOfLine = '\n';
}
else
j = match(output_pattern, msg);
switch (j) {
case XM_O_BELL:
if (strchr("SsCcw", Command[0]) != NULL) {
if (strncmp(msg, "Unknown ", 8) == 0 &&
Reissue_Is_Valid()) {
*msg = '\0';
break;
}
if (isdigit(msg[(int)strlen(msg) - 2]))
LASTCH(msg) = '\0';
else Command[0] = '\0';
}
if (strncmp(msg, tmpName, (int)strlen(tmpName)) == 0)
Bell("No mail in your system mailbox");
else {
Bell(msg);
*msg = '\0';
}
/*
** Save our current message number, because asking about our 'file' status
** will reset mail's idea of the 'current message' count to one, regardless.
*/
msgnum = SelectionNumber(False);
/*
** Now ask for 'file' status to determine if we are still caught in our 'bogus'
** mailfile, in order to generate a more meaningful title-bar status message.
*/
msg = QueryMail("file");
if (strncmp(&msg[1], tmpName, (int)strlen(tmpName)) == 0) {
(void) UpdateTitleBar("No current folder");
iw = XtNameToWidget(toplevel, "topBox");
writeTo(XtNameToWidget(iw, "indexWindow"), " ", REPLACE);
writeText(" ");
} else {
(void) UpdateTitleBar(msg);
/*
** If not in our bogus mail folder, reset the current message
** number in Mail by again pointing at that numbered message.
*/
(void) sprintf(tmp, "f %d", msgnum);
msg = QueryMail(tmp);
if (Command[0] != '\0')
if (strchr("Ssw", Command[0]))
markIndex(">S");
}
*msg = '\0';
break;
case XM_O_EXEC: writeText(msg);
if (strchr("-np", Command[0]))
msgnum = index_handler(0, 0);
else markIndex("> ");
*msg = '\0';
break;
case XM_O_FILE: c = strchr(msg, '\n');
if ((int)(c - msg) < (int)strlen(msg) - 1) {
*c = '\0'; /* don't bell this */
Bell(""); /* clear worthy flag */
Bell(msg);
}
*msg = '\0';
msgnum = file_handler();
update_times(); /* now that we're out */
if (msgnum) {
(void) sprintf(tmp, "%d", msgnum);
msg = QueryMail(tmp);
}
break;
}
break;
/*
** When deleting a message, simply mark that index line with a capital 'D'.
** If un-deleting, mark that index line with a blank ' '. If no current
** index, restore it. If autoprinting, mail will include text of next msg.
*/
case XM_C_DELETE : if (XM_O_BELL == match(output_pattern, msg)) {
Bell(msg);
*msg = '\0';
} else {
writeText(msg);
msgnum = SelectionNumber(False);
if (Command[0] == 'd') {
if (msgnum) {
markIndex("D");
msgnum = index_handler(msgnum + 1, 0);
}
} else { /* if we are 'undeleting' */
c = QueryMail("=");
(void) sscanf(c, "%d", &msgnum);
iw = XtNameToWidget(toplevel, "topBox.indexWindow");
if (TextGetLastPos(iw) < (XawTextPosition) 4) {
c = QueryMail("h");
writeTo(iw, c, REPLACE);
}
msgnum = index_handler(msgnum, 1);
}
c = QueryMail("file"); /* resets current msg to 1 */
(void) UpdateTitleBar(c);
if (msgnum == 0) /* if this was the last msg */
writeText(" "); /* in folder, erase its text */
else {
/*
** reset mail's idea of what is the current message
*/
(void) sprintf(tmp, "f %d", msgnum);
msg = QueryMail(tmp);
}
*msg = '\0';
}
break;
/*
** In response to a request to view new mail, first reset the mailbox flag.
** If mail came back with an error, complain to the user. Otherwise, clear
** the Newmail command button highlighting. Then display any new mail text.
*/
case XM_C_NEWMAIL:
button = XtNameToWidget(toplevel, "topBox.commandPanel.Folder");
UnsetNewmail(button, NULL, NULL);
if (XM_O_BELL == match(output_pattern, msg)) {
if (strncmp(msg, "No mail for ", 12) == 0) {
Bell("No mail in your system mailbox\n");
} else { Bell(msg); }
*msg = '\0';
} else {
Bell(""); /* reset any worthy-ness flag */
Bell(Default_Status_Info);
if (*msg) {
s = msg;
c = strchr(msg, '\n');
if ((c - s) + 1 < (int)strlen(msg)) {
*c = '\0'; /* don't bell this */
Bell(msg);
}
}
msgnum = file_handler();
update_times(); /* we're out of prev. folder */
*msg = '\0';
if (msgnum) {
(void) sprintf(tmp, "%d", msgnum);
msg = QueryMail(tmp);
}
}
break;
default: j = match(output_pattern, msg);
switch (j) {
case XM_O_BELL:
Bell(msg);
*msg = '\0';
break;
/*
** If output is from the print command, display a status message
*/
case XM_O_PRINT:
(void) sscanf(&Command[2], "%d", &j);
c = strrchr(msg, '/');
(void) sscanf(c, "/%d", &k);
(void) sprintf(tmp,
"Message %d sent to printer -- %d bytes\n", j, k);
Bell(tmp);
*msg = '\0';
break;
/*
** If we didn't specifically ask for it, and its not an error, just toss it.
** Preserve post processing moved here, in case we weren't allowed to do it.
*/
default:
if (strncmp(Command, "preserve", 8) == 0)
markIndex("P");
*msg = '\0';
break;
}
break;
}
/*
** If any text remains, display it in the bottom (text) window.
*/
if (*msg) {
for (j = (int)strlen(msg)-1; j > 1 && msg[j] == '\n' && msg[j-1] == '\n'; j--);
msg[++j] = '\0'; /* drop all but the last newline */
writeText(msg);
*msg = '\0';
}
SetCursor(NORMAL);
} /* parse */
#define FILEBUF 8192
/*
** @(#)QueryMail() - Sends a command and returns corresponding output.
** If called by the Xt input procedure readMail, no command is included.
** In an attempt to eliminate the occasional segmentation violation in
** either malloc or free, we no longer free this buffer each time it is
** used. Instead we simply allow it to grow to the maximum size of a
** read ever performed on the mail pipe.
*/
char *
QueryMail(char *cmd)
{
static int outputsize = 0;
int j, size, length = 0;
static String output;
String temp;
char s[FILEBUF];
char *endOfLine;
if (MailPrompt[0] != '\0') length = (int)strlen(MailPrompt);
/*
** allocate one block to 'output' to begin with
*/
if (outputsize <= 0) {
outputsize = FILEBUF;
output = (String) XtMalloc((unsigned) outputsize);
}
output[0] = '\0';
if (! mailpid)
Bell("No current mail program connection\n");
else {
if (*cmd) { /* allow us to be called by readMail */
if (LASTCH(cmd) != '\n')
(void) sprintf(s, "%s\n", cmd);
else
(void) strcpy(s, cmd);
writeMail(s);
}
for (;;) {
if ((size = read(mail_fd, s, FILEBUF)) < 1) { /* EOF or an error? */
if (! *output) { /* For some reason Mail has gone away */
(void) strcpy(output, "No current mail service connection\n");
if (mailpid) {
XtRemoveInput(mailInputId);
(void) close(mail_fd);
mailpid = 0;
MailPrompt[0] = '\0';
(void) strcpy(Command, "Start"); /* Focus our errors */
writeText(" "); /* erase any current message display */
writeTo(XtNameToWidget(toplevel, "topBox.indexWindow"), " ", REPLACE);
}
}
break;
}
if (size < FILEBUF)
s[size] = '\0';
if ((int)strlen(output) + size >= outputsize) {
outputsize += FILEBUF;
temp = (String) XtRealloc(output, outputsize);
output = temp;
}
(void) strcat(output, s);
if (strcmp(Command, "Start") == 0) {
/*
** Check for no message response before we try to catch the prompt.
*/
/* Once again, the crass hack for grepping just the first line. */
if (endOfLine = strchr(output,'\n'))
{
*endOfLine = 0;
j = match(output_pattern, output);
*endOfLine = '\n';
}
else
j = match(output_pattern, output);
if (XM_O_BELL == j)
break;
/*
** If we have no prompt and are just starting, get the prompt string.
** This makes the GROSS assumption that we will have gotten it here.
*/
if (*output && ! *MailPrompt) {
if ((temp = strrchr(output, '\n')) != NULL)
(void) strcpy(MailPrompt, temp + 1);
else
(void) strcpy(MailPrompt, output);
length = (int)strlen(MailPrompt);
}
}
j = (int)strlen(output) - length;
if (j < 0) j = 0; /* no references before zero */
if (*MailPrompt && strcmp(&output[j], MailPrompt) == 0) {
output[j] = '\0'; /* Drop prompt from end of text */
break; /* and return from read routine */
}
}
if (*cmd)
LASTCH(output) = '\0'; /* drop newline for normal queries */
}
return(output);
} /* QueryMail */
syntax highlighted by Code2HTML, v. 0.9.1