--- /dev/null
+Jon Steinhart's (jon@fourwinds.com) Attachment Handling Mods
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A bunch of changes have been made to improve the nmh user interface for
+handling MIME attachments.
+
+Why Did I Do This?
+~~~~~~~~~~~~~~~~~~
+
+Although nmh contains the necessary functionality for MIME message handing,
+the interface to this functionality is pretty obtuse. There's no way that
+I'm ever going to convince my partner to write mhbuild composition files!
+And even though I know how to write them, I often screw up when sending a
+message that contains a shell script because I forget that I have to double
+any # at the start of a line, which shell scripts have galore.
+
+These changes simplify the task of managing attachments on draft files.
+They allow attachments to be added, listed, and deleted. MIME messages are
+automatically created when drafts with attachments are sent.
+
+The Simple Setup
+~~~~~~~~~~~~~~~~
+
+The gory details are all given below. Here's how to set things up simply. This
+only works if you're using the "standard" interface, i.e., whatnow. Life is more
+complicated if you run mh-e.
+
+Add the following to your .mh_profile:
+
+ send: -attach X-MH-Attachment
+ whatnow: -attach X-MH-Attachment
+
+You may already have send and whatnow lines in your .mh_profile; if you do, just add
+the new stuff to the end of the line. For example, mine looks like:
+
+ send: -alias aliases -attach X-MH-Attachment
+ whatnow: -attach X-MH-Attachment
+
+That's it. Next time you want to send an attachment, say "attach filename" at the
+whatnow prompt and you're done. You can check your attachments by saying "alist".
+
+Did I Do This Correctly?
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Hard to say. Despite lots of time looking at the nmh code, I can't say that
+I get the philosophy behind its structure.
+
+I am aware of two deviations from what I saw in the nmh code.
+
+ 1. I commented my changes.
+
+ 2. It's been years since I've used a VT-100, so I don't try to make code
+ fit into 80 columns anymore. Seems silly to me.
+
+What Did I Do?
+~~~~~~~~~~~~~~
+
+I made changes to the following files:
+
+ h/
+ prototypes.h
+ man/
+ anno.man
+ send.man
+ whatnow.man
+ uip/
+ Makefile.in
+ anno.c
+ annosbr.c
+ send.c
+ sendsbr.c
+ viamail.c (needed change for new sendsbr argument)
+ whatnowsbr.c
+
+Attachments are associated with messages using header fields. For example, a
+draft that looks like this
+
+ To: jon
+ Subject: test of attachments
+ X-MH-Attachment: /export/home/jon/foo
+ X-MH-Attachment: /export/home/jon/bar
+ X-MH-Attachment: /export/home/jon/test/foo
+ --------
+
+has the files "foo", "bar", and foo as attachments.
+
+Although I use the header field name "X-MH-Attachment" to indicate
+attachments, the implementation allows any header field name.
+
+The advantage of using header fields is that the list of attachments
+travels with the draft so it remains valid across editing sessions.
+
+Note that the header fields for attachments are removed from the message
+before it is sent.
+
+Since I was adding header fields to messages, it seemed sensible to use the
+existing anno program to do this for me. This required several changes to
+generalize anno:
+
+ o I added a -draft option that permits annotations (header fields) to
+ be added to the draft instead of a message sequence.
+
+ o I added a -delete option that allows annotations to be deleted.
+
+ o I added a -list option that allows annotations to be listed.
+
+ o I added a -number option that modifies the behavior of -delete and -list.
+
+ o I added a -append option so that annotations are appended to the headers
+ instead of the default prepend. Without this, attachments come out in
+ reverse order.
+
+Using the modified anno, the example above could be created (assuming that the
+draft exists) by
+
+ prompt% anno -draft -comp X-MH-Attachment -text /export/home/jon/foo -nodate -append
+ prompt% anno -draft -comp X-MH-Attachment -text /export/home/jon/bar -nodate -append
+ prompt% anno -draft -comp X-MH-Attachment -text /export/home/jon/test/foo -nodate -append
+
+One can quite easily make an "attach" command using shell scripts, aliases or functions.
+For example, here's a bash function that does the job:
+
+ function attach() { for i in $*; do anno -append -nodate -draft -comp X-MH-Attachment -text "$i"; done; }
+
+The following examples show the different ways in which attachments can be
+listed.
+
+ prompt% anno -list -draft -comp X-MH-Attachment
+ foo
+ bar
+ foo
+
+ prompt% anno -list -draft -comp X-MH-Attachment -text /
+ /export/home/jon/foo
+ /export/home/jon/bar
+ /export/home/jon/test/foo
+
+ prompt% anno -list -draft -comp X-MH-Attachment -number
+ 1 foo
+ 2 bar
+ 3 foo
+
+ prompt% anno -list -draft -comp X-MH-Attachment -text / -number
+ 1 /export/home/jon/foo
+ 2 /export/home/jon/bar
+ 3 /export/home/jon/test/foo
+
+ prompt%
+
+Why all these listing options?
+
+I feel that the listing as produced by the first example is what most people
+would want most of the time.
+
+The listing as produced by the second example seemed necessary for situations
+where there were several attachments with the same file name in different
+directories.
+
+I included the numbering option so that attachments could be deleted by number
+which might be easier in situations where there were several attachments with
+the same file name in different directories, as in the above example.
+
+Attachments are deleted using the -delete option.
+
+ prompt% anno -delete -draft -comp X-MH-Attachment -text foo
+
+deletes the first attachment since the foo matches the basename of the attachment
+name.
+
+ prompt% anno -delete -draft -comp X-MH-Attachment -text /export/home/jon/test/foo
+
+deletes the third attachment since the text is a full path name and matches.
+
+ prompt% anno -delete -draft -comp X-MH-Attachment -number 2
+
+deletes the second attachment.
+
+The attachment annotations are converted to a MIME message by send. I'm not
+completely sure that this is the right place to do it, as opposed to doing
+it in post, but both would work. It seemed to me to make more sense to do
+it in send so that all of the various post options would apply to the MIME
+message instead of the original draft file.
+
+I added an -attach option to send that specifies the header field name used
+for attachments. Send performs the following additional steps if this option
+is set:
+
+ o It scans the header of the draft for attachments. Normal behavior applies
+ if none exist.
+
+ o A temporary mhbuild composition file is created if there are attachments.
+
+ o All non-attachment headers are copied from the draft file to the
+ composition file.
+
+ o The body of the draft is copied to a temporary body file if it contains at
+ least one non-whitespace character. A mhbuild directive for this file is
+ appended to the composition file. Note that this eliminates the problem
+ of lines beginning with the # character in the message body.
+
+ o A mhbuild directive is appended to the composition file for each attachment
+ header.
+
+ o mhbuild is run on the composition file, converting it to a MIME message.
+
+ o The converted composition file is substituted for the original draft file
+ and run through the rest of send.
+
+ o The original draft file is renamed instead of the converted composition
+ file. This preserves the original message instead of the composition file
+ which is really what a user would want.
+
+ o The ,file.orig file created by mhbuild is removed as it's a nuisance.
+
+The mhbuild directives appended to the composition file are constructed as
+follows:
+
+ o The content-type a file with a dot-suffix is obtained from the list of
+ mhshow-suffix- entries in the profile.
+
+ o A file with no dot-suffix or no entry in the profile is assigned a
+ content-type of application/octet-stream if it contains any non-ASCII
+ characters.
+
+ o A file with no dot-suffix or no entry in the profile is assigned a
+ content-type of text/plain if it contains only ASCII characters.
+
+ o The directive is of the form:
+
+ #content-type; name="basename"; x-unix-mode=mode [ description ] filename
+
+ The content type is derived as discussed above. The basename is the
+ last component of the pathname given in the body of the attachment header
+ field. The mode is file permissions. The description is obtained by
+ running the file command on the attachment file. The filename is the
+ field body from the attachment header field.
+
+I added an -attach option to whatnow that specifies the header field name for
+attachments.
+
+I added to the commands available at the whatnow prompt to provide an interface
+to the attachment mechanism.
+
+I'm not completely happy with some of these additions because they duplicate
+shell functionality. I'm not sure that there's a good way around it other than
+to not use whatnow.
+
+The first three additions (the ones I'm not happy with) are cd, ls, and pwd.
+These do the same thing as their system counterparts. As a matter of fact,
+these are implemented by running the commands in a subshell. I did this because
+I wanted proper interpretation of shell-specific things like ~ and wildcard
+expansion.
+
+The next command is attach. This takes a list of files and adds them to the draft
+as attachments using the same code as the modified anno. The list of files is
+run through ls using the user's shell, so wildcard expansion and all that works.
+
+The alist command lists the attachments on the current draft using listing function
+that I added to anno. It takes two optional options, -l for a long listing meaning
+full path names, and -n for a numbered listing.
+
+The detach command removes attachments from the current draft, again using the
+modified anno. The arguments are interpreted as numbers if the -n option is used,
+otherwise they're interpreted as file names. File names are shoveled through ls
+using the user's shell in the directory containing the file for wildcard expansion
+and such. File names are matched against the last pathname component unless they
+begin with a / in which case they're matched against the entire name.
+
+What's Left To Do?
+~~~~~~~~~~~~~~~~~~
+
+Nothing on this stuff. When I get time I'd like to improve the interface
+for reading messages with attachments. It's my opinion that, because of the
+command line nature of nmh, it makes the most sense to treat attachments as
+separate messages. In other words one should be able to read the next
+attachment using next, and the previous one using prev. One should be able
+to show and scan attachments. This would probably involve a major change
+in the message numbering scheme to allow something like 123.4 to indicate
+attachment 4 on message 123.
+
+ Jon Steinhart
/*
* prototypes for some routines in uip
*/
-int annotate (char *, char *, char *, int, int);
+int annotate (char *, char *, char *, int, int, int, int);
+void annolist(char *, char *, char *, int);
int distout (char *, char *, char *);
void replout (FILE *, char *, char *, struct msgs *, int,
int, char *, char *, char *);
-int sendsbr (char **, int, char *, struct stat *, int);
+int sendsbr (char **, int, char *, struct stat *, int, char *);
int what_now (char *, int, int, char *, char *,
int, struct msgs *, char *, int, char *);
.IR field ]
.RB [ \-inplace " | " \-noinplace ]
.RB [ \-date " | " \-nodate ]
+.RB [ \-draft ]
+.RB [ \-append ]
+.RB [ \-list ]
+.RB [ \-delete ]
+.RB [ \-number
+.IR [ num ]]
.RB [ \-version ]
.RB [ \-help ]
.RB [ \-text
.ad
.SH DESCRIPTION
.B Anno
-annotates the specified messages in the named folder using
-the field and body.
+manipulates header fields or
+.I annotations
+in messages.
+Header fields consist of a field name and an optional field body
+as defined by RFC-2822.
+The
+.B -component
+option specifies the field name, and the
+.B -text
+option specifies the field body.
+.PP
+The messages are either the
+.I msgs
+in the named folder, or the draft if invoked with the
+.B -draft
+option.
.PP
Usually, annotation is performed by the commands
.BR dist ,
switch inhibits the date annotation, leaving only the
body annotation.
.PP
+By default,
+.B anno
+prepends the annotations to the message.
+Annotations are instead appended if the
+.B -append
+option is specified.
+.PP
If a
.B \-component
.I field
.B anno
will prompt the user for the name of field for the annotation.
.PP
-The field specified should be a valid 822-style message field name,
-which means that it should consist of alphanumerics (or dashes) only.
+The field specified must be a valid 2822-style message field name,
+which means that it may only consist of alphanumerics and dashes,
The body specified is arbitrary text.
.PP
Normally
any links to the message. You may change this by using the
.B \-noinplace
switch.
-
+.PP
+The
+.B -list
+option produces a listing of the field bodies for header fields with
+names matching the specified component, one per line.
+The listing is numbered, starting at 1, if the
+.B -number
+option is also used.
+A tab character separates the number and the field body.
+The field body is treated as if it is a file name, and only the final
+path name component is listed.
+The complete field body is listed if the
+.B -text
+option is used, the contents of the text are ignored.
+.PP
+The
+.B -delete
+option removes header fields from messages.
+The first header field whose name matches the component is deleted if
+no other options are specified.
+If the
+.B -text
+option is used in conjunction with the
+.B -delete
+option, the first header field whose name matches the component and
+whose body matches the text is deleted.
+The text is treated as if it was a file name; if it begins with a
+slash, the entire field body must match the text, otherwise just the
+last path name component of the field body must match.
+If the
+.B -number
+option is used in conjuction with the
+.B -delete
+option, header field
+.I num
+whose name matches the component is deleted.
+The number matches that which is produced by the
+.B -list
+option.
.SH FILES
.fc ^ ~
.nf
\&...]
.RB [ \-version ]
.RB [ \-help ]
+.RB [ \-attach
+.IR header-field-name ]
.ad
.SH DESCRIPTION
.B Send
.B send
are actually performed by
.BR post .
+
+.PP
+If a
+.I header-field-name
+is supplied using the
+.B -attach
+option, the draft is scanned for a header whose field name matches the
+supplied
+.IR header-field-name .
+The draft is converted to a MIME message if one or more matches are found.
+This conversion occurs before all other processing.
+.PP
+The first part of the MIME message is the draft body if that body contains
+any non-blank characters.
+The body of each header field whose name matches the
+.I header-field-name
+is interpreted as a file name, and each file named is included as a separate
+part in the MIME message.
+.PP
+For file names with dot suffixes, the context is scanned for a
+.I mhshow-suffix-
+entry for that suffix.
+The content-type for the part is taken from that context entry if a match is
+found.
+If no match is found or the file does not have a dot suffix, the content-type
+is text/plain if the file contains only ASCII characters or application/octet-stream
+if it contains characters outside of the ASCII range.
+.PP
+Each part contains a name attribute that is the last component of the path name.
+A
+.I x-unix-mode
+attribute containing the file mode accompanies each part.
+Finally, a description attribute is generated by running the
+.I file
+command on the file.
.PP
If
.B \-push
.RI [ file ]
.RB [ \-version ]
.RB [ \-help ]
+.RB [ \-attach
+.IR header-field-name ]
.ad
.SH DESCRIPTION
.B Whatnow
and awaits a response. The valid responses are:
.PP
.RS 5
-.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
.B edit
re\-edit using the same editor that was used on the
preceding round unless a profile entry
\*(lq<lasteditor>\-next: <editor>\*(rq names an alternate editor
-.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
.B edit <editor>
invoke <editor> for further editing
-.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
.B refile +folder
refile the draft into the given folder
-.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
.B mime
process the draft as MIME composition file using
the
command
.RB ( mhbuild
by default)
-.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
.B display
list the message being distributed/replied\-to
on the terminal
-.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
.B list
list the draft on the terminal
-.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
.B send
send the message
-.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
.B send \-watch
send the message and monitor the delivery process
-.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
.B push
send the message in the background
-.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
.B whom
list the addresses that the message will go to
-.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
.B whom \-check
list the addresses and verify that they are
acceptable to the transport service
-.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
.B quit
preserve the draft and exit
-.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
.B quit \-delete
delete the draft and exit
-.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
.B delete
delete the draft and exit
+.TP \w'refilezzzzfolderz'u
+.B cd directory
+use the directory when interpreting attachment file names
+.TP \w'refilezzzzfolderz'u
+.B pwd
+print the working directory for attachment files
+.TP \w'refilezzzzfolderz'u
+.B ls [ls-options]
+list files in the attachment working directory using the ls command
+.TP \w'refilezzzzfolderz'u
+.B attach files
+add the named files to the draft as MIME attachments
+.TP \w'refilezzzzfolderz'u
+.B alist [-ln]
+list the MIME attachments, either short, long [-l] or numbered [-n]
+.TP \w'refilezzzzfolderz'u
+.B detach [-n] files-or-numbers
+remove MIME attachments, either by file name or by number with -n
.RE
.PP
When entering your response, you need only type enough characters
* This code is Copyright (c) 2002, by the authors of nmh. See the
* COPYRIGHT file in the root directory of the nmh distribution for
* complete copyright information.
+ *
+ * Four new options have been added: delete, list, number, and draft.
+ * Message header fields are used by the new MIME attachment code in
+ * the send command. Adding features to generalize the anno command
+ * seemed to be a better approach than the creation of a new command
+ * whose features would overlap with those of the anno command.
+ *
+ * The -draft option causes anno to operate on the current draft file
+ * instead of on a message sequence.
+ *
+ * The -delete option deletes header elements that match the -component
+ * field name. If -delete is used without the -text option, the first
+ * header field whose field name matches the component name is deleted.
+ * If the -delete is used with the -text option, and the -text argument
+ * begins with a /, the first header field whose field name matches the
+ * component name and whose field body matches the text is deleted. If
+ * the -text argument does not begin with a /, then the text is assumed
+ * to be the last component of a path name, and the first header field
+ * whose field name matches the component name and a field body whose
+ * last path name component matches the text is deleted. If the -delete
+ * option is used with the new -number option described below, the nth
+ * header field whose field name matches the component name is deleted.
+ * No header fields are deleted if none of the above conditions are met.
+ *
+ * The -list option outputs the field bodies from each header field whose
+ * field name matches the component name, one per line. If no -text
+ * option is specified, only the last path name component of each field
+ * body is output. The entire field body is output if the -text option
+ * is used; the contents of the -text argument are ignored. If the -list
+ * option is used in conjuction with the new -number option described
+ * below, each line is numbered starting with 1. A tab separates the
+ * number from the field body.
+ *
+ * The -number option works with both the -delete and -list options as
+ * described above. The -number option takes an optional argument. A
+ * value of 1 is assumed if this argument is absent.
*/
#include <h/mh.h>
{ "version", 0 },
#define HELPSW 7
{ "help", 0 },
+#define DRFTSW 8
+ { "draft", 2 },
+#define LISTSW 9
+ { "list", 1 },
+#define DELETESW 10
+ { "delete", 2 },
+#define NUMBERSW 11
+ { "number", 2 },
+#define APPENDSW 12
+ { "append", 1 },
{ NULL, 0 }
};
char *text = NULL, *folder = NULL, buf[BUFSIZ];
char **argp, **arguments, **msgs;
struct msgs *mp;
+ int append = 0; /* append annotations instead of default prepend */
+ int delete = -1; /* delete header element if set */
+ char *draft = (char *)0; /* draft file name */
+ int isdf = 0; /* return needed for m_draft() */
+ int list = 0; /* list header elements if set */
+ int number = 0; /* delete specific number of like elements if set */
#ifdef LOCALE
setlocale(LC_ALL, "");
while ((cp = *argp++)) {
if (*cp == '-') {
switch (smatch (++cp, switches)) {
- case AMBIGSW:
+ case AMBIGSW:
ambigsw (cp, switches);
done (1);
- case UNKWNSW:
+ case UNKWNSW:
adios (NULL, "-%s unknown", cp);
- case HELPSW:
+ case HELPSW:
snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]",
invo_name);
print_help (buf, switches, 1);
print_version(invo_name);
done (1);
- case COMPSW:
+ case COMPSW:
if (comp)
adios (NULL, "only one component at a time!");
if (!(comp = *argp++) || *comp == '-')
adios (NULL, "missing argument to %s", argp[-2]);
continue;
- case DATESW:
+ case DATESW:
datesw++;
continue;
- case NDATESW:
+ case NDATESW:
datesw = 0;
continue;
- case INPLSW:
+ case INPLSW:
inplace++;
continue;
- case NINPLSW:
+ case NINPLSW:
inplace = 0;
continue;
- case TEXTSW:
+ case TEXTSW:
if (text)
adios (NULL, "only one body at a time!");
if (!(text = *argp++) || *text == '-')
adios (NULL, "missing argument to %s", argp[-2]);
continue;
+
+ case DELETESW: /* delete annotations */
+ delete = 0;
+ continue;
+
+ case DRFTSW: /* draft message specified */
+ draft = "";
+ continue;
+
+ case LISTSW: /* produce a listing */
+ list = 1;
+ continue;
+
+ case NUMBERSW: /* number listing or delete by number */
+ if (number != 0)
+ adios (NULL, "only one number at a time!");
+
+ if (argp - arguments == argc - 1 || **argp == '-')
+ number = 1;
+
+ else if (!(number = atoi(*argp++)))
+ adios (NULL, "missing argument to %s", argp[-2]);
+
+ delete = number;
+ continue;
+
+ case APPENDSW: /* append annotations instead of default prepend */
+ append = 1;
+ continue;
}
}
if (*cp == '+' || *cp == '@') {
}
}
+ /*
+ * We're dealing with the draft message instead of message numbers.
+ * Get the name of the draft and deal with it just as we do with
+ * message numbers below.
+ */
+
+ if (draft != (char *)0) {
+ if (nummsgs != 0)
+ adios(NULL, "can only have message numbers or -draft.");
+
+ draft = getcpy(m_draft(folder, (char *)0, 1, &isdf));
+
+ make_comp(&comp);
+
+ if (list)
+ annolist(draft, comp, text, number);
+ else
+ annotate (draft, comp, text, inplace, datesw, delete, append);
+
+ return (done(0));
+ }
+
#ifdef UCI
if (strcmp(invo_name, "fanno") == 0) /* ugh! */
datesw = 0;
make_comp (&comp);
/* annotate all the SELECTED messages */
- for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++)
- if (is_selected(mp, msgnum))
- annotate (m_name (msgnum), comp, text, inplace, datesw);
+ for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
+ if (is_selected(mp, msgnum)) {
+ if (list)
+ annolist(m_name(msgnum), comp, text, number);
+ else
+ annotate (m_name (msgnum), comp, text, inplace, datesw, delete, append);
+ }
+ }
context_replace (pfolder, folder); /* update current folder */
seq_setcur (mp, mp->lowsel); /* update current message */
return done (0);
}
-
static void
make_comp (char **ap)
{
if (!isalnum (*cp) && *cp != '-')
adios (NULL, "invalid component name %s", *ap);
}
-
/*
* static prototypes
*/
-static int annosbr (int, char *, char *, char *, int, int);
+static int annosbr (int, char *, char *, char *, int, int, int, int);
int
-annotate (char *file, char *comp, char *text, int inplace, int datesw)
+annotate (char *file, char *comp, char *text, int inplace, int datesw, int delete, int append)
{
int i, fd;
/* open and lock the file to be annotated */
if ((fd = lkopen (file, O_RDWR, 0)) == NOTOK) {
switch (errno) {
- case ENOENT:
+ case ENOENT:
break;
- default:
+ default:
admonish (file, "unable to lock and open");
break;
}
return 1;
}
- i = annosbr (fd, file, comp, text, inplace, datesw);
+ i = annosbr (fd, file, comp, text, inplace, datesw, delete, append);
lkclose (fd, file);
return i;
}
+/*
+ * Produce a listing of all header fields (annotations) whose field name matches
+ * comp. Number the listing if number is set. Treate the field bodies as path
+ * names and just output the last component unless text is non-NULL. We don't
+ * care what text is set to.
+ */
+
+void
+annolist(char *file, char *comp, char *text, int number)
+{
+ int c; /* current character */
+ int count; /* header field (annotation) counter */
+ char *cp; /* miscellaneous character pointer */
+ char *field; /* buffer for header field */
+ int field_size; /* size of field buffer */
+ FILE *fp; /* file pointer made from locked file descriptor */
+ int length; /* length of field name */
+ int n; /* number of bytes written */
+ char *sp; /* another miscellaneous character pointer */
+
+ if ((fp = fopen(file, "r")) == (FILE *)0)
+ adios(file, "unable to open");
+
+ /*
+ * Allocate a buffer to hold the header components as they're read in.
+ * This buffer might need to be quite large, so we grow it as needed.
+ */
+
+ if ((field = (char *)malloc(field_size = 256)) == (char *)0)
+ adios(NULL, "can't allocate field buffer.");
+
+ /*
+ * Get the length of the field name since we use it often.
+ */
+
+ length = strlen(comp);
+
+ count = 0;
+
+ do {
+ /*
+ * Get a line from the input file, growing the field buffer as needed. We do this
+ * so that we can fit an entire line in the buffer making it easy to do a string
+ * comparison on both the field name and the field body which might be a long path
+ * name.
+ */
+
+ for (n = 0, cp = field; (c = getc(fp)) != EOF; *cp++ = c) {
+ if (c == '\n' && (c = getc(fp)) != ' ' && c != '\t') {
+ (void)ungetc(c, fp);
+ c = '\n';
+ break;
+ }
+
+ if (++n >= field_size - 1) {
+ if ((field = (char *)realloc((void *)field, field_size += 256)) == (char *)0)
+ adios(NULL, "can't grow field buffer.");
+
+ cp = field + n - 1;
+ }
+ }
+
+ /*
+ * NUL-terminate the field..
+ */
+
+ *cp = '\0';
+
+ if (strncasecmp(field, comp, length) == 0 && field[length] == ':') {
+ for (cp = field + length + 1; *cp == ' ' || *cp == '\t'; cp++)
+ ;
+
+ if (number)
+ (void)printf("%d\t", ++count);
+
+ if (text == (char *)0 && (sp = strrchr(cp, '/')) != (char *)0)
+ cp = sp + 1;
+
+ (void)printf("%s\n", cp);
+ }
+
+ } while (*field != '\0' && *field != '-');
+
+ /*
+ * Clean up.
+ */
+
+ free(field);
+
+ (void)fclose(fp);
+
+ return;
+}
+
static int
-annosbr (int fd, char *file, char *comp, char *text, int inplace, int datesw)
+annosbr (int fd, char *file, char *comp, char *text, int inplace, int datesw, int delete, int append)
{
int mode, tmpfd;
char *cp, *sp;
char buffer[BUFSIZ], tmpfil[BUFSIZ];
struct stat st;
- FILE *tmp;
+ FILE *tmp;
+ int c; /* current character */
+ int count; /* header field (annotation) counter */
+ char *field; /* buffer for header field */
+ int field_size; /* size of field buffer */
+ FILE *fp; /* file pointer made from locked file descriptor */
+ int length; /* length of field name */
+ int n; /* number of bytes written */
mode = fstat (fd, &st) != NOTOK ? (st.st_mode & 0777) : m_gmprot ();
}
chmod (tmpfil, mode);
- if (datesw)
- fprintf (tmp, "%s: %s\n", comp, dtimenow (0));
- if ((cp = text)) {
+ /*
+ * We're going to need to copy some of the message file to the temporary
+ * file while examining the contents. Convert the message file descriptor
+ * to a file pointer since it's a lot easier and more efficient to use
+ * stdio for this. Also allocate a buffer to hold the header components
+ * as they're read in. This buffer is grown as needed later.
+ */
+
+ if (delete >= 0 || append != 0) {
+ if ((fp = fdopen(fd, "r")) == (FILE *)0)
+ adios(NULL, "unable to fdopen file.");
+
+ if ((field = (char *)malloc(field_size = 256)) == (char *)0)
+ adios(NULL, "can't allocate field buffer.");
+ }
+
+ /*
+ * We're trying to delete a header field (annotation )if the delete flag is
+ * non-negative. A value greater than zero means that we're deleting the
+ * nth header field that matches the field (component) name. A value of
+ * zero means that we're deleting the first field in which both the field
+ * name matches the component name and the field body matches the text.
+ * The text is matched in its entirety if it begins with a slash; otherwise
+ * the text is matched against whatever portion of the field body follows
+ * the last slash. This allows matching of both absolute and relative path
+ * names. This is because this functionality was added to support attachments.
+ * It might be worth having a separate flag to indicate path name matching to
+ * make it more general.
+ */
+
+ if (delete >= 0) {
+ /*
+ * Get the length of the field name since we use it often.
+ */
+
+ length = strlen(comp);
+
+ /*
+ * Initialize the field counter. This is only used if we're deleting by
+ * number.
+ */
+
+ count = 0;
+
+ /*
+ * Copy lines from the input file to the temporary file until we either find the one
+ * that we're looking for (which we don't copy) or we reach the end of the headers.
+ * Both a blank line and a line beginning with a - terminate the headers so that we
+ * can handle both drafts and RFC-2822 format messages.
+ */
+
do {
- while (*cp == ' ' || *cp == '\t')
- cp++;
- sp = cp;
- while (*cp && *cp++ != '\n')
- continue;
- if (cp - sp)
- fprintf (tmp, "%s: %*.*s", comp, cp - sp, cp - sp, sp);
- } while (*cp);
- if (cp[-1] != '\n' && cp != text)
- putc ('\n', tmp);
+ /*
+ * Get a line from the input file, growing the field buffer as needed. We do this
+ * so that we can fit an entire line in the buffer making it easy to do a string
+ * comparison on both the field name and the field body which might be a long path
+ * name.
+ */
+
+ for (n = 0, cp = field; (c = getc(fp)) != EOF; *cp++ = c) {
+ if (c == '\n' && (c = getc(fp)) != ' ' && c != '\t') {
+ (void)ungetc(c, fp);
+ c = '\n';
+ break;
+ }
+
+ if (++n >= field_size - 1) {
+ if ((field = (char *)realloc((void *)field, field_size *= 2)) == (char *)0)
+ adios(NULL, "can't grow field buffer.");
+
+ cp = field + n - 1;
+ }
+ }
+
+ /*
+ * NUL-terminate the field..
+ */
+
+ *cp = '\0';
+
+ /*
+ * Check for a match on the field name. We delete the line by not copying it to the
+ * temporary file if
+ *
+ * o The delete flag is 0, meaning that we're going to delete the first matching
+ * field, and the text is NULL meaning that we don't care about the field body.
+ *
+ * o The delete flag is 0, meaning that we're going to delete the first matching
+ * field, and the text begins with a / meaning that we're looking for a full
+ * path name, and the text matches the field body.
+ *
+ * o The delete flag is 0, meaning that we're going to delete the first matching
+ * field, the text does not begin with a / meaning that we're looking for the
+ * last path name component, and the last path name component matches the text.
+ *
+ * o The delete flag is non-zero meaning that we're going to delete the nth field
+ * with a matching field name, and this is the nth matching field name.
+ */
+
+ if (strncasecmp(field, comp, length) == 0 && field[length] == ':') {
+ if (delete == 0) {
+ if (text == (char *)0)
+ break;
+
+ for (cp = field + length + 1; *cp == ' ' || *cp == '\t'; cp++)
+ ;
+
+ if (*text == '/') {
+ if (strcmp(cp, text) == 0)
+ break;
+ }
+ else {
+ if ((sp = strrchr(cp, '/')) != (char *)0)
+ cp = sp + 1;
+
+ if (strcmp(cp, text) == 0)
+ break;
+ }
+ }
+
+ else if (++count == delete)
+ break;
+ }
+
+ /*
+ * This line wasn't a match so copy it to the temporary file.
+ */
+
+ if ((n = fputs(field, tmp)) == EOF || (c == '\n' && fputc('\n', tmp) == EOF))
+ adios(NULL, "unable to write temporary file.");
+
+ } while (*field != '\0' && *field != '-');
+
+ /*
+ * Get rid of the field buffer because we're done with it.
+ */
+
+ free((void *)field);
+ }
+
+ else {
+ /*
+ * Find the end of the headers before adding the annotations if we're
+ * appending instead of the default prepending.
+ */
+
+ if (append) {
+ /*
+ * Copy lines from the input file to the temporary file until we
+ * reach the end of the headers.
+ */
+
+ while ((c = getc(fp)) != EOF) {
+ (void)putc(c, tmp);
+
+ if (c == '\n') {
+ (void)ungetc(c = getc(fp), fp);
+
+ if (c == '\n' || c == '-')
+ break;
+ }
+ }
+ }
+
+ if (datesw)
+ fprintf (tmp, "%s: %s\n", comp, dtimenow (0));
+ if ((cp = text)) {
+ do {
+ while (*cp == ' ' || *cp == '\t')
+ cp++;
+ sp = cp;
+ while (*cp && *cp++ != '\n')
+ continue;
+ if (cp - sp)
+ fprintf (tmp, "%s: %*.*s", comp, cp - sp, cp - sp, sp);
+ } while (*cp);
+ if (cp[-1] != '\n' && cp != text)
+ putc ('\n', tmp);
+ }
}
+
fflush (tmp);
+
+ /*
+ * We've been messing with the input file position. Move the input file
+ * descriptor to the current place in the file because the stock data
+ * copying routine uses the descriptor, not the pointer.
+ */
+
+ if (append || delete >= 0) {
+ if (lseek(fd, (off_t)ftell(fp), SEEK_SET) == (off_t)-1)
+ adios(NULL, "can't seek.");
+ }
+
cpydata (fd, fileno (tmp), file, tmpfil);
fclose (tmp);
if (inplace) {
if ((tmpfd = open (tmpfil, O_RDONLY)) == NOTOK)
adios (tmpfil, "unable to open for re-reading");
+
lseek (fd, (off_t) 0, SEEK_SET);
+
+ /*
+ * We're making the file shorter if we're deleting a header field
+ * so the file has to be truncated or it will contain garbage.
+ */
+
+ if (delete >= 0 && ftruncate(fd, 0) == -1)
+ adios(tmpfil, "unable to truncate.");
+
cpydata (tmpfd, fd, tmpfil, file);
close (tmpfd);
unlink (tmpfil);
}
}
+ /*
+ * Close the delete file so that we don't run out of file pointers if
+ * we're doing piles of files. Note that this will make the close() in
+ * lkclose() fail, but that failure is ignored so it's not a problem.
+ */
+
+ if (delete >= 0)
+ (void)fclose(fp);
+
return 0;
}
{ "saslmech", SASLminc(-5) },
#define USERSW 39
{ "user", SASLminc(-4) },
+#define ATTACHSW 40
+ { "attach", 6 },
{ NULL, 0 }
};
char *msgs[MAXARGS], *vec[MAXARGS];
struct msgs *mp;
struct stat st;
+ char *attach = (char *)0; /* header field name for attachments */
#ifdef UCI
FILE *fp;
#endif /* UCI */
adios (NULL, "missing argument to %s", argp[-2]);
vec[vecp++] = cp;
continue;
+
+ case ATTACHSW:
+ if (!(attach = *argp++) || *attach == '-')
+ adios (NULL, "missing argument to %s", argp[-2]);
+ continue;
}
} else {
msgs[msgp++] = cp;
closefds (3);
for (msgnum = 0; msgnum < msgp; msgnum++) {
- switch (sendsbr (vec, vecp, msgs[msgnum], &st, 1)) {
+ switch (sendsbr (vec, vecp, msgs[msgnum], &st, 1, attach)) {
case DONE:
done (++status);
case NOTOK:
static int armed = 0;
static jmp_buf env;
+static char body_file_name[MAXPATHLEN + 1]; /* name of temporary file for body content */
+static char composition_file_name[MAXPATHLEN + 1]; /* name of mhbuild composition temporary file */
+static int field_size; /* size of header field buffer */
+static char *field; /* header field buffer */
+static FILE *draft_file; /* draft file pointer */
+static FILE *body_file; /* body file pointer */
+static FILE *composition_file; /* composition file pointer */
+
/*
* external prototypes
*/
-int sendsbr (char **, int, char *, struct stat *, int);
+int sendsbr (char **, int, char *, struct stat *, int, char *);
int done (int);
char *getusername (void);
static int splitmsg (char **, int, char *, struct stat *, int);
static int sendaux (char **, int, char *, struct stat *);
+static int attach(char *, char *);
+static void clean_up_temporary_files(void);
+static int get_line(void);
+static void make_mime_composition_file_entry(char *);
+
/*
* Entry point into (back-end) routines to send message.
*/
int
-sendsbr (char **vec, int vecp, char *drft, struct stat *st, int rename_drft)
+sendsbr (char **vec, int vecp, char *drft, struct stat *st, int rename_drft, char *attachment_header_field_name)
{
int status;
char buffer[BUFSIZ], file[BUFSIZ];
struct stat sts;
+ char *original_draft; /* name of original draft file */
+ char *p; /* string pointer for building file name */
+
+ /*
+ * Save the original name of the draft file. The name of the draft file is changed
+ * to a temporary file containing the built MIME message if there are attachments.
+ * We need the original name so that it can be renamed after the message is sent.
+ */
+
+ original_draft = drft;
+
+ /*
+ * There might be attachments if a header field name for attachments is supplied.
+ * Convert the draft to a MIME message. Use the mhbuild composition file for the
+ * draft if there was a successful conversion because that now contains the MIME
+ * message. A nice side effect of this is that it leaves the original draft file
+ * untouched so that it can be retrieved and modified if desired.
+ */
+
+ if (attachment_header_field_name != (char *)0) {
+ switch (attach(attachment_header_field_name, drft)) {
+ case OK:
+ drft = composition_file_name;
+ break;
+
+ case NOTOK:
+ return (NOTOK);
+
+ case DONE:
+ break;
+ }
+ }
armed++;
switch (setjmp (env)) {
/* rename the original draft */
if (rename_drft && status == OK &&
- rename (drft, strncpy (buffer, m_backup (drft), sizeof(buffer))) == NOTOK)
+ rename (original_draft, strncpy (buffer, m_backup (original_draft), sizeof(buffer))) == NOTOK)
advise (buffer, "unable to rename %s to", drft);
break;
if (distfile)
unlink (distfile);
+ /*
+ * Get rid of any temporary files that we created for attachments. Also get rid of
+ * the renamed composition file that mhbuild leaves as a turd. It looks confusing,
+ * but we use the body file name to help build the renamed composition file name.
+ */
+
+ if (drft == composition_file_name) {
+ clean_up_temporary_files();
+
+ if (strlen(composition_file_name) >= sizeof (composition_file_name) - 6)
+ advise((char *)0, "unable to remove original composition file.");
+
+ else {
+ if ((p = strrchr(composition_file_name, '/')) == (char *)0)
+ p = composition_file_name;
+ else
+ p++;
+
+ (void)strcpy(body_file_name, p);
+ *p++ = ',';
+ (void)strcpy(p, body_file_name);
+ (void)strcat(p, ".orig");
+
+ (void)unlink(composition_file_name);
+ }
+ }
+
return status;
}
+static int
+attach(char *attachment_header_field_name, char *draft_file_name)
+{
+ char buf[MAXPATHLEN + 6]; /* miscellaneous buffer */
+ int c; /* current character for body copy */
+ int has_attachment; /* draft has at least one attachment */
+ int has_body; /* draft has a message body */
+ int length; /* length of attachment header field name */
+ char *p; /* miscellaneous string pointer */
+
+ /*
+ * Open up the draft file.
+ */
+
+ if ((draft_file = fopen(draft_file_name, "r")) == (FILE *)0)
+ adios((char *)0, "can't open draft file `%s'.", draft_file_name);
+
+ /*
+ * Allocate a buffer to hold the header components as they're read in.
+ * This buffer might need to be quite large, so we grow it as needed.
+ */
+
+ if ((field = (char *)malloc(field_size = 256)) == (char *)0)
+ adios(NULL, "can't allocate field buffer.");
+
+ /*
+ * Scan the draft file for a header field name that matches the -attach
+ * argument. The existence of one indicates that the draft has attachments.
+ * Bail out if there are no attachments because we're done. Read to the
+ * end of the headers even if we have no attachments.
+ */
+
+ length = strlen(attachment_header_field_name);
+
+ has_attachment = 0;
+
+ while (get_line() != EOF && *field != '\0' && *field != '-')
+ if (strncasecmp(field, attachment_header_field_name, length) == 0 && field[length] == ':')
+ has_attachment = 1;
+
+ if (has_attachment == 0)
+ return (DONE);
+
+ /*
+ * We have at least one attachment. Look for at least one non-blank line
+ * in the body of the message which indicates content in the body.
+ */
+
+ has_body = 0;
+
+ while (get_line() != EOF) {
+ for (p = field; *p != '\0'; p++) {
+ if (*p != ' ' && *p != '\t') {
+ has_body = 1;
+ break;
+ }
+ }
+
+ if (has_body)
+ break;
+ }
+
+ /*
+ * Make names for the temporary files.
+ */
+
+ (void)strncpy(body_file_name, m_scratch("", m_maildir(invo_name)), sizeof (body_file_name));
+ (void)strncpy(composition_file_name, m_scratch("", m_maildir(invo_name)), sizeof (composition_file_name));
+
+ if (has_body)
+ body_file = fopen(body_file_name, "w");
+
+ composition_file = fopen(composition_file_name, "w");
+
+ if ((has_body && body_file == (FILE *)0) || composition_file == (FILE *)0) {
+ clean_up_temporary_files();
+ adios((char *)0, "unable to open all of the temporary files.");
+ }
+
+ /*
+ * Start at the beginning of the draft file. Copy all non-attachment header fields
+ * to the temporary composition file. Then add the dashed line separator.
+ */
+
+ rewind(draft_file);
+
+ while (get_line() != EOF && *field != '\0' && *field != '-')
+ if (strncasecmp(field, attachment_header_field_name, length) != 0 || field[length] != ':')
+ (void)fprintf(composition_file, "%s\n", field);
+
+ (void)fputs("--------\n", composition_file);
+
+ /*
+ * Copy the message body to a temporary file.
+ */
+
+ if (has_body) {
+ while ((c = getc(draft_file)) != EOF)
+ putc(c, body_file);
+
+ (void)fclose(body_file);
+ }
+
+ /*
+ * Add a mhbuild MIME composition file line for the body if there was one.
+ */
+
+ if (has_body)
+ make_mime_composition_file_entry(body_file_name);
+
+ /*
+ * Now, go back to the beginning of the draft file and look for header fields
+ * that specify attachments. Add a mhbuild MIME composition file for each.
+ */
+
+ rewind(draft_file);
+
+ while (get_line() != EOF && *field != '\0' && *field != '-') {
+ if (strncasecmp(field, attachment_header_field_name, length) == 0 && field[length] == ':') {
+ for (p = field + length + 1; *p == ' ' || *p == '\t'; p++)
+ ;
+
+ make_mime_composition_file_entry(p);
+ }
+ }
+
+ (void)fclose(composition_file);
+
+ /*
+ * We're ready to roll! Run mhbuild on the composition file. Note that mhbuild
+ * is in the context as buildmimeproc.
+ */
+
+ (void)sprintf(buf, "%s %s", buildmimeproc, composition_file_name);
+
+ if (system(buf) != 0) {
+ clean_up_temporary_files();
+ return (NOTOK);
+ }
+
+ return (OK);
+}
+
+static void
+clean_up_temporary_files(void)
+{
+ (void)unlink(body_file_name);
+ (void)unlink(composition_file_name);
+
+ return;
+}
+
+static int
+get_line(void)
+{
+ int c; /* current character */
+ int n; /* number of bytes in buffer */
+ char *p; /* buffer pointer */
+
+ /*
+ * Get a line from the input file, growing the field buffer as needed. We do this
+ * so that we can fit an entire line in the buffer making it easy to do a string
+ * comparison on both the field name and the field body which might be a long path
+ * name.
+ */
+
+ for (n = 0, p = field; (c = getc(draft_file)) != EOF; *p++ = c) {
+ if (c == '\n' && (c = getc(draft_file)) != ' ' && c != '\t') {
+ (void)ungetc(c, draft_file);
+ c = '\n';
+ break;
+ }
+
+ if (++n >= field_size - 1) {
+ if ((field = (char *)realloc((void *)field, field_size += 256)) == (char *)0)
+ adios(NULL, "can't grow field buffer.");
+
+ p = field + n - 1;
+ }
+ }
+
+ /*
+ * NUL-terminate the field..
+ */
+
+ *p = '\0';
+
+ return (c);
+}
+
+static void
+make_mime_composition_file_entry(char *file_name)
+{
+ int binary; /* binary character found flag */
+ int c; /* current character */
+ char cmd[MAXPATHLEN + 6]; /* file command buffer */
+ char *content_type; /* mime content type */
+ FILE *fp; /* content and pipe file pointer */
+ struct node *np; /* context scan node pointer */
+ char *p; /* miscellaneous string pointer */
+ struct stat st; /* file status buffer */
+
+ content_type = (char *)0;
+
+ /*
+ * Check the file name for a suffix. Scan the context for that suffix on a
+ * mhshow-suffix- entry. We use these entries to be compatible with mhnshow,
+ * and there's no reason to make the user specify each suffix twice. Context
+ * entries of the form "mhshow-suffix-contenttype" in the name have the suffix
+ * in the field, including the dot.
+ */
+
+ if ((p = strrchr(file_name, '.')) != (char *)0) {
+ for (np = m_defs; np; np = np->n_next) {
+ if (strncasecmp(np->n_name, "mhshow-suffix-", 14) == 0 && strcasecmp(p, np->n_field) == 0) {
+ content_type = np->n_name + 14;
+ break;
+ }
+ }
+ }
+
+ /*
+ * No content type was found, either because there was no matching entry in the
+ * context or because the file name has no suffix. Open the file and check for
+ * non-ASCII characters. Choose the content type based on this check.
+ */
+
+ if (content_type == (char *)0) {
+ if ((fp = fopen(file_name, "r")) == (FILE *)0) {
+ clean_up_temporary_files();
+ adios((char *)0, "unable to access file \"%s\"", file_name);
+ }
+
+ binary = 0;
+
+ while ((c = getc(fp)) != EOF) {
+ if (c > 127 || c < 0) {
+ binary = 1;
+ break;
+ }
+ }
+
+ (void)fclose(fp);
+
+ content_type = binary ? "application/octet-stream" : "text/plain";
+ }
+
+ /*
+ * Make sure that the attachment file exists and is readable. Append a mhbuild
+ * directive to the draft file. This starts with the content type. Append a
+ * file name attribute and a private x-unix-mode attribute. Also append a
+ * description obtained (if possible) by running the "file" command on the file.
+ */
+
+ if (stat(file_name, &st) == -1 || access(file_name, R_OK) != 0) {
+ clean_up_temporary_files();
+ adios((char *)0, "unable to access file \"%s\"", file_name);
+ }
+
+ (void)fprintf(composition_file, "#%s; name=\"%s\"; x-unix-mode=0%.3ho",
+ content_type, ((p = strrchr(file_name, '/')) == (char *)0) ? file_name : p + 1, (unsigned short)(st.st_mode & 0777));
+
+ if (strlen(file_name) > MAXPATHLEN) {
+ clean_up_temporary_files();
+ adios((char *)0, "attachment file name `%s' too long.", file_name);
+ }
+
+ (void)sprintf(cmd, "file '%s'", file_name);
+
+ if ((fp = popen(cmd, "r")) != (FILE *)0 && fgets(cmd, sizeof (cmd), fp) != (char *)0) {
+ *strchr(cmd, '\n') = '\0';
+
+ /*
+ * The output of the "file" command is of the form
+ *
+ * file: description
+ *
+ * Strip off the "file:" and subsequent white space.
+ */
+
+ for (p = cmd; *p != '\0'; p++) {
+ if (*p == ':') {
+ for (p++; *p != '\0'; p++) {
+ if (*p != '\t')
+ break;
+ }
+ break;
+ }
+ }
+
+ if (*p != '\0')
+ (void)fprintf(composition_file, " [ %s ]", p);
+
+ (void)pclose(fp);
+ }
+
+ /*
+ * Finish up with the file name.
+ */
+
+ (void)fprintf(composition_file, " %s\n", file_name);
+
+ return;
+}
/*
* Split large message into several messages of
if (is_selected(mp, msgnum)) {
if (debugsw)
advise (NULL, "annotate message %d", msgnum);
- annotate (m_name (msgnum), annotext, cp, inplace, 1);
+ annotate (m_name (msgnum), annotext, cp, inplace, 1, 0, 0);
}
}
if (verbsw)
vec[vecp++] = "-verbose";
- switch (sendsbr (vec, vecp, tmpfil, &st, 0)) {
+ switch (sendsbr (vec, vecp, tmpfil, &st, 0, (char *)0)) {
case DONE:
case NOTOK:
status++;
* This code is Copyright (c) 2002, by the authors of nmh. See the
* COPYRIGHT file in the root directory of the nmh distribution for
* complete copyright information.
+ *
+ * Several options have been added to ease the inclusion of attachments
+ * using the header field name mechanism added to anno and send. The
+ * -attach option is used to specify the header field name for attachments.
+ *
+ * Several commands have been added at the whatnow prompt:
+ *
+ * cd [ directory ] This option works just like the shell's
+ * cd command and lets the user change the
+ * directory from which attachments are
+ * taken so that long path names are not
+ * needed with every file.
+ *
+ * ls [ ls-options ] This option works just like the normal
+ * ls command and exists to allow the user
+ * to verify file names in the directory.
+ *
+ * pwd This option works just like the normal
+ * pwd command and exists to allow the user
+ * to verify the directory.
+ *
+ * attach files This option attaches the named files to
+ * the draft.
+ *
+ * alist [-ln] This option lists the attachments on the
+ * draft. -l gets long listings, -n gets
+ * numbered listings.
+ *
+ * detach files This option removes attachments from the
+ * detach -n numbers draft. This can be done by file name or
+ * by attachment number.
*/
#include <h/mh.h>
{ "version", 0 },
#define HELPSW 7
{ "help", 0 },
+#define ATTACHSW 8
+ { "attach header-field-name", 0 },
{ NULL, 0 }
};
{ "quit [-delete]", 0 },
#define DELETESW 9
{ "delete", 0 },
+#define CDCMDSW 10
+ { "cd [directory]", 0 },
+#define PWDCMDSW 11
+ { "pwd", 0 },
+#define LSCMDSW 12
+ { "ls", 0 },
+#define ATTACHCMDSW 13
+ { "attach", 0 },
+#define DETACHCMDSW 14
+ { "detach [-n]", 2 },
+#define ALISTCMDSW 15
+ { "alist [-ln] ", 2 },
{ NULL, 0 }
};
char buf[BUFSIZ], prompt[BUFSIZ];
char **argp, **arguments;
struct stat st;
+ char *attach = (char *)0; /* attachment header field name */
+ char cwd[MAXPATHLEN + 1]; /* current working directory */
+ char file[MAXPATHLEN + 1]; /* file name buffer */
+ char shell[MAXPATHLEN + 1]; /* shell response buffer */
+ FILE *f; /* read pointer for bgnd proc */
+ char *l; /* set on -l to alist command */
+ int n; /* set on -n to alist command */
invo_name = r1bindex (argv[0], '/');
arguments = getarguments (invo_name, argc, argv, 1);
argp = arguments;
+ /*
+ * Get the initial current working directory.
+ */
+
+ if (getcwd(cwd, sizeof (cwd)) == (char *)0) {
+ adios("getcwd", "could not get working directory");
+ }
+
while ((cp = *argp++)) {
if (*cp == '-') {
switch (smatch (++cp, whatnowswitches)) {
- case AMBIGSW:
+ case AMBIGSW:
ambigsw (cp, whatnowswitches);
done (1);
- case UNKWNSW:
+ case UNKWNSW:
adios (NULL, "-%s unknown", cp);
- case HELPSW:
+ case HELPSW:
snprintf (buf, sizeof(buf), "%s [switches] [file]", invo_name);
print_help (buf, whatnowswitches, 1);
done (1);
print_version(invo_name);
done (1);
- case DFOLDSW:
+ case DFOLDSW:
if (dfolder)
adios (NULL, "only one draft folder at a time!");
if (!(cp = *argp++) || *cp == '-')
dfolder = path (*cp == '+' || *cp == '@' ? cp + 1 : cp,
*cp != '@' ? TFOLDER : TSUBCWF);
continue;
- case DMSGSW:
+ case DMSGSW:
if (dmsg)
adios (NULL, "only one draft message at a time!");
if (!(dmsg = *argp++) || *dmsg == '-')
adios (NULL, "missing argument to %s", argp[-2]);
continue;
- case NDFLDSW:
+ case NDFLDSW:
dfolder = NULL;
isdf = NOTOK;
continue;
- case EDITRSW:
+ case EDITRSW:
if (!(ed = *argp++) || *ed == '-')
adios (NULL, "missing argument to %s", argp[-2]);
nedit = 0;
continue;
- case NEDITSW:
+ case NEDITSW:
nedit++;
continue;
if (!(myprompt = *argp++) || *myprompt == '-')
adios (NULL, "missing argument to %s", argp[-2]);
continue;
+
+ case ATTACHSW:
+ if (attach != (char *)0)
+ adios(NULL, "only one attachment header field name at a time!");
+ if (!(attach = *argp++) || *attach == '-')
+ adios (NULL, "missing argument to %s", argp[-2]);
+ continue;
}
}
if (drft)
done (1);
break;
- case LISTSW:
+ case LISTSW:
/* display the draft file */
showfile (++argp, drft);
break;
done (1);
break;
- case SENDSW:
+ case SENDSW:
/* Send draft */
sendfile (++argp, drft, 0);
break;
- case REFILEOPT:
+ case REFILEOPT:
/* Refile the draft */
if (refile (++argp, drft) == 0)
done (0);
break;
- default:
+ case CDCMDSW:
+ /* Change the working directory for attachments
+ *
+ * Run the directory through the user's shell so that
+ * we can take advantage of any syntax that the user
+ * is accustomed to. Read back the absolute path.
+ */
+
+ if (*++argp == (char *)0) {
+ (void)sprintf(buf, "$SHELL -c \"cd;pwd\"");
+ }
+ else if (strlen(*argp) >= BUFSIZ) {
+ adios((char *)0, "arguments too long");
+ }
+ else {
+ (void)sprintf(buf, "$SHELL -c \"cd %s;cd %s;pwd\"", cwd, *argp);
+ }
+ if ((f = popen(buf, "r")) != (FILE *)0) {
+ fgets(cwd, sizeof (cwd), f);
+
+ if (strchr(cwd, '\n') != (char *)0)
+ *strchr(cwd, '\n') = '\0';
+
+ pclose(f);
+ }
+ else {
+ advise("popen", "could not get directory");
+ }
+
+ break;
+
+ case PWDCMDSW:
+ /* Print the working directory for attachments */
+ printf("%s\n", cwd);
+ break;
+
+ case LSCMDSW:
+ /* List files in the current attachment working directory
+ *
+ * Use the user's shell so that we can take advantage of any
+ * syntax that the user is accustomed to.
+ */
+
+ cp = buf + sprintf(buf, "$SHELL -c \" cd %s;ls", cwd);
+
+ while (*++argp != (char *)0) {
+ if (cp + strlen(*argp) + 2 >= buf + BUFSIZ)
+ adios((char *)0, "arguments too long");
+
+ cp += sprintf(cp, " %s", *argp);
+ }
+
+ (void)strcat(cp, "\"");
+ (void)system(buf);
+ break;
+
+ case ALISTCMDSW:
+ /*
+ * List attachments on current draft. Options are:
+ *
+ * -l long listing (full path names)
+ * -n numbers listing
+ */
+
+ if (attach == (char *)0) {
+ advise((char *)0, "can't list because no header field name was given.");
+ break;
+ }
+
+ l = (char *)0;
+ n = 0;
+
+ while (*++argp != (char *)0) {
+ if (strcmp(*argp, "-l") == 0)
+ l = "/";
+
+ else if (strcmp(*argp, "-n") == 0)
+ n = 1;
+
+ else if (strcmp(*argp, "-ln") == 0 || strcmp(*argp, "-nl") == 0) {
+ l = "/";
+ n = 1;
+ }
+
+ else {
+ n = -1;
+ break;
+ }
+ }
+
+ if (n == -1)
+ advise((char *)0, "usage is alist [-ln].");
+
+ else
+ annolist(drft, attach, l, n);
+
+ break;
+
+ case ATTACHCMDSW:
+ /*
+ * Attach files to current draft.
+ */
+
+ if (attach == (char *)0) {
+ advise((char *)0, "can't attach because no header field name was given.");
+ break;
+ }
+
+ /*
+ * Build a command line that causes the user's shell to list the file name
+ * arguments. This handles and wildcard expansion, tilde expansion, etc.
+ */
+
+ cp = buf + sprintf(buf, "$SHELL -c \" cd %s;ls", cwd);
+
+ while (*++argp != (char *)0) {
+ if (cp + strlen(*argp) + 3 >= buf + BUFSIZ)
+ adios((char *)0, "arguments too long");
+
+ cp += sprintf(cp, " %s", *argp);
+ }
+
+ (void)strcat(cp, "\"");
+
+ /*
+ * Read back the response from the shell, which contains a number of lines
+ * with one file name per line. Remove off the newline. Determine whether
+ * we have an absolute or relative path name. Prepend the current working
+ * directory to relative path names. Add the attachment annotation to the
+ * draft.
+ */
+
+ if ((f = popen(buf, "r")) != (FILE *)0) {
+ while (fgets(shell, sizeof (shell), f) != (char *)0) {
+ *(strchr(shell, '\n')) = '\0';
+
+ if (*shell == '/')
+ (void)annotate(drft, attach, shell, 1, 0, -1, 1);
+ else {
+ (void)sprintf(file, "%s/%s", cwd, shell);
+ (void)annotate(drft, attach, file, 1, 0, -1, 1);
+ }
+ }
+
+ pclose(f);
+ }
+ else {
+ advise("popen", "could not get file from shell");
+ }
+
+ break;
+
+ case DETACHCMDSW:
+ /*
+ * Detach files from current draft.
+ */
+
+ if (attach == (char *)0) {
+ advise((char *)0, "can't detach because no header field name was given.");
+ break;
+ }
+
+ /*
+ * Scan the arguments for a -n. Mixed file names and numbers aren't allowed,
+ * so this catches a -n anywhere in the argument list.
+ */
+
+ for (n = 0, arguments = argp + 1; *arguments != (char *)0; arguments++) {
+ if (strcmp(*arguments, "-n") == 0) {
+ n = 1;
+ break;
+ }
+ }
+
+ /*
+ * A -n was found so interpret the arguments as attachment numbers.
+ * Decrement any remaining argument number that is greater than the one
+ * just processed after processing each one so that the numbering stays
+ * correct.
+ */
+
+ if (n == 1) {
+ for (arguments = argp + 1; *arguments != (char *)0; arguments++) {
+ if (strcmp(*arguments, "-n") == 0)
+ continue;
+
+ if (**arguments != '\0') {
+ n = atoi(*arguments);
+ (void)annotate(drft, attach, (char *)0, 1, 0, n, 1);
+
+ for (argp = arguments + 1; *argp != (char *)0; argp++) {
+ if (atoi(*argp) > n) {
+ if (atoi(*argp) == 1)
+ *argp = "";
+ else
+ (void)sprintf(*argp, "%d", atoi(*argp) - 1);
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * The arguments are interpreted as file names. Run them through the
+ * user's shell for wildcard expansion and other goodies. Do this from
+ * the current working directory if the argument is not an absolute path
+ * name (does not begin with a /).
+ */
+
+ else {
+ for (arguments = argp + 1; *arguments != (char *)0; arguments++) {
+ if (**arguments == '/') {
+ if (strlen(*arguments) + sizeof ("$SHELL -c \"ls \"") >= sizeof (buf))
+ adios((char *)0, "arguments too long");
+
+ (void)sprintf(buf, "$SHELL -c \"ls %s\"", *arguments);
+ }
+ else {
+ if (strlen(cwd) + strlen(*arguments) + sizeof ("$SHELL -c \" cd ; ls \"") >= sizeof (buf))
+ adios((char *)0, "arguments too long");
+
+ (void)sprintf(buf, "$SHELL -c \" cd %s;ls %s\"", cwd, *arguments);
+ }
+
+ if ((f = popen(buf, "r")) != (FILE *)0) {
+ while (fgets(shell, sizeof (cwd), f) != (char *)0) {
+ *(strchr(shell, '\n')) = '\0';
+ (void)annotate(drft, attach, shell, 1, 0, 0, 1);
+ }
+
+ pclose(f);
+ }
+ else {
+ advise("popen", "could not get file from shell");
+ }
+ }
+ }
+
+ break;
+
+ default:
/* Unknown command */
advise (NULL, "say what?");
break;
fflush (stdout);
switch (pid = vfork ()) {
- case NOTOK:
+ case NOTOK:
advise ("fork", "unable to");
status = NOTOK;
break;
- case OK:
+ case OK:
if (cwd)
chdir (cwd);
if (altmsg) {
perror (*ed);
_exit (-1);
- default:
+ default:
if ((status = pidwait (pid, NOTOK))) {
#ifdef ATTVIBUG
if ((cp = r1bindex (*ed, '/'))
for (i = 0; (child_id = vfork()) == NOTOK && i < 5; i++)
sleep (5);
switch (child_id) {
- case NOTOK:
+ case NOTOK:
advise (NULL, "unable to fork, so sending directly...");
- case OK:
+ case OK:
vecp = 0;
vec[vecp++] = invo_name;
if (pushsw)
perror (sendproc);
_exit (-1);
- default:
+ default:
if (pidwait(child_id, OK) == 0)
done (0);
return 1;
{ "saslmech", SASLminc(-5) },
#define USERSW 38
{ "user", SASLminc(-4) },
+#define SNDATTACHSW 39
+ { "attach file", 6 },
{ NULL, 0 }
};
char *cp, buf[BUFSIZ], **argp;
char **arguments, *vec[MAXARGS];
struct stat st;
+ char *attach = (char *)0; /* attachment header field name */
#ifndef lint
int distsw = 0;
while ((cp = *argp++)) {
if (*cp == '-') {
switch (smatch (++cp, sendswitches)) {
- case AMBIGSW:
+ case AMBIGSW:
ambigsw (cp, sendswitches);
return;
- case UNKWNSW:
+ case UNKWNSW:
advise (NULL, "-%s unknown\n", cp);
return;
- case SHELPSW:
+ case SHELPSW:
snprintf (buf, sizeof(buf), "%s [switches]", sp);
print_help (buf, sendswitches, 1);
return;
print_version (invo_name);
return;
- case SPSHSW:
+ case SPSHSW:
pushed++;
continue;
- case NSPSHSW:
+ case NSPSHSW:
pushed = 0;
continue;
- case SPLITSW:
+ case SPLITSW:
if (!(cp = *argp++) || sscanf (cp, "%d", &splitsw) != 1) {
advise (NULL, "missing argument to %s", argp[-2]);
return;
}
continue;
- case UNIQSW:
+ case UNIQSW:
unique++;
continue;
- case NUNIQSW:
+ case NUNIQSW:
unique = 0;
continue;
- case FORWSW:
+ case FORWSW:
forwsw++;
continue;
- case NFORWSW:
+ case NFORWSW:
forwsw = 0;
continue;
- case VERBSW:
+ case VERBSW:
verbsw++;
vec[vecp++] = --cp;
continue;
vec[vecp++] = --cp;
continue;
- case DEBUGSW:
+ case DEBUGSW:
debugsw++; /* fall */
- case NFILTSW:
- case FRMTSW:
- case NFRMTSW:
+ case NFILTSW:
+ case FRMTSW:
+ case NFRMTSW:
case BITSTUFFSW:
case NBITSTUFFSW:
- case MIMESW:
- case NMIMESW:
- case MSGDSW:
- case NMSGDSW:
- case WATCSW:
- case NWATCSW:
- case MAILSW:
- case SAMLSW:
- case SSNDSW:
- case SOMLSW:
- case SNOOPSW:
+ case MIMESW:
+ case NMIMESW:
+ case MSGDSW:
+ case NMSGDSW:
+ case WATCSW:
+ case NWATCSW:
+ case MAILSW:
+ case SAMLSW:
+ case SSNDSW:
+ case SOMLSW:
+ case SNOOPSW:
case SASLSW:
vec[vecp++] = --cp;
continue;
- case ALIASW:
- case FILTSW:
- case WIDTHSW:
- case CLIESW:
- case SERVSW:
+ case ALIASW:
+ case FILTSW:
+ case WIDTHSW:
+ case CLIESW:
+ case SERVSW:
case SASLMECHSW:
case USERSW:
vec[vecp++] = --cp;
vec[vecp++] = cp;
continue;
- case SDRFSW:
- case SDRMSW:
+ case SDRFSW:
+ case SDRMSW:
if (!(cp = *argp++) || *cp == '-') {
advise (NULL, "missing argument to %s", argp[-2]);
return;
}
- case SNDRFSW:
+ case SNDRFSW:
+ continue;
+
+ case SNDATTACHSW:
+ if (!(attach = *argp++) || *attach == '-') {
+ advise (NULL, "missing argument to %s", argp[-2]);
+ return;
+ }
continue;
}
}
vec[0] = r1bindex (postproc, '/');
closefds (3);
- if (sendsbr (vec, vecp, file, &st, 1) == OK)
+ if (sendsbr (vec, vecp, file, &st, 1, attach) == OK)
done (0);
}
fflush (stdout);
switch (pid = vfork ()) {
- case NOTOK:
+ case NOTOK:
advise ("fork", "unable to");
return 1;
- case OK:
+ case OK:
vecp = 0;
vec[vecp++] = r1bindex (whomproc, '/');
vec[vecp++] = file;
perror (whomproc);
_exit (-1); /* NOTREACHED */
- default:
+ default:
return (pidwait (pid, NOTOK) & 0377 ? 1 : 0);
}
}