Copyright (c) 1991 Bell Communications Research, Inc. (Bellcore) Permission to use, copy, modify, and distribute this material for any purpose and without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies, and that the name of Bellcore not be used in advertising or publicity pertaining to this material without the specific, prior written permission of an authorized representative of Bellcore. BELLCORE MAKES NO REPRESENTATIONS ABOUT THE ACCURACY OR SUITABILITY OF THIS MATERIAL FOR ANY PURPOSE. IT IS PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. 1 Adding Diverse Multimedia Format Support to Established RFC822 Mail and Bulletin Board Readers Nathaniel S. Borenstein Bellcore Abstract It is surprisingly easy to use the RFC1049 "Content-type" header to turn virtually any mail reading interface into a multi-media mail reading interface. Mail readers are simply modified to use the new "metamail" program whenever they receive non-text mail. The metamail program is itself easily customizable by the use of a "mailcap" file that specifies the media types supported by a given site or user. Given the existence of the metamail program, this document explains how to add multimedia support to sixteen very different mail reading programs, including all of the most popular UNIX mail reading programs and (so far) one DOS mail reading program. Motivation Multimedia mail has been a long time coming. The biggest impediments to multimedia mail have been the absence of standards and the pain users experience when they convert to a new mail system. Thus, for example, to receive the benefits of the rich multimedia capabilities of the Andrew mail format, you have, in the past, had to convert from whatever other mailer you're using to one of the Andrew-based mailers. In the context of a new research project (the MAGICMAIL language for active messaging) the author of this document has discovered that it is remarkably easy to extend nearly any mail reading interface so that it recognizes certain "Content-type" headers and, when it receives mail with such headers, calls an appropriate external program to display or interpret the mail body. Because this functionality seems so generally useful, I have taken the time to create this document, in the hope that it will help to make various kinds of multimedia mail functionality more widely available. The ideas and code in this system apply equally well to both mail and bulletin board programs. Although this document talks mostly about mail, it applies equally to bulletin board readers, and in fact one bulletin board reading program (the Berkeley "msgs" program) is included in the set of programs discussed here. The Basic Idea Basically, there are only two things you have to do to each mail reading program: 1. Make the mail reader notice the special header ("Content-type") that marks a message as a non-text message. (In the case of mail readers that already understand certain content-types, such as Andrew, the mail reader must be modified only to deal with the content-types it does not already know how to handle. 2. When the special header appears, instead of (or, if it's much easier, in addition to) showing the user the body of the message, the mail reader must send that body off to the metamail interpreter. The metamail interpreter includes features that deal with the diverse situations of terminal-oriented and window-oriented mail readers. Beyond this, of course, you have to make sure that the appropriate interpreters are available on your users' search paths. In particular, you'll need the metamail binary, an appropriately-configured mailcap file, and interpreters for whatever mail formats you support locally. For information on how to use the metamail program or how to customize a mailcap file, see the metamail program's manual entry. This document will describe the specific patches that can be used to make certain established mail-reading programs work with metamail. A Variety of Mail and Bulletin Board Reading Interfaces With this document, you can patch all of your site's mail reading interfaces to support whatever multimedia formats are deemed useful at your site. This means that those who regularly use the multimedia tools can begin to send mail in those formats freely, without worrying about the ability of any local user to interpret the mail. It is my intent to make this document exhaustive; as time goes on, I hope it will grow to include an ever widening set of mail reading interfaces. Currently it includes all of the mail reading interfaces that I know to be in use anywhere in Bellcore's research laboratories. Currently this document describes how to add support for the following mail readers: Berkeley Mail (/usr/ucb/Mail, /usr/ucb/mail, and Tahoe mail) SunMail (another version of Berkeley mail, but rather different) Xmail (an X11 interface to Berkeley mail) Mailtool (Graphical Sun interface to Berkeley mail) Imail (Bellcore MICE mailer) PCS readmail/rdmail/sreadmail (another Bellcore mailer) MH -- Rand Message Handling System XMH -- X11 Interface to Rand Message Handling System Rmail -- GNU Emacs mail reading package VM -- Another GNU Emacs mail reading package MH-E -- Yet another GNU Emacs mail reading package (GNU interface to MH) CUI -- Andrew low-end mail reader VUI -- Andrew termcap-based mail reader Messages -- Andrew multimedia mail reader BatMail -- Andrew Emacs mail-reading interface Elm -- Mail reader from HP. Mush -- Yet another popular mail reader Msgs -- simple Berkeley bulletin board reader UUPC --a mail reading program for MS-DOS TRN -- a threaded netnews reader. MM -- Columbia University's mail reader If you have mail readers that are not dicussed here, you will still probably find some of this code useful as a model. If you develop a patch for some other mail reader, and you send it back to me, I'll include it in future versions of this document. NOTE: For the programs that were written in C, all of the patches are surrounded by "#ifndef NOMETAMAIL/#endif". This was done so that a single source could easily be maintained even for use at those sites that for some reason desire to inhibit the metamail functionality. Such sites need only compile with "-DNOMETAMAIL" to have the modified mailers behave exactly like their unmodified predecessors. ADDITIONAL NOTE: Most of these patches send a message to metamail if it has ANY content-type header. Technically, this is not necessary with a content-type such as Content-type: text/plain or Content-type: text/plain; charset=US-ASCII However, this can't just be a literal string comparison -- the full MIME content-type syntax must be parsed and the "charset" value checked. For example, you WOULD want to pass the following to metamail: Content-type: text/plain; charset=iso-8859-8 However, there is not really a need to pass the following to metamail: Content-type: text/plain; something-else=foobar; charset=us-ascii Instead of adding the content-type parsing to each mailer, most of the patches below simply call metamail for any message with a content-type header. This is a bit inefficient for plain ascii text, but most such messages probably won't have content-type headers anyway, and it makes the patches much simpler. Programmers incorporating these patches into "production" versions of mail readers to be released to the world might consider parsing the content-type header in full. 1.1 Berkeley Mail (/usr/ucb/[Mm]ail, and Tahoe mail) NOTE: If you don't have the sources for Berkeley mail, you can get about 95% of metamail functionality to work by setting your PAGER environment to "metamail" and reading everything with the pager (e.g. by using "more" instead of "type".) Alternately, you can just put the following two lines in your .mailrc file: set PAGER=metamail set crt=1 Alternately, you can modify NOTHING AT ALL, and can still read non-text message by simply typing "| metamail" in response to the "&" prompt. However, things will work slightly better if you can modify the source files. There are several versions of Berkeley mail; this describes the patch to the versions I happened to have. All of the changes are localized to the file cmd1.c However, the way that the Mail program handles output piped to "more" (or some other paging program) makes the patch about twice as complicated as it otherwise would be. At any rate, there are four changes: 1. In cmd1.c, there is a routine called "type1" -- in my version it starts on line 321. At the end of the declarations at the top of this routine, there is a line that says: FILE *ibuf, *obuf; (In some versions, the "*ibuf" is omitted. That's fine.) Immediately after that line, add the following declaration: #ifndef NOMETAMAIL int PipeToMore = 0; #endif 2. In the same routine, about 18 lines down from the previous patch, there is a code fragment that says: cp = value("PAGER"); if (cp == NULL || *cp == '\0') cp = MORE; obuf = popen(cp, "w"); if (obuf == NULL) { perror(cp); obuf = stdout; } else { pipef = obuf; sigset(SIGPIPE, brokpipe); } You need to put three new lines before this code fragment and one new line after it. The end result should look like this: #ifndef NOMETAMAIL PipeToMore = 1; #else cp = value("PAGER"); if (cp == NULL || *cp == '\0') cp = MORE; obuf = popen(cp, "w"); if (obuf == NULL) { perror(cp); obuf = stdout; } else { pipef = obuf; sigset(SIGPIPE, brokpipe); } #endif Note that in some versions of UNIX, the code will say "signal" instead of "sigset" -- you should use whichever is used in your original. In some other variants (notably Ultrix) this code is significantly different. What matters is that the code that sets up the pager be preceded by the #ifndef ... #else lines, and followed by the #endif line. 3. About 8 lines further down in that routine, you'll find something that looks like this: for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) { mesg = *ip; touch(mesg); mp = &message[mesg-1]; dot = mp; print(mp, obuf, doign); In the Tahoe version, there is another line before the "print" line: if (value("quiet") == NOSTR) fprintf(obuf, "Message %d:\n", mesg); THIS LINE SHOULD BE ADDED IF IT DOES NOT ALREADY EXIST! Otherwise xmail may behave badly with the modified mail program. The "print" line is, in my version of Berkeley mail, line 367, and in my version of Tahoe mail, line 343. REPLACE the "print" line with the following code: #ifndef NOMETAMAIL #include if (!getenv("NOMETAMAIL") && nontext(mp)) { char Fname[100], Cmd[120]; FILE *fp; int code; struct sgttyb ttystatein, ttystateout; sprintf(Fname, "/tmp/mail-metamail.%d.%d", getpid(), getuid()); fp = fopen(Fname, "w"); if (!fp) { perror(Fname); } else { send(mp, fp, 0); fclose(fp); sprintf(Cmd, "metamail -z %s -m Mail %s", (PipeToMore) ? "-p" : "", Fname); if (obuf != stdout) { pipef = NULL; pclose(obuf); obuf = stdout; } gtty(fileno(stdin), &ttystatein); gtty(fileno(stdout), &ttystateout); code = system(Cmd); stty(fileno(stdin), &ttystatein); stty(fileno(stdout), &ttystateout); /* The following line would cause the raw mail to print out if metamail failed */ /* if (code) print(mp, obuf, doign); */ } } else { if (PipeToMore && stdout == obuf) { obuf = popen(MORE, "w"); if (obuf == NULL) { perror(MORE); obuf = stdout; } else { files[fileno(obuf)].filep = obuf; files[fileno(obuf)].flags = PIPE_OPEN & ~KEEP_OPEN; pipef = obuf; sigset(SIGPIPE, brokpipe); } } print(mp, obuf, doign); } #else print(mp, obuf, doign); #endif Note that in some versions, it will say "send" instead of "print" -- use whichever routine name is used in your version. Note also that on some versions of UNIX, you will need to use "signal" instead of "sigset". Finally, in some versions of the sources, the "files" array does not exist, and the two lines that refer to it can simply be eliminated. IMPORTANT ADDITIONAL NOTE: IN SOME VERSIONS OF MAIL, the "send" procedure requires a fourth parameter. This should be set to NOSTR, as in send(mp, fp, 0, NOSTR); 4. At the very end of cmd1.c, add the following new routines: #ifndef NOMETAMAIL #include nontext(mp) struct message *mp; { long c; FILE *ibuf; char line[LINESIZE], *s, *t; ibuf = setinput(mp); c = mp->m_size; while (c > 0L) { fgets(line, LINESIZE, ibuf); c -= (long) strlen(line); if (line[0] == '\n') return(0); for (s=line; *s; ++s) if (isupper(*s)) *s = tolower(*s); if (!strncmp(line, "content-type:", 13) && notplain(line+13)) return(1); } return(0); } notplain(s) char *s; { char *t; if (!s) return(1); while (*s && isspace(*s)) ++s; for(t=s; *t; ++t) if (isupper(*t)) *t = tolower(*t); while (t > s && isspace(*--t)) {;} if (((t-s) == 3) && !strncmp(s, "text", 4)) return(0); if (strncmp(s, "text/plain", 10)) return(1); t = (char *) index(s, ';'); while (t) { ++t; while (*t && isspace(*t)) ++t; if (!strncmp(t, "charset", 7)) { s = (char *) index(t, '='); if (s) { ++s; while (*s && isspace(*s)) ++s; if (*s == '"') ++s; if (!strncmp(s, "us-ascii", 8)) return(0); } return(1); } t = (char *) index(t, ';'); } return(0); /* no charset, was text/plain */ } #endif These two changes should be all you need to do in order to make Berkeley mail work with metamail, assuming that you already have the "metamail" binary installed somewhere on your search path. 1.2 Sun Mail (another version of Berkeley mail) Sun's version of Berkeley mail is sufficiently different from the others to warrant a separate section. Patching the "Mail" program properly will almost automatically add metamail support to older versions of mailtool and xmail, because they simply call Mail. However, some further changes are necessary for those programs, as noted in subsequent sections. All of the changes are localized to two files, cmd1.c and cmd2.c In particular, there are four changes: 1. In cmd1.c, there is a routine called "type1" -- in my version it starts on line 317. At the end of the declarations at the top of this routine, there is a pair of lines that say: FILE *ibuf, *obuf; void (*saveint)(); Immediately after that line, add the following declaration: #ifndef NOMETAMAIL int PipeToMore = 0; #endif 2. In the same routine, about 18 lines down from the previous patch, there is a code fragment that says: obuf = popen(MORE, "w"); if (obuf == NULL) { perror(MORE); obuf = stdout; } else { pipef = obuf; sigset(SIGPIPE, brokpipe); } You need to put three new lines before this code fragment and one new line after it. The end result should look like this: #ifndef NOMETAMAIL PipeToMore = 1; #else obuf = popen(MORE, "w"); if (obuf == NULL) { perror(MORE); obuf = stdout; } else { pipef = obuf; sigset(SIGPIPE, brokpipe); } #endif 3. About 8 lines further down in that routine, you'll find something that looks like this: for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) { mesg = *ip; touch(mesg); mp = &message[mesg-1]; dot = mp; print(mp, obuf, doign); The "print;" line is, in my version, line 363. REPLACE the "print" line with the following code: #ifndef NOMETAMAIL #include if (!getenv("NOMETAMAIL") && nontext(mp)) { char Fname[100], Cmd[120]; FILE *fp; int code; struct sgttyb ttystatein, ttystateout; sprintf(Fname, "/tmp/mail-metamail.%d.%d", getpid(), getuid()); fp = fopen(Fname, "w"); if (!fp) { perror(Fname); } else { if (value("quiet") == NOSTR) fprintf(obuf, "Message %2d:\n", mp - &message[0] + 1); msend(mp, fp, 0, fputs); fclose(fp); sprintf(Cmd, "metamail -z %s -m Mail %s", (PipeToMore) ? "-p" : "", Fname); if (obuf != stdout) { pipef = NULL; pclose(obuf); obuf = stdout; } gtty(fileno(stdin), &ttystatein); gtty(fileno(stdout), &ttystateout); code = shell(Cmd); stty(fileno(stdin), &ttystatein); stty(fileno(stdout), &ttystateout); /* if (code) print(mp, obuf, doign); */ } } else { if (PipeToMore && obuf == stdout) { obuf = popen(MORE, "w"); if (obuf == NULL) { perror(MORE); obuf = stdout; } else { pipef = obuf; sigset(SIGPIPE, brokpipe); } } print(mp, obuf, doign); } #else print(mp, obuf, doign); #endif 2. At the very end of cmd1.c, add the following new routines: #ifndef NOMETAMAIL #include nontext(mp) struct message *mp; { long c; FILE *ibuf; char line[LINESIZE], *s, *t; ibuf = setinput(mp); c = mp->m_size; while (c > 0L) { fgets(line, LINESIZE, ibuf); c -= (long) strlen(line); if (line[0] == '\n') return(0); for (s=line; *s; ++s) if (isupper(*s)) *s = tolower(*s); if (!strncmp(line, "content-type:", 13) && notplain(line+13)) return(1); } return(0); } notplain(s) char *s; { char *t; if (!s) return(1); while (*s && isspace(*s)) ++s; for(t=s; *t; ++t) if (isupper(*t)) *t = tolower(*t); while (t > s && isspace(*--t)) {;} if (((t-s) == 3) && !strncmp(s, "text", 4)) return(0); if (strncmp(s, "text/plain", 10)) return(1); t = (char *) index(s, ';'); while (t) { ++t; while (*t && isspace(*t)) ++t; if (!strncmp(t, "charset", 7)) { s = (char *) index(t, '='); if (s) { ++s; while (*s && isspace(*s)) ++s; if (*s == '"') ++s; if (!strncmp(s, "us-ascii", 8)) return(0); } return(1); } t = (char *) index(t, ';'); } return(0); /* no charset, was text/plain */ } #endif 3. This step is required only if you want the changes to work right with Sun's MailTool program. In cmd2.c, there is a routine called "savemsglist" -- in my version it starts on line 187. About a page down in that routine, you'll find a line that looks like this: mp = &message[mesg-1]; This line is, in my version, line 215. Immediately following that line (i.e. between line 215 and line 216, in my version) add the following code: #ifndef NOMETAMAIL #include if (!mark && !getenv("NOMETAMAIL") && nontext(mp)) { char Fname[100], Cmd[120]; FILE *fp; int code; struct sgttyb ttystatein, ttystateout; sprintf(Fname, "/tmp/mail-metamail.%d.%d", getpid(), getuid()); fp = fopen(Fname, "w"); if (!fp) { perror(Fname); } else { msend(mp, fp, 0, fputs); fclose(fp); fclose(obuf); /* To allow appending, sigh */ sprintf(Cmd, "metamail -z -m Mail %s >> %s", Fname, file); gtty(fileno(stdin), &ttystatein); gtty(fileno(stdout), &ttystateout); code = shell(Cmd); /* ignore it if it fails */ stty(fileno(stdin), &ttystatein); stty(fileno(stdout), &ttystateout); if ((obuf = fopen(file, "a")) == NULL) { perror(""); return; } continue; } } #endif These three changes should be all you need to do in order to make Sun's version of Berkeley mail work with metamail, assuming that you already have the metamail binary installed somewhere on your search path. 1.3 Xmail (X11 Interface to Berkeley Mail) Xmail is a program that provides a graphical interface to Berkeley mail. It does most of its work by talking to the Berkeley mail program itself. Thus, if you want Xmail to work with metamail, you have to first of all upgrade your version of the Berkeley Mail program as described in the previous sections. Once you have done this, everything would work fine automatically except that there's no way for metamail to tell that it has been called non-interactively. You can tell it that this is the case (so that it won't try to ask any questions) by setting the MM_NOTTTY environment variable. Thus, a simple way to fix xmail would be to rename the "xmail" program to be "xmail.std" and then to install a new "xmail" program that looked something like this: #!/bin/csh -f setenv MM_NOTTTY 1 setenv MM_NOASK 1 setenv MM_TRANSPARENT 1 setenv XMAILER /usr/local/bin/Mail xmail.std $* The MM_NOASK variable is also set to avoid lots of terminal windows popping up to ask questions that are only being asked in the hope of saving you time if you don't want to run a handling program. This should make xmail work with metamail, although, unlike most of the modified mailers described in this document, it will not ask the users for confirmation before running metamail applications. IMPORTANT NOTE: Xmail is no longer known to be supported by ANYONE. People have reported some strange bugs with xmail, particularly in conjunction with metamail, which the author of metamail will NOT assume responsibility for fixing! No data has been lost, but the xmail program sometimes gets into a confused state and needs to be restarted. 1.4 Mailtool (SunTools Interface to Berkeley Mail) There are TWO basic methods for modifying mailtool. The easier method simply uses mailtool's built in customization mechanism. The harder method involves modifying the code a bit. 1.4.a Customizing mailtool to use metamail This information was supplied by Ingo Dean :; I don't know if anyone has mentioned this yet, but there's a really cheap way to view MIME encoded messages from within Sun's Mailtool program if you have metamail installed: Just copy /usr/lib/text_extras_menu to ~/.text_extras_menu and add the following line somewhere: "MIME" metamail -m textedit -p -x Now you get a new submenu in any Xview-based textedit widget called "MIME" under the "Text Pane" "Extras" menu. If you triple-click the text (selecting all text in the text window) and pull up the "MIME" option in the "Extras" submenu, it will start up metamail in an xterm for you. 1.4.b Customizing the mailtool software IMPORTANT NOTE: The instructions here do NOT work for Mailtool 2.0, Sun's latest version as of this writing. A future version of Mailtool will have MIME support built-in, but for now 2.0 users have no patch available and must use the customization mechanism described in 1.4.a. Mailtool is a program that provides a graphical interface to Berkeley mail. It does most of its work by talking to the Berkeley mail program itself. Thus, if you want mailtool to work with metamail, you have to first of all upgrade your version of the Berkeley Mail program as described in the previous sections. Once you have done this, everything would work fine automatically except that there's no way for metamail to tell that it has been called non-interactively. You can tell it that this is the case (so that it won't try to ask any questions) by setting the MM_NOTTTY environment variable. Thus, a simple way to fix mailtool would be to rename the "mailtool" program to be "mailtool.std" and then to install a new "mailtool" program that looked something like this: #!/bin/csh -f setenv MM_NOTTTY 1 setenv MM_NOASK 1 mailtool.std $* The MM_NOASK variable is also set to avoid lots of terminal windows popping up to ask questions that are only being asked in the hope of saving you time if you don't want to run a handling program. This should make mailtool work with metamail, although, unlike most of the modified mailers described in this document, it will not ask the users for confirmation before running metamail applications. 1.5 Imail (Bellcore MICE mailer) All of the changes are localized to the file util.c In particular, there are two changes to be made: 1. In util.c, there is a routine called "listit" -- in my version it starts on line 82. The first line of that routine looks like this: debug("about to listit, %d lines\n",nlines); Immediately following that line (i.e. between line 88 and line 89, in my version) add the following code: #ifndef NOMETAMAIL #include if (!getenv("NOMETAMAIL") && nontext(fp)) { struct sgttyb ttystatein, ttystateout; char Fname[100], Cmd[100], linebuf[1000]; FILE *fp2; int chars = 0, code; sprintf(Fname, "/tmp/imail-metamail.%d.%d", getpid(), getuid()); fp2 = fopen(Fname, "w"); if (!fp2) { perror(Fname); } else { while( chars < p->len && fgets(linebuf,sizeof(linebuf),fp) != NULL) { fputs(linebuf,fp2); chars += strlen(linebuf); } fclose(fp2); sprintf(Cmd, "metamail -p -R -z -m imail %s", Fname); gtty(fileno(stdin), &ttystatein); gtty(fileno(stdout), &ttystateout); code = system(Cmd); stty(fileno(stdin), &ttystatein); stty(fileno(stdout), &ttystateout); /* if (!code) return(0); */ return(0); /* Don't show raw datastream anyway! */ } } #endif 2. After the "listit" procedure, or at the very end of util.c, whichever you prefer, add the following new routines, which are used by the patch above: #ifndef NOMETAMAIL #include nontext(fp) FILE *fp; { char buf[1000], *s; int oldnl=nl, oldcnt=cnt, oldfptr, retval = 0; oldfptr = ftell(fp); while( cnt < nlines && fgets(buf,sizeof(buf),fp) != NULL) { if (buf[0] == '\n') break; for (s=buf; *s; ++s) if (isupper(*s)) *s = tolower(*s); if (!strncmp(buf, "content-type:", 13) && notplain(buf+13)) { retval = 1; break; } nl++; cnt++; } nl = oldnl; cnt = oldcnt; fseek(fp, oldfptr, 0); return(retval); } notplain(s) char *s; { char *t; if (!s) return(1); while (*s && isspace(*s)) ++s; for(t=s; *t; ++t) if (isupper(*t)) *t = tolower(*t); while (t > s && isspace(*--t)) {;} if (((t-s) == 3) && !strncmp(s, "text", 4)) return(0); if (strncmp(s, "text/plain", 10)) return(1); t = (char *) index(s, ';'); while (t) { ++t; while (*t && isspace(*t)) ++t; if (!strncmp(t, "charset", 7)) { s = (char *) index(t, '='); if (s) { ++s; while (*s && isspace(*s)) ++s; if (!strncmp(s, "us-ascii", 8)) return(0); } return(1); } t = (char *) index(t, ';'); } return(0); /* no charset, was text/plain */ } #endif These two changes should be all you need to do in order to make imail work with metamail, assuming that you already have the metamail binary installed somewhere on your search path. 1.6 PCS readmail/rdmail All of the changes are localized to the file display.c In particular, there are two changes to be made: 1. In display.c, there is a routine called "display". The first line of that routine looks like this: messnum = messord[messnum]; /* convert to internal number */ Immediately following that line (i.e. between 13 and 14 in my version) add the following code: #ifndef NOMETAMAIL { #include if (!getenv("NOMETAMAIL") && nontext(messnum)) { char Fname[100], Cmd[100], linebuf[1000]; FILE *fp2; int code; struct sgttyb ttystatein, ttystateout; sprintf(Fname, "/tmp/readmail-metamail.%d.%d.%d", getpid(), getuid()); fp2 = fopen(Fname, "w"); if (!fp2) { perror(Fname); } else { fseek(curr.fp,messbeg[messnum],0); while (fgets (linebuf, sizeof(linebuf), curr.fp) != NULL && ftell(curr.fp) <= messend[messnum]) { fputs(linebuf, fp2); } fclose(fp2); sprintf(Cmd, "metamail -m readmail -z %s %s", profile("page") ? "-p" : "", Fname); gtty(fileno(stdin), &ttystatein); gtty(fileno(stdout), &ttystateout); code = system(Cmd); stty(fileno(stdin), &ttystatein); stty(fileno(stdout), &ttystateout); /* if (!code)) */ return(0); } } } #endif 2. At the very end of display.c, add the following new routines, which are used by the patch above: #ifndef NOMETAMAIL #include nontext(messnum) int messnum; { char buf[1000], *s; fseek(curr.fp,messbeg[messnum],0); while (fgets (buf, sizeof(buf), curr.fp) != NULL && ftell(curr.fp) < messend[messnum]) { if (buf[0] == '\n') return(0); for (s=buf; *s; ++s) if (isupper(*s)) *s = tolower(*s); if (!strncmp(buf, "content-type:", 13) && notplain(buf+13)) return(1); } return(0); } notplain(s) char *s; { char *t; if (!s) return(1); while (*s && isspace(*s)) ++s; for(t=s; *t; ++t) if (isupper(*t)) *t = tolower(*t); while (t > s && isspace(*--t)) {;} if (((t-s) == 3) && !strncmp(s, "text", 4)) return(0); if (strncmp(s, "text/plain", 10)) return(1); t = (char *) index(s, ';'); while (t) { ++t; while (*t && isspace(*t)) ++t; if (!strncmp(t, "charset", 7)) { s = (char *) index(t, '='); if (s) { ++s; while (*s && isspace(*s)) ++s; if (!strncmp(s, "us-ascii", 8)) return(0); } return(1); } t = (char *) index(t, ';'); } return(0); /* no charset, was text/plain */ } #endif These two changes should be all you need to do in order to make pcs readmail/rdmail work with metamail, assuming that you already have the metamail binary installed somewhere on your search path. 1.7 PCS sreadmail All of the changes are localized to the file display.c In particular, there are two changes to be made: 1. In display.c, there is a routine called "display". The first line of that routine looks like this: messnum = messord[messnum]; /* convert to internal number */ Immediately following that line (i.e. between line 14 and line 15, in my version) add the following code: #ifndef NOMETAMAIL { #include if (!getenv("NOMETAMAIL") && nontext(messnum)) { char Fname[100], Cmd[100], linebuf[1000]; FILE *fp2; int code; struct sgttyb ttystatein, ttystateout; sprintf(Fname, "/tmp/sreadmail-metamail.%d.%d.%d", getpid(), getuid()); fp2 = fopen(Fname, "w"); if (!fp2) { perror(Fname); } else { fseek(curr.fp,messbeg[messnum],0); while (fgets (linebuf, sizeof(linebuf), curr.fp) != NULL && ftell(curr.fp) <= messend[messnum]) { fputs(linebuf, fp2); } fclose(fp2); sprintf(Cmd, "metamail -R -m sreadmail -z -p %s", Fname); gtty(fileno(stdin), &ttystatein); gtty(fileno(stdout), &ttystateout); code = system(Cmd); stty(fileno(stdin), &ttystatein); stty(fileno(stdout), &ttystateout); clear(); redisplay=TRUE; curr.pageno=0; helppage=0; /* if (!code)) */ return(0); } } } #endif 2. At the very end of display.c, add the following new routines, which are used by the patch above: #ifndef NOMETAMAIL #include nontext(messnum) int messnum; { char buf[1000], *s; fseek(curr.fp,messbeg[messnum],0); while (fgets (buf, sizeof(buf), curr.fp) != NULL && ftell(curr.fp) < messend[messnum]) { if (buf[0] == '\n') return(0); for (s=buf; *s; ++s) if (isupper(*s)) *s = tolower(*s); if (!strncmp(buf, "content-type:", 13) && notplain(buf+13)) return(1); } return(0); } notplain(s) char *s; { char *t; if (!s) return(1); while (*s && isspace(*s)) ++s; for(t=s; *t; ++t) if (isupper(*t)) *t = tolower(*t); while (t > s && isspace(*--t)) {;} if (((t-s) == 3) && !strncmp(s, "text", 4)) return(0); if (strncmp(s, "text/plain", 10)) return(1); t = (char *) index(s, ';'); while (t) { ++t; while (*t && isspace(*t)) ++t; if (!strncmp(t, "charset", 7)) { s = (char *) index(t, '='); if (s) { ++s; while (*s && isspace(*s)) ++s; if (!strncmp(s, "us-ascii", 8)) return(0); } return(1); } t = (char *) index(t, ';'); } return(0); /* no charset, was text/plain */ } #endif These two changes should be all you need to do in order to make pcs readmail/rdmail/sreadmail work with metamail, assuming that you already have the metamail binary installed somewhere on your search path. 1.8 MH -- Rand Message Handling System All of the changes are localized to the file uip/show.c In particular, there are two changes to be made: 1. In uip/show.c, there is a label "go_to_it:". Shortly after that you'll find the following bit of code: if (nshow) proc = "/bin/cat"; else { (void) putenv ("mhfolder", folder); if (strcmp (r1bindex (showproc, '/'), "mhl") == 0) { vec[0] = "mhl"; (void) mhl (vecp, vec); done (0); } proc = showproc; } Immediately preceding that code fragment (i.e. between between 245 and 246 in my version of uip/show.c) add the following code: #ifndef NOMETAMAIL if (!getenv("NOMETAMAIL") && nontext(--msgnum, mp)) { #ifdef SYS5 #include struct termio ttystatein, ttystateout; #else #include struct sgttyb ttystatein, ttystateout; #endif SYS5 char Cmd[120]; FILE *fp; int code; sprintf(Cmd, "metamail -e -p -m MH %s/%d", mp->foldpath, msgnum); #ifdef SYS5 ioctl (fileno(stdin), TCGETA, &ttystatein); ioctl (fileno(stdout), TCGETA, &ttystateout); #else gtty(fileno(stdin), &ttystatein); gtty(fileno(stdout), &ttystateout); #endif SYS5 code = system(Cmd); #ifdef SYS5 ioctl (fileno(stdin), TCSETAW, &ttystatein); ioctl (fileno(stdout), TCSETAW, &ttystateout); #else stty(fileno(stdin), &ttystatein); stty(fileno(stdout), &ttystateout); #endif SYS5 exit(code); } #endif 2. At the very end of uip/show.c, add the following new routine, which is used by the patch above: #ifndef NOMETAMAIL #include nontext(msgnum, mp) int msgnum; struct msgs *mp; { FILE *fp; char line[1000], *s; sprintf(line, "%s/%d", mp->foldpath, msgnum); fp = fopen(line, "r"); if (!fp) return(0); while (fgets(line, sizeof(line), fp)) { if (line[0] == '\n') {fclose(fp);return(0);} for (s=line; *s; ++s) if (isupper(*s)) *s = tolower(*s); if (!strncmp(line, "content-type:", 13) && notplain(line+13)) return(1); } fclose(fp); return(0); } notplain(s) char *s; { char *t; if (!s) return(1); while (*s && isspace(*s)) ++s; for(t=s; *t; ++t) if (isupper(*t)) *t = tolower(*t); while (t > s && isspace(*--t)) {;} if (((t-s) == 3) && !strncmp(s, "text", 4)) return(0); if (strncmp(s, "text/plain", 10)) return(1); t = (char *) index(s, ';'); while (t) { ++t; while (*t && isspace(*t)) ++t; if (!strncmp(t, "charset", 7)) { s = (char *) index(t, '='); if (s) { ++s; while (*s && isspace(*s)) ++s; if (!strncmp(s, "us-ascii", 8)) return(0); } return(1); } t = (char *) index(t, ';'); } return(0); /* no charset, was text/plain */ } #endif These two changes should be all you need to do in order to make MH work with metamail, assuming that you already have the metamail binary installed somewhere on your search path. 1.9 XMH -- X11 Interface to Rand Message Handling System All of the changes are localized to the file msg.c In particular, there are only two change to be made: 1. Near the beginning of the file (e.g. right after the last #include line), add the following code: #ifndef NOMETAMAIL struct mmdata { Msg msg; Scrn scrn; }; void RedisplayMsg(); void NoCallMetamail (widget, client_data, call_data) Widget widget; XtPointer *client_data; XtPointer call_data; { } void CallMetamail (widget, mmd, call_data) Widget widget; struct mmdata *mmd; XtPointer call_data; { char TmpFileName[200]; char Cmd[200]; sprintf(TmpFileName, "/tmp/xmh.%d.%d", getpid(), getuid()); sprintf(Cmd, "csh -c \"metamail -e -m XMH -x -d %s >& %s \"", MsgFileName(mmd->msg), TmpFileName); fprintf(stderr, "Executing %s, please wait...\n", Cmd); system(Cmd); mmd->msg->source = CreateFileSource(mmd->scrn->viewwidget, TmpFileName, FALSE); RedisplayMsg(mmd->scrn); unlink(TmpFileName); } #endif 2. In msg.c, there is a procedure "SetScrnNewMsg". In that procedure you will find the following bit of code: msg->num_scrns++; msg->scrn = (Scrn *) XtRealloc((char *)msg->scrn, (unsigned) sizeof(Scrn)*msg->num_scrns); msg->scrn[msg->num_scrns - 1] = scrn; if ((msg->source == NULL) || (msg->toc == DraftsFolder)) msg->source = CreateFileSource(scrn->viewwidget, MsgFileName(msg), scrn->kind == STcomp); Immediately following that code fragment (i.e. between between 312 and 313 in my version of msg.c) add the following code: #ifndef NOMETAMAIL if (!getenv("NOMETAMAIL")) { #include static XtCallbackRec yes_callbacks[] = { {CallMetamail, (XtPointer) NULL}, {(XtCallbackProc) NULL, (XtPointer) NULL} }; static XtCallbackRec no_callbacks[] = { {NoCallMetamail, (XtPointer) NULL}, {(XtCallbackProc) NULL, (XtPointer) NULL} }; FILE *fp; char line[1000], *s, *t, Query[200]; static struct mmdata Mmdata; fp = fopen(MsgFileName(msg), "r"); if (fp) { while (fgets(line, sizeof(line), fp)) { if (line[0] == '\n') break; for (s=line; *s; ++s) if (isupper(*s)) *s = tolower(*s); if (!strncmp(line, "content-type:", 13)) { s = &line[13]; while (s && isspace(*s)) ++s; t = index(s, ';'); if (t) { *t = (char) NULL; } else { t = index(s, '\n'); if (t) *t = (char) NULL; } if (t--) { while (isspace(*t) && (t > s)) { *t-- = (char) NULL; } } if (notplain(s)) { Mmdata.msg = msg; Mmdata.scrn = scrn; yes_callbacks[0].closure = (XtPointer) &Mmdata; no_callbacks[0].closure = (XtPointer) NULL; sprintf(Query, "This message is in %s format.\nDo you want to try to run an external interpreter?", s); if (getenv("MM_NOASK")) { CallMetamail(scrn->viewwidget, &Mmdata, NULL); } else { PopupConfirm(scrn->tocwidget, Query, yes_callbacks, no_callbacks); } break; } } } fclose(fp); } } #endif Finally, add the following procedure to the end of the file: notplain(s) char *s; { char *t; if (!s) return(1); while (*s && isspace(*s)) ++s; for(t=s; *t; ++t) if (isupper(*t)) *t = tolower(*t); while (t > s && isspace(*--t)) {;} if (((t-s) == 3) && !strncmp(s, "text", 4)) return(0); if (strncmp(s, "text/plain", 10)) return(1); t = (char *) index(s, ';'); while (t) { ++t; while (*t && isspace(*t)) ++t; if (!strncmp(t, "charset", 7)) { s = (char *) index(t, '='); if (s) { ++s; while (*s && isspace(*s)) ++s; if (!strncmp(s, "us-ascii", 8)) return(0); } return(1); } t = (char *) index(t, ';'); } return(0); /* no charset, was text/plain */ } These changes should be all you need to do in order to make XMH work with metamail, assuming that you already have the metamail binary installed somewhere on your search path. 1.10 Rmail -- GNU Emacs mail reading package Emacs being what it is, there are several versions of the rmail package floating around. Therefore the patch is particularly hard to describe, since your version of rmail may vary. The patch is therefore described in several steps. 1. Make sure your rmail has an "rmail-show-message-hook" function. If you look in the source file rmail.el, you will (probably) find a function called "rmail-show-message". This function itself varies significantly in different versions of rmail. Near the end of it, you might find a line of the form: (run-hooks 'rmail-show-message-hook) If there isn't such a line, you should add one -- probably right at the end of the function, before it returns. 2. You may now either make changes to the rmail source, or only to your own personal hook function. 2a. If you want to change the rmail source, put the following line immediately before the aforementioned "run-hooks" line: (rmail-check-content-type) 2a. If you want to change only your own customized rmail behavior, put the following lines in your "~/.emacs" file: (setq rmail-show-message-hook '(lambda() (rmail-check-content-type))) (If you already had an rmail-show-message-hook function defined, you can just put the rmail-check-content-type call at the beginning of it.) 3. Make sure you have the GNU "transparent.el" package installed. This is not part of the standard distribution, but is widely available, and should be part of most FTP archives, etc. 4. Put the following LISP code somewhere that emacs will find it. In particular, if you modified rmail.el in step 2a, then it probably makes sense to put this code at the end of that file. If you just modified your own .emacs file, it probably makes sense to put this code in your .emacs file. The code you want is: ;;; Functions added for METAMAIL support (require 'transparent) (defvar rmail-never-execute-automatically t "*Prevent metamail from happening semi-automatically") (define-key rmail-mode-map "!" 'rmail-execute-content-type) (defun rmail-check-content-type () "Check for certain Content Type headers in mail" (rmail-maybe-execute-content-type nil)) (defun rmail-execute-content-type () "Check for certain Content Type headers in mail" (interactive) (rmail-maybe-execute-content-type t)) (defun rmail-handle-content-type (ctype override dotoggle) (let (oldpt (oldbuf (current-buffer)) (fname (make-temp-name "/tmp/rmailct"))) (cond ((and rmail-never-execute-automatically (not override)) (progn (if dotoggle (rmail-toggle-header)) (message (concat "You can use '!' to run an interpreter for this '" ctype "' format mail.")))) ((or override (getenv "MM_NOASK") (y-or-n-p (concat "Run an interpreter for this '" ctype "' format mail? "))) (progn (save-restriction (goto-char (point-max)) (setq oldpt (point)) (goto-char 0) (widen) (write-region (point) oldpt fname 'nil "silent")) (if dotoggle (rmail-toggle-header)) (if (and window-system (getenv "DISPLAY")) (progn (switch-to-buffer-other-window "METAMAIL") (erase-buffer) (pop-to-buffer oldbuf) (start-process "metamail" "METAMAIL" "metamail" "-m" "rmail" "-p" "-x" "-d" "-z" "-q" fname) (message "Starting metamail. Sending output to METAMAIL buffer.")) (progn (switch-to-buffer "METAMAIL") (erase-buffer) (sit-for 0) (transparent-window "METAMAIL" "metamail" (list "-m" "rmail" "-p" "-d" "-z" "-q" fname) nil (concat "\n\r\n\r*****************************************" "*******************************\n\rPress any key " "to go back to EMACS\n\r\n\r***********************" "*************************************************\n\r") ))))) (t (progn (if dotoggle (rmail-toggle-header)) (message (concat "You can use the '!' keystroke to " "execute the external viewing program."))))))) (defun rmail-maybe-execute-content-type (dorun) "Check for certain Content Type headers in mail" (cond ((not (getenv "NOMETAMAIL")) (let* ((ct nil) (needs-toggled nil)) (save-excursion (save-restriction (widen) (goto-char (rmail-msgbeg rmail-current-message)) (forward-line 1) (if (and dorun (= (following-char) ?1)) (setq needs-toggled t)) (if (= (following-char) ?0) (narrow-to-region (progn (forward-line 2) (point)) (progn (search-forward "\n\n" (rmail-msgend rmail-current-message) 'move) (point))) (narrow-to-region (point) (progn (search-forward "\n*** EOOH ***\n") (beginning-of-line) (point)))) (setq ct (mail-fetch-field "content-type" t)))) (cond (ct (cond ((and (not (string= (downcase ct) "text")) (not (string= (downcase ct) "text/plain")) (not (string= (downcase ct) "text/plain; charset=us-ascii"))) (progn (if needs-toggled (rmail-toggle-header)) (rmail-handle-content-type ct dorun needs-toggled))) (needs-toggled (rmail-toggle-header))))))))) The above changes should be all you need to do in order to make rmail work with metamail, assuming that you already have the metamail binary installed somewhere on your search path. IMPORTANT NOTE: For some users and some versions of Emacs, terminal-oriented programs work poorly in the "transparent-window" (non-X11) case with the above patch. If you have problems in this case, you might get better behavior by adding "-R" to the list of options that are given to metamail. 1.11 VM -- Another GNU Emacs mail reading package Emacs being what it is, there are several mail readers besides rmail floating around. The'yre all similar, however. The following patch for VM is extremely similar to the patch for rmail -- in fact, the contents of the hook functions are identical. However, there are two versions of this patch given, for older and newer versions of VM 1.11.a VM Version 5.32 Patches for the current beta release of the VM GNU Emacs mail reading package: The following set of patches works for VM beta 5.32. 1. Modify the variable of "vm-visible-headers" to force the inclusion of the "Content-Type" and "Content-Transfer-Encoding" headers. In vm-vars.el, you will find a definition that looks something like this: (defvar vm-visible-headers '("From:" "Sender:" "Resent-From" "To:" "Apparently-To:" "Cc:" "Subject:" "Date:" "Resent-Date:") "*List of headers that should be visible when VM first displays a message. These should be listed in the order you wish them presented. Regular expressions are allowed.") You should alter this definition so that it looks like this instead: (defvar vm-visible-headers '("From:" "Sender:" "Resent-From" "To:" "Apparently-To:" "Cc:" "Subject:" "Date:" "Resent-Date:" "Content-Type:" "Content-Transfer-Encoding:") "*List of headers that should be visible when VM first displays a message. These should be listed in the order you wish them presented. Regular expressions are allowed.") (defvar vm-never-execute-automatically t "*Prevent metamail from happening semi-automatically") 2. Further down in vm-vars.el you will find a long variable definition that begins with (defvar vm-mode-map inside of this definition is this line (define-key map "!" 'shell-command) Exchange this for the line below (define-key map "!" 'vm-execute-content-type) 3. If you look in the source file vm-page.el, you will find a function called "vm-show-current-message". Near the end of it, you might find a line of the form: (vm-update-summary-and-mode-line) Immediately after that, you should add the line: (run-hooks 'vm-show-message-hook) 4. You may now either make changes to the vm source, or only to your own personal hook function. 4a. If you want to change the vm source, put the following line immediately before the aforementioned "run-hooks" line: (vm-check-content-type) 4b. If you want to change only your own customized vm behavior, put the following lines in your "~/.emacs" file: (setq vm-show-message-hook '(lambda() (vm-check-content-type))) (If you already had an vm-show-message-hook function defined, you can just put the vm-check-content-type call at the beginning of it.) 5. Make sure you have the GNU "transparent.el" package installed. This is not part of the standard distribution, but is widely available, and should be part of most FTP archives, etc. 6. Put the following LISP code somewhere that emacs will find it. At the end of vm-page.el or in your .emacs file are probably both good choices, depending perhaps on if you did 4a or 4b. The code you want is: ;;; Functions added for METAMAIL support (require 'transparent) (defun vm-check-content-type () "Check for certain Content Type headers in mail" (vm-maybe-execute-content-type nil)) (defun vm-execute-content-type () "Check for certain Content Type headers in mail" (interactive) (vm-maybe-execute-content-type t)) (defun vm-handle-content-type (ctype override) (let (oldpt (fname (make-temp-name "/tmp/rmailct"))) (cond ((and vm-never-execute-automatically (not override)) (progn (message (concat "You can use '!' to run an interpreter for this '" ctype "' format mail.")))) ((or override (getenv "MM_NOASK") (y-or-n-p (concat "Run an interpreter for this '" ctype "' format mail? "))) (progn (save-restriction (goto-char (point-max)) (setq oldpt (point)) (goto-char 0) (widen) (write-region (point) oldpt fname 'nil "silent")) (if (and window-system (getenv "DISPLAY")) (progn (switch-to-buffer-other-window "METAMAIL") (erase-buffer) (start-process "metamail" "METAMAIL" "metamail" "-m" "vm" "-x" "-d" "-z" "-q" fname) (if vm-mail-buffer (pop-to-buffer vm-mail-buffer)) (message "Starting metamail. Sending output to METAMAIL buffer.")) (progn (switch-to-buffer "METAMAIL") (erase-buffer) (sit-for 0) (transparent-window "METAMAIL" "metamail" (list "-m" "vm" "-d" "-z" "-q" fname) nil (concat "\n\r\n\r*****************************************" "*******************************\n\rPress any key " "to go back to EMACS\n\r\n\r***********************" "*************************************************\n\r") ))))) (t (progn (message (concat "You can use the '!' keystroke to " "execute the external viewing program."))))))) (defun vm-maybe-execute-content-type (dorun) "Check for certain Content Type headers in mail" (cond ((not (getenv "NOMETAMAIL")) (save-restriction (let ((saved-buffer (current-buffer)) (saved-window (selected-window))) (set-buffer (if vm-mail-buffer vm-mail-buffer (current-buffer))) (setq buffer-read-only 'nil) (let ((headend 0) (ctype "text") (old-min (point-min)) (old-max (point-max)) (opoint 0)) (goto-char (point-min)) (forward-line 1) (goto-char (point-min)) (search-forward "\n\n") (setq headend (point)) (setq opoint (- headend 1)) (goto-char (point-min)) (setq case-fold-search 'T) (cond ((search-forward "\ncontent-type:" opoint 't) (progn (forward-word 1) (backward-word 1) ; Took care of white space (setq opoint (point)) (re-search-forward "[;\n]" (point-max) t) (setq ctype (downcase (buffer-substring opoint (- (point) 1)))) (goto-char (point-min))))) (cond ((and (not (string= (downcase ctype) "text")) (not (string= (downcase ctype) "text/plain")) (not (string= (downcase ctype) "text/plain; charset=us-ascii"))) (vm-handle-content-type ctype dorun)) )) (set-buffer saved-buffer) (select-window saved-window) ))))) An older version of the last function, vm-maybe-execute-content-type, is given below for anyone who has problems with the version given above: (defun vm-maybe-execute-content-type (dorun) "Check for certain Content Type headers in mail" (cond ((not (getenv "NOMETAMAIL")) (save-restriction (setq buffer-read-only 'nil) (let ((headend 0) (ctype "text") (old-min (point-min)) (old-max (point-max)) (opoint 0)) (goto-char (point-min)) (forward-line 1) (goto-char (point-min)) (search-forward "\n\n") (setq headend (point)) (setq opoint (- headend 1)) (goto-char (point-min)) (setq case-fold-search 'T) (cond ((search-forward "\ncontent-type:" opoint 't) (progn (forward-word 1) (backward-word 1) ; Took care of white space (setq opoint (point)) (re-search-forward "[;\n]" (point-max) t) (setq ctype (downcase (buffer-substring opoint (- (point) 1)))) (goto-char (point-min))))) (cond ((and (not (string= (downcase ctype) "text")) (not (string= (downcase ctype) "text/plain")) (not (string= (downcase ctype) "text/plain; charset=us-ascii"))) (vm-handle-content-type ctype dorun)) )))))) 7. Watch out for an oddity in the terminal emulator package. If you get the error message Key sequence \277 uses invalid prefix characters or something like that, this probably means that you have set your global variable "help-char" to a META key. The terminal emulator package doesn't like that at all, and you'll need to change it in order for the terminal emulator package, and these extensions to vm, to work properly. The above changes should be all you need to do in order to make vm 5.32 work with metamail, assuming that you already have the metamail binary installed somewhere on your search path. IMPORTANT NOTE: For some users and some versions of Emacs, terminal-oriented programs work poorly in the "transparent-window" (non-X11) case with the above patch. If you have problems in this case, you might get better behavior by adding "-R" to the list of options that are given to metamail. Important note for Emacs "Hyperbole" users: Hyperbole, in its "hvm.el" file, overloads the definition of some VM functions. Therefore, any VM functions that need to be modified for metamail usage that exist in "hvm.el" should be patched there rather than in the VM code itself. 1.11.b Older Versions of VM 1. Modify the definition of "vm-build-visible-header-alist" to force the inclusion of the "Content-Type" and "Content-Transfer-Encoding" headers. In vm.el, you will find a definition that looks something like this: (defun vm-build-visible-header-alist () (let ((header-alist (cons nil nil)) (vheaders vm-visible-headers) list) (setq list header-alist) (while vheaders (setcdr list (cons (cons (car vheaders) nil) nil)) (setq list (cdr list) vheaders (cdr vheaders))) (setq vm-visible-header-alist (cdr header-alist)))) You should alter the declaration of "vheaders", so that it looks like this instead: (defun vm-build-visible-header-alist () (let ((header-alist (cons nil nil)) (vheaders (append vm-visible-headers '("Content-Type:" "Content-Transfer-Encoding:"))) list) (setq list header-alist) (while vheaders (setcdr list (cons (cons (car vheaders) nil) nil)) (setq list (cdr list) vheaders (cdr vheaders))) (setq vm-visible-header-alist (cdr header-alist)))) 2. Make sure your version of VM has a "vmail-show-message-hook" function. It probably doesn't. However, if you look in the source file vm.el, you will (probably) find a function called "vm-show-current-message". Near the end of it, you might find a line of the form: (vm-update-summary-and-mode-line) Immediately after that, you should add the line: (run-hooks 'vm-show-message-hook) 3. You may now either make changes to the vm source, or only to your own personal hook function. 3a. If you want to change the vm source, put the following line immediately before the aforementioned "run-hooks" line: (vm-check-content-type) 3a. If you want to change only your own customized vm behavior, put the following lines in your "~/.emacs" file: (setq vm-show-message-hook '(lambda() (vm-check-content-type))) (If you already had an vm-show-message-hook function defined, you can just put the vm-check-content-type call at the beginning of it.) 4. Make sure you have the GNU "transparent.el" package installed. This is not part of the standard distribution, but is widely available, and should be part of most FTP archives, etc. 5. Put the following LISP code somewhere that emacs will find it. In particular, if you modified vm.el in step 2a, then it probably makes sense to put this code at the end of that file. If you just modified your own .emacs file, it probably makes sense to put this code in your .emacs file. The code you want is: ;;; Functions added for METAMAIL support (require 'transparent) (defvar vm-never-execute-automatically t "*Prevent metamail from happening semi-automatically") (define-key vm-mode-map "!" 'vm-execute-content-type) (defun vm-check-content-type () "Check for certain Content Type headers in mail" (vm-maybe-execute-content-type nil)) (defun vm-execute-content-type () "Check for certain Content Type headers in mail" (interactive) (vm-maybe-execute-content-type t)) (defun vm-handle-content-type (ctype override) (let (oldpt (fname (make-temp-name "/tmp/rmailct"))) (cond ((and vm-never-execute-automatically (not override)) (progn (message (concat "You can use '!' to run an interpreter for this '" ctype "' format mail.")))) ((or override (getenv "MM_NOASK") (y-or-n-p (concat "Run an interpreter for this '" ctype "' format mail? "))) (progn (save-restriction (goto-char (point-max)) (setq oldpt (point)) (goto-char 0) (widen) (write-region (point) oldpt fname 'nil "silent")) (if (and window-system (getenv "DISPLAY")) (progn (switch-to-buffer-other-window "METAMAIL") (erase-buffer) (start-process "metamail" "METAMAIL" "metamail" "-m" "vm" "-x" "-d" "-z" "-q" fname) (if vm-mail-buffer (pop-to-buffer vm-mail-buffer)) (message "Starting metamail. Sending output to METAMAIL buffer.")) (progn (switch-to-buffer "METAMAIL") (erase-buffer) (sit-for 0) (transparent-window "METAMAIL" "metamail" (list "-m" "vm" "-d" "-z" "-q" fname) nil (concat "\n\r\n\r*****************************************" "*******************************\n\rPress any key " "to go back to EMACS\n\r\n\r***********************" "*************************************************\n\r") ))))) (t (progn (message (concat "You can use the '!' keystroke to " "execute the external viewing program."))))))) (defun vm-maybe-execute-content-type (dorun) "Check for certain Content Type headers in mail" (cond ((not (getenv "NOMETAMAIL")) (save-restriction (setq buffer-read-only 'nil) (let ((headend 0) (ctype "text") (old-min (point-min)) (old-max (point-max)) (opoint 0)) (goto-char (point-min)) (forward-line 1) (goto-char (point-min)) (search-forward "\n\n") (setq headend (point)) (setq opoint (- headend 1)) (goto-char (point-min)) (setq case-fold-search 'T) (cond ((search-forward "\ncontent-type:" opoint 't) (progn (forward-word 1) (backward-word 1) ; Took care of white space (setq opoint (point)) (re-search-forward "[;\n]" (point-max) t) (setq ctype (downcase (buffer-substring opoint (- (point) 1)))) (goto-char (point-min))))) (cond ((and (not (string= (downcase ctype) "text")) (not (string= (downcase ctype) "text/plain")) (not (string= (downcase ctype) "text/plain; charset=us-ascii"))) (vm-handle-content-type ctype dorun)) )))))) 6. Watch out for an oddity in the terminal emulator package. If you get the error message Key sequence \277 uses invalid prefix characters or something like that, this probably means that you have set your global variable "help-char" to a META key. The terminal emulator package doesn't like that at all, and you'll need to change it in order for the terminal emulator package, and these extensions to vm, to work properly. The above changes should be all you need to do in order to make vm work with metamail, assuming that you already have the metamail binary installed somewhere on your search path. IMPORTANT NOTE: For some users and some versions of Emacs, terminal-oriented programs work poorly in the "transparent-window" (non-X11) case with the above patch. If you have problems in this case, you might get better behavior by adding "-R" to the list of options that are given to metamail. Important note for Emacs "Hyperbole" users: Hyperbole, in its "hvm.el" file, overloads the definition of some VM functions. Therefore, any VM functions that need to be modified for metamail usage that exist in "hvm.el" should be patched there rather than in the VM code itself. 1.12 MH-E -- GNU Emacs interface to MH mail This patch was contributed by Dave Cohrs. Alter the definition of the function mh-display-msg. In that function, near the very end (line 1223 in my version) you will find the following lines: (setq mode-line-buffer-identification (list (format mh-show-buffer-mode-line-buffer-id folder msg-num)))))) You should REPLACE these lines with the following ones: (setq mode-line-buffer-identification (list (format mh-show-buffer-mode-line-buffer-id folder msg-num))) (mh-check-content-type) ; Metamail extension ))) 3. Add the following code to the end of the source file: ;;; Functions added for METAMAIL support (require 'transparent) (defvar mh-never-execute-automatically t "*Prevent metamail from happening semi-automatically") (define-key mh-folder-mode-map "@" 'mh-execute-content-type) (defun mh-check-content-type () "Check for certain Content Type headers in mail" (mh-maybe-execute-content-type nil)) (defun mh-execute-content-type () "Check for certain Content Type headers in mail" (interactive) (mh-maybe-execute-content-type t)) (defun mh-exec-metamail-cmd-output (command &rest args) ;; Execute MH library command COMMAND with ARGS. ;; Put the output into buffer after point. Set mark after inserted text. (push-mark (point) t) (erase-buffer) (apply 'call-process command nil t nil (mh-list-to-string args)) (exchange-point-and-mark) (set-buffer-modified-p nil) (other-window -1)) (defun mh-handle-content-type (ctype override) (let (oldpt (fname (make-temp-name "/tmp/rmailct"))) (cond ((and mh-never-execute-automatically (not override)) (progn (message (concat "You can use '@' to run an interpreter for this '" ctype "' format mail.")))) ((or override (getenv "MM_NOASK") (y-or-n-p (concat "Run an interpreter for this '" ctype "' format mail? "))) (progn (save-restriction (goto-char (point-max)) (setq oldpt (point)) (goto-char 0) (widen) (write-region (point) oldpt fname 'nil "silent")) (if (and window-system (getenv "DISPLAY")) (mh-exec-metamail-cmd-output "metamail" "-m" "mh-e" "-x" "-d" "-q" fname) (progn (other-window -1) (switch-to-buffer "METAMAIL") (erase-buffer) (sit-for 0) (transparent-window "METAMAIL" "metamail" (list "-m" "mh-e" "-p" "-d" "-q" fname) nil (concat "\n\r\n\r*****************************************" "*******************************\n\rPress any key " "to go back to EMACS\n\r\n\r***********************" "*************************************************\n\r")))))) (t (progn (message (concat "You can use the '@' keystroke to " "execute the external viewing program."))))))) (defun mh-maybe-execute-content-type (dorun) "Check for certain Content Type headers in mail" (cond ((not (getenv "NOMETAMAIL")) (save-restriction (setq buffer-read-only 'nil) (let ((headend 0) (ctype "text/plain") (old-min (point-min)) (old-max (point-max)) (opoint 0)) (if mh-show-buffer (pop-to-buffer mh-show-buffer)) (goto-char (point-min)) (forward-line 1) (goto-char (point-min)) (search-forward "\n\n" nil 1) (setq headend (point)) (setq opoint (- headend 1)) (goto-char (point-min)) (setq case-fold-search 'T) (cond ((search-forward "\ncontent-type:" opoint 't) (progn (forward-word 1) (backward-word 1) ; Took care of white space (setq opoint (point)) (re-search-forward "[;\n]" (point-max) t) (setq ctype (downcase (buffer-substring opoint (- (point) 1)))) (goto-char (point-min))))) (cond ((and (not (string= (downcase ctype) "text")) (not (string= (downcase ctype) "text/plain")) (not (string= (downcase ctype) "text/plain; charset=us-ascii"))) (mh-handle-content-type ctype dorun)) )))))) 3. Make sure you have the GNU "transparent.el" package installed. This is not part of the standard distribution, but is widely available, and should be part of most FTP archives, etc. The above changes should be all you need to do in order to make mh-e work with metamail, assuming that you already have the metamail binary installed somewhere on your search path. If MH-E hackers contribute improvements, they will be incorporated into a future version of this document. IMPORTANT NOTE: For some users and some versions of Emacs, terminal-oriented programs work poorly in the "transparent-window" (non-X11) case with the above patch. If you have problems in this case, you might get better behavior by adding "-R" to the list of options that are given to metamail. 1.13 CUI (Simplest Andrew Mail Reader) NOTE: If you have the version of CUI that corresponds to Messages version 8.0 or later, you do not need this patch. If you have the version that corresponds to Messages 7.15 or later, you probably do not need this patch, either. If you have an earlier version, you need this patch. In the file ams/msclients/cui/cui.c, there is a routine called "GetBodyFromCUID" The first line of that routine looks like this: debug(1,("GetBodyFromCUID %d\n", cuid)); Immediately after that line, which is between lines 1228 and 1229 in my version, add the following lines: #ifndef NOMETAMAIL { #include struct sgttyb ttystatein, ttystateout; char ctype[100], TmpFileName[1+MAXPATHLEN], Cmd[1+MAXPATHLEN]; int ShouldDelete, code; extern int LinesOnTerminal; if (CUI_GetHeaderContents(cuid,(char *) NULL, HP_CONTENTTYPE, ctype, sizeof(ctype) - 1) != NULL) { /* error already reported */ return(-1); } if (!getenv("NOMETAMAIL") && ctype[0] && strnicmp(ctype, "x-be2", 5) && nontext(ctype)) { if (CUI_GetBodyToLocalFile(cuid, TmpFileName, &ShouldDelete)) { return(-1); /* error reported */ } sprintf(Cmd, "metamail -m cui %s %s %s", (LinesOnTerminal > 0) ? "-p" : "", ShouldDelete ? "-z" : "", TmpFileName); gtty(fileno(stdin), &ttystatein); gtty(fileno(stdout), &ttystateout); code = system(Cmd); stty(fileno(stdin), &ttystatein); stty(fileno(stdout), &ttystateout); /* if (code) */ return(0); } } #endif Then, at the end of the file, add the following code: #ifndef NOMETAMAIL nontext(s) char *s; { char *t; if (!s) return(1); while (*s && isspace(*s)) ++s; for(t=s; *t; ++t) if (isupper(*t)) *t = tolower(*t); while (t > s && isspace(*--t)) {;} if (((t-s) == 3) && !strncmp(s, "text", 4)) return(0); if (strncmp(s, "text/plain", 10)) return(1); t = (char *) index(s, ';'); while (t) { ++t; while (*t && isspace(*t)) ++t; if (!strncmp(t, "charset", 7)) { s = (char *) index(t, '='); if (s) { ++s; while (*s && isspace(*s)) ++s; if (!strncmp(s, "us-ascii", 8)) return(0); } return(1); } t = (char *) index(t, ';'); } return(0); /* no charset, was text/plain */ } #endif This simple change should be all you need to do in order to make CUI work with metamail, assuming that you already have the "metamail" executable installed somewhere on your search path. 1.14 VUI (Terminal-oriented Andrew Mail Reader) NOTE: If you have the version of VUI that corresponds to Messages version 8.0 or later, you do not need this patch. If you have the version that corresponds to Messages 7.15 or later, you probably do not need this patch, either. If you have an earlier version, you need this patch. In the file ams/msclients/vui/vuipnl.c, there is a routine called "InitBodyData" That routine starts out with the following line of code: char filename[MAXPATHLEN+1]; Immediately after that code, which is between lines 2386 and 2387 in my version, add the following lines: #ifndef NOMETAMAIL #include struct sgttyb ttystatein, ttystateout; char ctype[100], TmpFileName[1+MAXPATHLEN], Cmd[1+MAXPATHLEN]; int ShouldDelete, cuid = CuidFromMsgno(msgno), code; if (CUI_GetHeaderContents(cuid,(char *) NULL, HP_CONTENTTYPE, ctype, sizeof(ctype) - 1) != NULL) { /* error already reported */ return(-1); } if (!getenv("NOMETAMAIL") && ctype[0] && ULstrncmp(ctype, "x-be2", 5) && nontext(ctype)) { if (CUI_GetBodyToLocalFile(cuid, TmpFileName, &ShouldDelete)) { return(-1); /* error reported */ } sprintf(Cmd, "metamail -R -m vui -p %s %s", ShouldDelete ? "-z" : "", TmpFileName); gtty(fileno(stdin), &ttystatein); gtty(fileno(stdout), &ttystateout); code = system(Cmd); stty(fileno(stdin), &ttystatein); stty(fileno(stdout), &ttystateout); RedrawScreen(0); UpdateMsgData (msgno, 1); return(MENU_EMPTY); } #endif Then, at the end of the file, add the following code: #ifndef NOMETAMAIL nontext(s) char *s; { char *t; if (!s) return(1); while (*s && isspace(*s)) ++s; for(t=s; *t; ++t) if (isupper(*t)) *t = tolower(*t); while (t > s && isspace(*--t)) {;} if (((t-s) == 3) && !strncmp(s, "text", 4)) return(0); if (strncmp(s, "text/plain", 10)) return(1); t = (char *) index(s, ';'); while (t) { ++t; while (*t && isspace(*t)) ++t; if (!strncmp(t, "charset", 7)) { s = (char *) index(t, '='); if (s) { ++s; while (*s && isspace(*s)) ++s; if (!strncmp(s, "us-ascii", 8)) return(0); } return(1); } t = (char *) index(t, ';'); } return(0); /* no charset, was text/plain */ } #endif This simple change should be all you need to do in order to make VUI work with metamail, assuming that you already have the "metamail" executable installed somewhere on your search path. 1.15 Messages (Andrew Multimedia Mail Reader) NOTE: If you have Messages version 8.0 or later, you do not need this patch. If you have Messages 7.15 or later, you probably do not need this patch, either. If you have an earlier version, you need this patch. There are two changes, both localized to the file atkams/messages/lib/text822.c: 1. Near the beginning of atkams/messages/lib/text822.c, there are the following #include lines: #include #include #include #include Immediately after those lines, insert the following lines: #ifndef NOMETAMAIL #include #include #undef popen /* BOGUS -- should be handled by fdphack */ #undef pclose /* ditto */ #include #include #include MetaOutput(fp, self) FILE *fp; struct text *self; { char buf[1000]; if (fgets(buf, sizeof(buf), fp) != NULL) { text_AlwaysInsertCharacters(self, text_GetLength(self), buf, strlen(buf)); text_NotifyObservers(self, 0); return; } if (errno != EWOULDBLOCK) { im_RemoveFileHandler(fp); pclose(fp); strcpy(buf, "\n--- Command execution terminated ---\n"); text_AlwaysInsertCharacters(self, text_GetLength(self), buf, strlen(buf)); } text_NotifyObservers(self, 0); } #endif 2. In atkams/messages/lib/text822.c, there is a routine called "text822__ReadIntoText" Near the end of that very long routine, you will find some code that looks like this: } else if (!amsutil_lc2strncmp("troff", sfmttype, strlen(sfmttype))) { char **resources = (char **) amsutil_BreakDownResourcesIntoArray(fmtresources); rofftext_ReadRoffIntoText(d, fp, ShowPos, resources); if (resources) free(resources); ReadRaw = FALSE; } The patch needs to be added AFTER the line that says "ReadRaw = FALSE" but before the next line, which is just a closing brace. (In my version, this means the patch goes between lines 353 and 354.) The patch to insert is as follows: #ifndef NOMETAMAIL } else if (!environ_Get("NOMETAMAIL") && IsReallyTextObject && amsutil_lc2strncmp("text", sfmttype, strlen(sfmttype))) { /* IsReallyTextObject test ensures we don't run metamail when printing!!! */ char TmpFileName[1+MAXPATHLEN], LineBuf[1000], Cmd[1+MAXPATHLEN], Msg[50+MAXPATHLEN], TmpFile2[1+MAXPATHLEN]; FILE *fp2; sprintf(Msg, "Do you want to run an interpreter for this '%s' format mail", sfmttype); if (environ_Get("MM_NOASK") || ams_GetBooleanFromUser(ams_GetAMS(), Msg, TRUE)) { ams_CUI_GenLocalTmpFileName(ams_GetAMS(), TmpFileName); ams_CUI_GenLocalTmpFileName(ams_GetAMS(), TmpFile2); fp2 = (FILE *) fopen (TmpFileName, "w"); if (fp2) { fseek(fp, 0, 0); while (fgets(LineBuf, sizeof(LineBuf), fp)) { fputs(LineBuf, fp2); } fclose(fp2); sprintf(Cmd, "metamail -m messages -z -x -d -q %s 2>&1", TmpFileName); sprintf(Msg, "Executing: %s\n", Cmd); linelen = strlen(Msg); text822_AlwaysInsertCharacters(d, ShowPos, Msg, linelen); ShowPos += strlen(Msg); fp2 = (FILE *) popen(Cmd, "r"); im_AddFileHandler(fp2, MetaOutput, d, 0); } } #endif This simple change should be all you need to do in order to make Messages work with metamail, assuming that you already have the "metamail" executable installed somewhere on your search path. 1.16 BatMail (Emacs interface to Andrew) Patching BatMail is quite simple. This patch was provided by Bob Glickstein, and has not been tested by the author of this document. Batmail already provides for a hook called "bat-body-hook" which gets called when a new message body is displayed. You can hook metamail into batmail by simply defining that hook to be the Lisp code below. (The one catch is that the user must have "mime-version" and "content-type" as headers which are normally displayed, specified by the bat-headers variable. The reason is that the hook searches the body buffer for these headers [rather than the time-consuming option of grepping the body file ("robin" does not have a GetHeaderContents stub)].) The following is the code that you need to add to BatMail: (setq bat-headers (concat "from:resent-from:resent-to:date:subject:to" ":cc:newsgroups:mime-version:content-type:content-transfer-encoding")) (setq bat-body-hook 'bat-body-hook-fn) (defun bat-body-hook-fn () (save-excursion (set-buffer bat-display-buf) (goto-char (point-min)) (let ((end-of-headers (if (search-forward "\n\n" (point-max) t) (point)))) (goto-char (point-min)) (if (re-search-forward "^[Mm][Ii][Mm][Ee]-[Vv]ersion:[ \\t]*\\(.+\\)" end-of-headers t) (let ((mime-version (buffer-substring (match-beginning 1) (match-end 1)))) (goto-char (point-min)) (if (re-search-forward "^[Cc]ontent-[Tt]ype:[ \\t]*\\(.+\\)" end-of-headers t) (let ((content-type (buffer-substring (match-beginning 1) (match-end 1)))) (if (not (string-match "^text/plain" content-type)) (if (get-tty-bool "Run metamail" t) (let ((tmp (concat "/tmp/" (make-temp-name "bat-meta-"))) (cuid (bat-current-cuid)) (process-connection-type nil) metamail-process) (bat-command "a" cuid ":" tmp "\n") (while (not (file-exists-p tmp)) (sit-for 1)) (setq metamail-process (start-process "metamail" bat-display-buf "metamail" "-d" "-m" "batmail" "-x" "-z" tmp)) (if metamail-process (progn (process-kill-without-query metamail-process nil)) (error "Couldn't start metamail")))))))))))) 1.17 Elm (Mail system from HP) NOTE: Elm version 2.4 (and, presumably later versions) have metamail built-in, so no patching of the Elm code is necessary if you have a recent enough version. All of the changes are localized to the file src/showmsg.c In particular, there are two changes to be made: 1. In src/showmsg.c, there is a routine called "show_msg" my version it starts on line 49. About a page down in that routine, you'll find something that looks like this: memory_lock = FALSE; /* some explanation for that last one - We COULD use memory locking to speed up the paging, but the action of "ClearScreen" on a screen with memory lock turned on seems to vary considerably (amazingly so) so it's safer to only allow memory lock to be a viable bit of trickery when dumping text to the screen in scroll mode. Philosophical arguments should be forwarded to Bruce at the University of Walamazoo, Australia, via ACSNet *wry chuckle* */ Immediately following that fragment (i.e. between line 114 and line 115, in my version) add the following code: #ifndef NOMETAMAIL if (!getenv("NOMETAMAIL") && nontext(current_header)) { char fname[100], Cmd[200], line[VERY_LONG_STRING]; int code; long lines = current_header->lines; FILE *fpout; if (fseek(mailfile, current_header->offset, 0) != -1) { sprintf(fname, "/tmp/elm-metamail.%d.%d", getpid(), getuid()); fpout = fopen(fname, "w"); if (fpout) { while (lines > 0L) { fgets(line, VERY_LONG_STRING, mailfile); fputs(line, fpout); --lines; } fclose(fpout); sprintf(Cmd, "metamail -R -P -z -m Elm %s", fname); Raw(OFF); code = system(Cmd); Raw(ON); /* if (code) */ return(0); } } } #endif 2. At the very end of src/showmsg.c, add the following new routines, which are used by the patch above: #ifndef NOMETAMAIL nontext(ch) struct header_rec *ch; { long lines = ch->lines; char line[VERY_LONG_STRING], *s, *t; if (fseek(mailfile, ch->offset, 0) == -1) return(-1); while (lines > 0L) { fgets(line, VERY_LONG_STRING, mailfile); --lines; if (line[0] == '\n') return(0); for (s=line; *s; ++s) if (isupper(*s)) *s = tolower(*s); if (!strncmp(line, "content-type:", 13)) { s = &line[13]; while (s && isspace(*s)) ++s; t = index(s, '\n'); if (!t) t = index(s, ';'); if (t) *t-- = NULL; while (t && *t && t > s && isspace(*t)) *t-- = NULL; if (notplain(s)) return(1); } } return(0); } notplain(s) char *s; { char *t; if (!s) return(1); while (*s && isspace(*s)) ++s; for(t=s; *t; ++t) if (isupper(*t)) *t = tolower(*t); while (t > s && isspace(*--t)) {;} if (((t-s) == 3) && !strncmp(s, "text", 4)) return(0); if (strncmp(s, "text/plain", 10)) return(1); t = (char *) index(s, ';'); while (t) { ++t; while (*t && isspace(*t)) ++t; if (!strncmp(t, "charset", 7)) { s = (char *) index(t, '='); if (s) { ++s; while (*s && isspace(*s)) ++s; if (!strncmp(s, "us-ascii", 8)) return(0); } return(1); } t = (char *) index(t, ';'); } return(0); /* no charset, was text/plain */ } #endif These two changes should be all you need to do in order to make Elm work with metamail, assuming that you already have the "metamail" executable installed somewhere on your search path. NOTE ABOUT XTERM AND ELM: Apparently, sometimes the combination of Elm, xterm, and richtext can cause problems. The following note from the Elm documentation describes the problem and how to fix it: > Limitations/Problems you might encounter in compiling and installing Elm: > > >From comp.mail.elm, dws@ssec.wisc.edu (DaviD W. Sanderson) writes: > >... whoever wrote the default termcap > >and/or terminfo descriptions for xterm included in the ti/te strings > >the special escape sequences to make xterm switch between the normal > >and alternate screen buffers. These sequences are: > > > > \E[?47h - use alternate screen buffer > > \E[?47l - use normal screen buffer > >... > >The elm code is just fine as it is. If you change it so that it > >doesn't ever send ti/te, you'll just break elm for somebody else. Fix > >your termcap/terminfo definition instead. Making this change allows richtext messages to be properly displayed. 1.18 Mush mail reader The following patch was provided by Bart Schaefer. Her reports that this patch should work with versions of mush as far back as 6.5, although 6.5 doesn't have M_PRIORITY() either, so you'd have to apply by hand and omit that part. He also reports that mush version 7.2.5 will have metamail support built in, so no patches will be necessary if you are using that version or later. There are two files to patch, msgs.c and mush.h. I haven't included the non-essential third patch that shows an "M" in the "headers" summary if the message is a MIME-format message. The first two hunks of this patch, to msgs.c, modify display_msg() to have it send the "raw" message to the program named in the $metamail variable when the message's METAMAIL flag bit is set. If $metamail is not set, it ignores the METAMAIL bit and pages the message normally. The third hunk modifies load_folder() to recognize the Content-Type: header and turn the METAMAIL bit on. (There's some other slop in there about checking Resent-Date: for a date which can safely be ignored if patching by hand.) *** 7.2.4/msgs.c Sun Feb 2 13:59:14 1992 --- 7.2.5/msgs.c Mon Mar 9 23:38:34 1992 *************** *** 8,13 **** --- 8,14 ---- u_long flg; { char buf[32], *pager = NULL; + int intro = TRUE; if (ison(msg[n].m_flags, DELETE) && !do_set(set_options, "show_deleted")) { print("Message %d deleted; ", n+1); *************** *** 31,37 **** #ifdef MSG_SEPARATOR turnon(flg, NO_SEPARATOR); #endif /* MMDF */ ! if (!istool && isoff(flg, NO_PAGE) && crt < msg[n].m_lines && isoff(flg, M_TOP)) { if (!(pager = do_set(set_options, "pager"))) pager = DEF_PAGER; --- 32,44 ---- #ifdef MSG_SEPARATOR turnon(flg, NO_SEPARATOR); #endif /* MMDF */ ! if (ison(msg[n].m_flags, METAMAIL) && isoff(flg, NO_PAGE) && ! (pager = do_set(set_options, "metamail"))) { ! intro = FALSE; ! turnoff(flg, NO_HEADER); ! turnoff(flg, M_TOP); ! turnon(flg, NO_IGNORE); ! } else if (!istool && isoff(flg, NO_PAGE) && crt < msg[n].m_lines && isoff(flg, M_TOP)) { if (!(pager = do_set(set_options, "pager"))) pager = DEF_PAGER; *************** *** 38,46 **** if (!*pager || !strcmp(pager, "internal")) pager = NULL; /* default to internal pager if pager set to "" */ } ! (void) do_pager(pager, TRUE); /* start pager */ ! (void) do_pager(sprintf(buf, "Message #%d (%d lines)\n", ! n+1, msg[n].m_lines), FALSE); (void) copy_msg(n, NULL_FILE, flg, NULL); (void) do_pager(NULL, FALSE); /* end pager */ } --- 45,54 ---- if (!*pager || !strcmp(pager, "internal")) pager = NULL; /* default to internal pager if pager set to "" */ } ! (void) do_pager(pager, intro? 1 : -1); /* start pager */ ! if (intro) ! (void) do_pager(sprintf(buf, "Message #%d (%d lines)\n", ! n+1, msg[n].m_lines), FALSE); (void) copy_msg(n, NULL_FILE, flg, NULL); (void) do_pager(NULL, FALSE); /* end pager */ } *************** *** 897,905 **** */ while (fgets(buf, sizeof (buf), fp) && (*buf != '\n')) { p = buf; if (!strncmp(buf, "Date:", 5)) strdup(msg[cnt].m_date_sent, parse_date(p+5)); ! if (!strncmp(buf, "Priority:", 9)) { for (p += 9 ; *p != '\n'; p++) { if (!isalpha(*p) || upper(*p) > 'A' + MAX_PRIORITY) continue; --- 910,923 ---- */ while (fgets(buf, sizeof (buf), fp) && (*buf != '\n')) { p = buf; if (!strncmp(buf, "Date:", 5)) strdup(msg[cnt].m_date_sent, parse_date(p+5)); ! else if (!msg[cnt].m_date_sent && ! !strncmp(buf, "Resent-Date:", 12)) ! msg[cnt].m_date_sent = savestr(parse_date(p+12)); ! else if (!strncmp(buf, "Content-Type:", 13)) ! turnon(msg[cnt].m_flags, METAMAIL); ! else if (!strncmp(buf, "Priority:", 9)) { for (p += 9 ; *p != '\n'; p++) { if (!isalpha(*p) || upper(*p) > 'A' + MAX_PRIORITY) continue; This patch to mush.h defines the METAMAIL bit. I hope nobody is counting on being able to redefine MAX_PRIORITY as 10. :-} *** 7.2.4/mush.h Sun Feb 2 13:50:50 1992 --- 7.2.5/mush.h Mon Mar 2 21:04:59 1992 *************** *** 430,441 **** #define REPLIED ULBIT(17) /* Messages that have been replied to */ #define NEW_SUBJECT ULBIT(18) /* new subject regardless of $ask (mail -s) */ #define SAVED ULBIT(19) /* when message has been saved */ - #ifdef MSG_SEPARATOR #define NO_SEPARATOR ULBIT(20) /* don't include message separator lines */ ! #endif /* MSG_SEPARATOR */ ! #define M_PRIORITY(n) ULBIT(21+(n)) ! /* It is possible to reset MAX_PRIORITY to as high as 10 */ #define MAX_PRIORITY 5 #define MAXMSGS_BITS MAXMSGS/sizeof(char) /* number of bits for bitmap */ --- 430,440 ---- #define REPLIED ULBIT(17) /* Messages that have been replied to */ #define NEW_SUBJECT ULBIT(18) /* new subject regardless of $ask (mail -s) */ #define SAVED ULBIT(19) /* when message has been saved */ #define NO_SEPARATOR ULBIT(20) /* don't include message separator lines */ ! #define METAMAIL ULBIT(21) /* message can display with "metamail" */ ! #define M_PRIORITY(n) ULBIT(22+(n)) ! /* It is possible to reset MAX_PRIORITY to as high as 9 */ #define MAX_PRIORITY 5 #define MAXMSGS_BITS MAXMSGS/sizeof(char) /* number of bits for bitmap */ 1.19 Msgs (Berkeley Bulletin Board system) All of the changes are localized to the file msgs.c In particular, there are four changes to be made: 1. In msgs.c, there are lots of global declarations near the top. In my version, the last of them (around line 114) looks like this: jmp_buf tstpbuf; Immediately after that line, add the following code: #ifndef NOMETAMAIL int NonTextMessage; #endif 2. Further down in msgs.c, there is a routine named "prmesg" (around line 580) which starts with the following declarations: FILE *outf, *inf; int c; Immediately after these declaration, add the following code: #ifndef NOMETAMAIL if (!getenv("NOMETAMAIL") && NonTextMessage) { char Fname[100], Cmd[120]; FILE *fp; int code; struct sgttyb ttystatein, ttystateout; sprintf(Cmd, "metamail %s -m Mail %s", pause ? "-p" : "", fname); gtty(fileno(stdin), &ttystatein); gtty(fileno(stdout), &ttystateout); code = system(Cmd); stty(fileno(stdin), &ttystatein); stty(fileno(stdout), &ttystateout); return; } #endif 3. Still further down in msgs.c, there is a routine named "gfrsub" (around line 775). A page or so down in that routine, there is code that looks like this: while (fgets(inbuf, sizeof inbuf, infile) && !(blankline = (inbuf[0] == '\n'))) { /* * extract Subject line */ if (!seensubj && strncmp(inbuf, "Subj", 4)==0) { seensubj = YES; frompos = ftell(infile); strncpy(subj, nxtfld(inbuf), sizeof subj); } Immediately before this code fragment, add the following three lines: #ifndef NOMETAMAIL NonTextMessage = 0; #endif Immediately after this code fragment (around line 840 in my version) add the following code: #ifndef NOMETAMAIL { char *s, *t; for (s=inbuf; *s; ++s) if (isupper(*s)) *s = tolower(*s); if (!strncmp(inbuf, "content-type:", 13)) { s = inbuf + 13; while (s && isspace(*s)) ++s; t = (char *) index(s, ';'); if (t) *t = NULL; else t = s+strlen(s); while (--t > s && isspace(*t)) *t = NULL; NonTextMessage = nontext(s); } } #endif Finally, add this routine to the end of the file: nontext(s) char *s; { char *t; if (!s) return(1); while (*s && isspace(*s)) ++s; for(t=s; *t; ++t) if (isupper(*t)) *t = tolower(*t); while (t > s && isspace(*--t)) {;} if (((t-s) == 3) && !strncmp(s, "text", 4)) return(0); if (strncmp(s, "text/plain", 10)) return(1); t = (char *) index(s, ';'); while (t) { ++t; while (*t && isspace(*t)) ++t; if (!strncmp(t, "charset", 7)) { s = (char *) index(t, '='); if (s) { ++s; while (*s && isspace(*s)) ++s; if (!strncmp(s, "us-ascii", 8)) return(0); } return(1); } t = (char *) index(t, ';'); } return(0); /* no charset, was text/plain */ } These four changes should be all you need to do in order to make msgs work with metamail, assuming that you already have the "metamail" executable installed somewhere on your search path. 1.20 UUPC (MS-DOS Mail-Reading Program) UUPC is an Internet mail-reading program for MS-DOS. It turns out that if you build metamail for DOS, you don't need to patch the UUPC program at all. Instead, you can simply set your PAGER to metamail. In particular, you need to put a line like the following in your personal configuration file: Pager=metamail %s This should be all that it takes to make UUPC work with metamail. However, you should note that the metamail "-p" feature is not being used (and probably doesn't yet work on DOS), which means that your mailcap entries should take paging into account. In particular, you probably want to have an entry for "text/richtext" that passes "-p" to the richtext program, and a followup general entry for "text/*" that uses a normal text paging program to view the mail. 1.21 TRN (Threaded News Reader) A metamail patch for TRN was contributed byJohn Schmitz as follows: *** ../trn2.2/common.h Fri Nov 20 11:10:25 1992 --- common.h Mon Nov 23 15:51:15 1992 *************** *** 521,526 **** --- 521,542 ---- # define UNSHAR "/bin/sh" #endif + #ifndef NOMETAMAIL + /* default MIME extraction program */ + # ifndef MIMESTORE + # define MIMESTORE "/usr/local/bin/mh/mhn -store -auto -file " + # endif + + /* default MIME show program */ + # ifndef MIMESHOW + # ifdef SERVER + # define MIMESHOW "metamail -e -p -m trn %P/rrn%a.%$" + # else + # define MIMESHOW "metamail -e -p -m trn %s %A" + # endif + # endif + #endif + /* path to default editor */ #ifndef DEFEDITOR # define DEFEDITOR "/usr/ucb/vi" *************** *** 679,684 **** --- 695,710 ---- # endif #endif + #ifndef NOMETAMAIL + # ifndef EXMIMESAVER + # ifdef SERVER + # define EXMIMESAVER "%e %P/rrn%a.%$" + # else + # define EXMIMESAVER "%e %A" + # endif + # endif + #endif + #ifndef NORMSAVER /* % and ~ */ # ifdef SERVER # define NORMSAVER "%X/norm.saver %P/rrn%a.%$ %P %c %a %B %C \"%b\"" *************** *** 940,945 **** --- 966,975 ---- EXT char nocd[] INIT("Can't chdir to directory %s\n"); #else EXT char nocd[] INIT("Can't find %s\n"); + #endif + + #ifndef NOMETAMAIL + EXT bool mime_article INIT(FALSE); #endif #ifdef NOLINEBUF *** ../trn2.2/head.h Mon Nov 25 09:49:12 1991 --- head.h Fri Nov 20 11:12:06 1992 *************** *** 34,39 **** --- 34,72 ---- #define ACAT_LINE 4 /* ACategory (ClariNet) */ #define ANPA_LINE 5 /* ANPA (ClariNet) */ #define CODES_LINE 6 /* Codes (ClariNet) */ + #ifndef NOMETAMAIL + #define CONTENT_LINE 7 /* MIME */ + #define DIST_LINE 8 /* distribution */ + #define DATE_LINE 9 /* date */ + #define RECEIVED_LINE 10 /* date-received */ + #define EXPIR_LINE 11 /* expires */ + #define FOLLOW_LINE 12 /* followup-to */ + #define FROM_LINE 13 /* from */ + #define FORM_LINE 14 /* Format (ClariNet) */ + #define KEYW_LINE 15 /* keywords */ + #define LINES_LINE 16 /* lines */ + #define MESSID_LINE 17 /* message-id */ + #define NFFR_LINE 18 /* nf-from */ + #define NFID_LINE 19 /* nf-id */ + #define NGS_LINE 20 /* newsgroups */ + #define NNTP_LINE 21 /* nntp-posting-host */ + #define NOTE_LINE 22 /* Note (ClariNet) */ + #define ORG_LINE 23 /* organization */ + #define PATH_LINE 24 /* path */ + #define POSTED_LINE 25 /* posted */ + #define PVER_LINE 26 /* posting-version */ + #define PRI_LINE 27 /* Priority (ClariNet) */ + #define REPLY_LINE 28 /* reply-to */ + #define REFS_LINE 29 /* references */ + #define RVER_LINE 30 /* relay-version */ + #define SENDER_LINE 31 /* sender */ + #define SUMRY_LINE 32 /* summary */ + #define SUBJ_LINE 33 /* subject */ + #define SLUG_LINE 34 /* Slugword (ClariNet) */ + #define XREF_LINE 35 /* xref */ + #define XSUP_LINE 36 /* X-Supersedes (ClariNet) */ + #define HEAD_LAST 37 /* one more than the last one above */ + #else /* NOMETAMAIL #define DIST_LINE 7 /* distribution */ #define DATE_LINE 8 /* date */ #define RECEIVED_LINE 9 /* date-received */ *************** *** 63,68 **** --- 96,102 ---- #define XREF_LINE 33 /* xref */ #define XSUP_LINE 34 /* X-Supersedes (ClariNet) */ #define HEAD_LAST 35 /* one more than the last one above */ + #endif NOMETAMAIL struct headtype { char *ht_name; /* header line identifier */ *************** *** 94,99 **** --- 128,136 ---- {"acategory", 0, 0, 9, HT_HIDE }, {"anpa", 0, 0, 4, HT_HIDE }, {"codes", 0, 0, 5, HT_HIDE }, + #ifndef NOMETAMAIL + {"content-type", 0, 0, 12, HT_MAGIC }, + #endif {"distribution", 0, 0, 12, 0 }, #ifdef USETHREADS {"date", 0, 0, 4, HT_MAGIC }, *************** *** 111,116 **** --- 148,156 ---- {"nf-from", 0, 0, 7, HT_HIDE }, {"nf-id", 0, 0, 5, HT_HIDE }, {"newsgroups", 0, 0, 10, HT_MAGIC|HT_HIDE}, + #ifndef NOMETAMAIL + {"nntp-posting-host", 0, 0, 17, HT_HIDE}, + #endif {"note", 0, 0, 4, 0, }, {"organization", 0, 0, 12, 0 }, {"path", 0, 0, 4, HT_HIDE }, *** ../trn2.2/respond.c Fri Nov 20 11:10:31 1992 --- respond.c Mon Nov 23 15:55:50 1992 *************** *** 66,72 **** --- 66,76 ---- #ifdef ASYNC_PARSE parse_maybe(art); #endif + #ifndef NOMETAMAIL + savefrom = (!mime_article && (cmd == 'w' || cmd == 'e') ? htype[PAST_HEADER].ht_minpos : 0); + #else savefrom = (cmd == 'w' || cmd == 'e' ? htype[PAST_HEADER].ht_minpos : 0); + #endif if (artopen(art) == Nullfp) { #ifdef VERBOSE IF(verbose) *************** *** 173,178 **** --- 177,202 ---- while(fgets(art_buf,LBUFLEN,artfp) != Nullch) { if (*art_buf <= ' ') continue; /* Ignore empty or initially-whitespace lines */ + #ifndef NOMETAMAIL + if (mime_article) { + if (!custom_extract) { + printf("Extracting MIME article into %s:\n", s) FLUSH; + extractprog = savestr(filexp(getval("MIMESTORE",MIMESTORE))); + } + else + printf("Extracting MIME article into %s using %s\n", + s, extractprog) FLUSH; + cnt = 0; + interp(cmd_buf,(sizeof cmd_buf),getval("EXMIMESAVER",EXMIMESAVER)); + termlib_reset(); + resetty(); /* restore tty state */ + doshell(SH,cmd_buf); + noecho(); /* revert to cbreaking */ + crmode(); + termlib_init(); + break; + } + #endif if (found_cut && custom_extract) { printf("Extracting data into %s using %s:\n", s, extractprog) FLUSH; *** ../trn2.2/art.c Fri Nov 20 11:10:23 1992 --- art.c Mon Nov 23 14:24:27 1992 *************** *** 78,84 **** --- 78,106 ---- ; } + #ifndef NOMETAMAIL + #define VERY_LONG_STRING 200 int + display_mime() + { + if (!getenv("NOMETAMAIL")) { + int code; + + interp(cmd_buf,(sizeof cmd_buf),getval("MIMESHOW",MIMESHOW)); + termlib_reset(); + resetty(); + code = doshell(SH,cmd_buf); + noecho(); + crmode(); + termlib_init(); + return(code); + } + else return(1); + } + #endif + + + int do_article() { register char *s; *************** *** 96,105 **** --- 118,134 ---- bool notesfiles = FALSE; /* might there be notesfiles junk? */ char oldmode = mode; char *ctime(); + #ifndef NOMETAMAIL + bool tried_display_mime = FALSE; + #endif + #ifdef INNERSEARCH register int outputok; #endif + #ifndef NOMETAMAIL + mime_article = FALSE; + #endif if (fstat(fileno(artfp),&filestat)) /* get article file stats */ *************** *** 287,292 **** --- 316,326 ---- localtime(&curr_p_art->date)); } #endif + #ifndef NOMETAMAIL + else if (in_header == CONTENT_LINE) { + mime_article = nontext(art_buf+14); + } + #endif } if (in_header == SUBJ_LINE && htype[SUBJ_LINE].ht_flags & HT_MAGIC) { *************** *** 360,365 **** --- 394,405 ---- } #endif else { /* just a normal line */ + #ifndef NOMETAMAIL + if (mime_article && ! tried_display_mime) + if (display_mime() == 0) + return DA_NORM; + else tried_display_mime = TRUE; + #endif if (highlight==artline) { /* this line to be highlit? */ if (marking == STANDOUT) { #ifdef NOFIREWORKS *************** *** 1021,1025 **** --- 1061,1109 ---- return TRUE; } return FALSE; + } + #endif + #ifndef NOMETAMAIL + nontext(content_type) + char *content_type; + { + char *t; + + if (content_type[0] == '\n') return(0); + while (content_type && isspace(*content_type)) ++content_type; + t = index(content_type, ';'); + if (!t) t = index(content_type, '\n'); + if (t) *t-- = NULL; + while (t && *t && t > content_type && isspace(*t)) *t-- = NULL; + if (notplain(content_type)) return(1); + return(0); + } + + notplain(s) + char *s; + { + char *t; + if (!s) return(1); + while (*s && isspace(*s)) ++s; + for(t=s; *t; ++t) if (isupper(*t)) *t = tolower(*t); + while (t > s && isspace(*--t)) {;} + if (((t-s) == 3) && !strncmp(s, "text", 4)) return(0); + if (strncmp(s, "text/plain", 10)) return(1); + t = (char *) index(s, ';'); + while (t) { + ++t; + while (*t && isspace(*t)) ++t; + if (!strncmp(t, "charset", 7)) { + s = (char *) index(t, '='); + if (s) { + ++s; + while (*s && isspace(*s)) ++s; + if (!strncmp(s, "us-ascii", 8)) return(0); + } + return(1); + } + t = (char *) index(t, ';'); + } + return(0); /* no charset, was text/plain */ } #endif 1.22 MM (Mail system from Columbia University) All of the changes are localized to the file more.c. In particular, there are two changes to be made: 1. In more.c, there is a routine called "display_message". In my version it starts on line 151. A few lines down in that routine, you'll find something that looks like this: if (use_crt_filter_always || (logical_lines (msg, cmcsb._cmrmx) +1 >= cmcsb._cmrmx)) { fp = more_pipe_open(out); } Immediately BEFORE that fragment (i.e. between line 167 and line 168, in my version) insert the following code: #ifndef NOMETAMAIL if (!nontext(msg) || (fp = mm_popen ("metamail -m MM -p -R", "w")) == NULL) #endif 2. At the very end of more.c, add the following new routines, which are used by the patch above: #ifndef NOMETAMAIL nontext(msg) char *msg; { char *s, *ct=NULL, *ctend; int LineStart=1; for (s=msg; *s; ++s) { if (LineStart) { if (*s == '\n') break; /* end of headers */ LineStart = 0; if (!lc2strncmp(s, "content-type:", 13)) { ct=s + 13; break; /* found it */ } } else if (*s == '\n') { LineStart = 1; } } if (!ct) return 0; ctend = ct; while (1) { ctend = index(ct, '\n'); if (!ctend) break;/* use whole thing */ if (*(ctend+1) == ' ' || *(ctend+1) == '\t') { ++ctend; /* keep looking */ } else { break; } } *ctend = NULL; if (notplain(ct)) { *ctend = '\n'; return(1); } *ctend = '\n'; return(0); } notplain(s) char *s; { char *t; if (!s) return(1); while (*s && isspace(*s)) ++s; for(t=s; *t; ++t) if (isupper(*t)) *t = tolower(*t); while (t > s && isspace(*--t)) {;} if (((t-s) == 3) && !strncmp(s, "text", 4)) return(0); if (strncmp(s, "text/plain", 10)) return(1); t = (char *) index(s, ';'); while (t) { ++t; while (*t && isspace(*t)) ++t; if (!strncmp(t, "charset", 7)) { s = (char *) index(t, '='); if (s) { ++s; while (*s && isspace(*s)) ++s; if (!strncmp(s, "us-ascii", 8)) return(0); } return(1); } t = (char *) index(t, ';'); } return(0); /* no charset, was text/plain */ } lc2strncmp(s1, s2, len) char *s1, *s2; int len; { if (!s1 || !s2) return (-1); while (*s1 && *s2 && len > 0) { if (*s1 != *s2 && (tolower(*s1) != *s2)) return(-1); ++s1; ++s2; --len; } if (len <= 0) return(0); return((*s1 == *s2) ? 0 : -1); } #endif These two changes should be all you need to do in order to make MM work with metamail, assuming that you already have the "metamail" executable installed somewhere on your search path.