2 ** mhstore.c -- store the contents of MIME messages
4 ** This code is Copyright (c) 2002, by the authors of nmh. See the
5 ** COPYRIGHT file in the root directory of the nmh distribution for
6 ** complete copyright information.
11 #include <h/signals.h>
16 #include <h/mhparse.h>
23 static struct swit switches[] = {
28 #define FILESW 2 /* interface from show */
33 { "type content", 0 },
43 char *version=VERSION;
46 extern char *tmp; /* directory to place temp files */
51 extern char *parts[NPARTS + 1];
52 extern char *types[NTYPES + 1];
57 #define quitser pipeser
60 CT parse_mime(char *);
65 void set_endian(void);
66 void flush_errors(void);
69 void free_content(CT);
70 extern CT *cts; /* The list of top-level contents to display */
76 static void pipeser(int);
81 ** Cache of current directory. This must be
82 ** set before these routines are called.
87 ** The directory in which to store the contents.
92 ** Type for a compare function for qsort. This keeps
93 ** the compiler happy.
95 typedef int (*qsort_comp) (const void *, const void *);
100 int type_ok(CT, int);
101 int make_intermediates(char *);
102 void flush_errors(void);
105 int show_content_aux(CT, int, char *, char *);
110 static void store_single_message(CT);
111 static int store_switch(CT);
112 static int store_generic(CT);
113 static int store_multi(CT);
114 static int store_partial(CT);
115 static int store_external(CT);
116 static int ct_compar(CT *, CT *);
117 static int store_content(CT, CT);
118 static int output_content_file(CT, int);
119 static int output_content_folder(char *, char *);
120 static int parse_format_string(CT, char *, char *, int, char *);
121 static int copy_some_headers(FILE *, CT);
122 static void store_all_messages(CT *);
126 main(int argc, char **argv)
129 char *cp, *file = NULL, *folder = NULL;
130 char *maildir, buf[100], **argp;
132 struct msgs_array msgs = { 0, 0, NULL };
133 struct msgs *mp = NULL;
137 if (atexit(freects_done) != 0) {
138 adios(EX_OSERR, NULL, "atexit failed");
141 setlocale(LC_ALL, "");
142 invo_name = mhbasename(argv[0]);
144 /* read user profile/context */
147 arguments = getarguments(invo_name, argc, argv, 1);
153 while ((cp = *argp++)) {
155 switch (smatch(++cp, switches)) {
157 ambigsw(cp, switches);
160 adios(EX_USAGE, NULL, "-%s unknown", cp);
163 snprintf(buf, sizeof(buf), "%s [+folder] [msgs] [switches]", invo_name);
164 print_help(buf, switches, 1);
165 exit(argc == 2 ? EX_OK : EX_USAGE);
167 print_version(invo_name);
168 exit(argc == 2 ? EX_OK : EX_USAGE);
178 if (!(cp = *argp++) || *cp == '-')
179 adios(EX_USAGE, NULL, "missing argument to %s",
182 adios(EX_USAGE, NULL, "too many parts (starting with %s), %d max", cp, NPARTS);
187 if (!(cp = *argp++) || *cp == '-')
188 adios(EX_USAGE, NULL, "missing argument to %s",
191 adios(EX_USAGE, NULL, "too many types (starting with %s), %d max", cp, NTYPES);
196 if (!(cp = *argp++) || (*cp == '-' && cp[1]))
197 adios(EX_USAGE, NULL, "missing argument to %s",
199 file = *cp == '-' ? cp : mh_xstrdup(expanddir(cp));
207 if (*cp == '+' || *cp == '@') {
209 adios(EX_USAGE, NULL, "only one folder at a time!");
211 folder = mh_xstrdup(expandfol(cp));
213 app_msgarg(&msgs, cp);
216 /* null terminate the list of acceptable parts/types */
223 ** Check if we've specified an additional profile
225 if ((cp = getenv("MHSTORE"))) {
226 if ((fp = fopen(cp, "r"))) {
227 readconfig((struct node **) 0, fp, cp, 0);
230 admonish("", "unable to read $MHSTORE profile (%s)",
236 ** Read the standard profile setup
238 if ((fp = fopen(cp = etcpath("mhn.defaults"), "r"))) {
239 readconfig((struct node **) 0, fp, cp, 0);
244 ** Cache the current directory before we do any chdirs()'s.
246 cwd = mh_xstrdup(pwd());
249 ** Check for storage directory. If specified,
250 ** then store temporary files there. Else we
251 ** store them in standard nmh directory.
253 if ((cp = context_find(nmhstorage)) && *cp)
254 tmp = concat(cp, "/", invo_name, NULL);
256 tmp = mh_xstrdup(toabsdir(invo_name));
258 if (file && msgs.size)
259 adios(EX_USAGE, NULL, "cannot specify msg and file at same time!");
262 ** check if message is coming from file
265 cts = mh_xcalloc(2, sizeof(*cts));
268 if ((ct = parse_mime(file)))
272 ** message(s) are coming from a folder
275 app_msgarg(&msgs, seq_cur);
277 folder = getcurfol();
278 maildir = toabsdir(folder);
280 if (chdir(maildir) == NOTOK)
281 adios(EX_OSERR, maildir, "unable to change directory to");
283 /* read folder and create message structure */
284 if (!(mp = folder_read(folder)))
285 adios(EX_IOERR, NULL, "unable to read folder %s", folder);
287 /* check for empty folder */
289 adios(EX_DATAERR, NULL, "no messages in %s", folder);
291 /* parse all the message ranges/sequences and set SELECTED */
292 for (msgnum = 0; msgnum < msgs.size; msgnum++)
293 if (!m_convert(mp, msgs.msgs[msgnum]))
295 seq_setprev(mp); /* set the previous-sequence */
297 cts = mh_xcalloc(mp->numsel + 1, sizeof(*cts));
300 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
301 if (is_selected(mp, msgnum)) {
304 msgnam = m_name(msgnum);
305 if ((ct = parse_mime(msgnam)))
315 SIGNAL(SIGQUIT, quitser);
316 SIGNAL(SIGPIPE, pipeser);
319 ** Get the associated umask for the relevant contents.
321 for (ctp = cts; *ctp; ctp++) {
325 if (type_ok(ct, 1) && !ct->c_umask) {
326 if (stat(ct->c_file, &st) != NOTOK)
327 ct->c_umask = ~(st.st_mode & 0777);
329 ct->c_umask = ~m_gmprot();
334 ** Store the message content
336 store_all_messages(cts);
338 /* Now free all the structures for the content */
339 for (ctp = cts; *ctp; ctp++)
344 /* If reading from a folder, do some updating */
346 context_replace(curfolder, folder); /* update current folder */
347 seq_setcur(mp, mp->hghsel); /* update current message */
348 seq_save(mp); /* synchronize sequences */
349 context_save(); /* save the context file */
362 fprintf(stderr, "\n");
373 ** Main entry point to store content from a collection of messages.
376 store_all_messages(CT *cts)
382 ** Check for the directory in which to
383 ** store any contents.
385 if ((cp = context_find(nmhstorage)) && *cp)
386 dir = mh_xstrdup(cp);
388 dir = mh_xstrdup(cwd);
390 for (ctp = cts; *ctp; ctp++) {
392 store_single_message(ct);
400 ** Entry point to store the content
401 ** in a (single) message
405 store_single_message(CT ct)
407 if (type_ok(ct, 1)) {
414 if (ct->c_ceclosefnx)
415 (*ct->c_ceclosefnx) (ct);
421 ** Switching routine to store different content types
427 switch (ct->c_type) {
429 return store_multi(ct);
433 switch (ct->c_subtype) {
434 case MESSAGE_PARTIAL:
435 return store_partial(ct);
438 case MESSAGE_EXTERNAL:
439 return store_external(ct);
443 return store_generic(ct);
453 return store_generic(ct);
457 adios(EX_DATAERR, NULL, "unknown content type %d", ct->c_type);
461 return OK; /* NOT REACHED */
466 ** Generic routine to store a MIME content.
467 ** (application, audio, video, image, text, message/rfc922)
472 char **ap, **vp, *cp, *filename;
473 CI ci = &ct->c_ctinfo;
476 ** Check if the content specifies a filename in its MIME parameters.
477 ** Don't bother with this for type "message"
478 ** (only the "message" subtype "rfc822" will use store_generic).
480 if (autosw && ct->c_type != CT_MESSAGE) {
481 /* First check for "filename" in Content-Disposition header */
482 filename = extract_name_value("filename", ct->c_dispo);
483 if (filename && strcmp(filename, ct->c_dispo)!=0) {
484 /* We found "filename" */
485 cp = mhbasename(filename);
486 if (*cp && *cp!='.' && *cp!='|' && *cp!='!' &&
488 /* filename looks good: use it */
489 ct->c_storeproc = mh_xstrdup(cp);
494 ** Check the attribute/value pairs, for the attribute "name".
495 ** If found, take the basename, do a few sanity checks and
496 ** copy the value into c_storeproc.
498 for (ap = ci->ci_attrs, vp = ci->ci_values; *ap; ap++,vp++) {
499 if (mh_strcasecmp(*ap, "name")!=0) {
502 cp = mhbasename(*vp);
503 if (*cp && *cp!='.' && *cp!='|' && *cp!='!' &&
505 /* filename looks good: use it */
506 ct->c_storeproc = mh_xstrdup(cp);
513 return store_content(ct, NULL);
518 ** Store the content of a multipart message
525 struct multipart *m = (struct multipart *) ct->c_ctparams;
529 for (part = m->mp_parts; part; part = part->mp_next) {
530 CT p = part->mp_part;
532 if (part_ok(p, 1) && type_ok(p, 1)) {
533 result = store_switch(p);
534 if (result == OK && ct->c_subtype == MULTI_ALTERNATE)
544 ** Reassemble and store the contents of a collection
545 ** of messages of type "message/partial".
554 struct partial *pm, *qm;
556 qm = (struct partial *) ct->c_ctparams;
561 for (ctp = cts; *ctp; ctp++) {
563 if (p->c_type == CT_MESSAGE && p->c_subtype == ct->c_subtype) {
564 pm = (struct partial *) p->c_ctparams;
565 if (!pm->pm_stored &&
566 strcmp(qm->pm_partid, pm->pm_partid)
568 pm->pm_marked = pm->pm_partno;
579 advise(NULL, "missing (at least) last part of multipart message");
583 base = mh_xcalloc(i + 1, sizeof(*base));
586 for (ctp = cts; *ctp; ctp++) {
588 if (p->c_type == CT_MESSAGE && p->c_subtype == ct->c_subtype) {
589 pm = (struct partial *) p->c_ctparams;
597 qsort((char *) base, i, sizeof(*base), (qsort_comp) ct_compar);
600 for (ctq = base; *ctq; ctq++) {
602 pm = (struct partial *) p->c_ctparams;
603 if (pm->pm_marked != cur) {
604 if (pm->pm_marked == cur - 1) {
605 admonish(NULL, "duplicate part %d of %d part multipart message", pm->pm_marked, hi);
610 advise (NULL, "missing %spart %d of %d part multipart message", cur != hi ? "(at least) " : "", cur, hi);
621 ** Now cycle through the sorted list of messages of type
622 ** "message/partial" and save/append them to a file.
627 if (store_content(ct, NULL) == NOTOK) {
633 for (; *ctq; ctq++) {
635 if (store_content(p, ct) == NOTOK)
645 ** Show how to retrieve content of type "message/external".
648 store_external(CT ct)
655 msg = add("You need to fetch the contents yourself:", NULL);
656 ap = ct->c_ctinfo.ci_attrs;
657 ep = ct->c_ctinfo.ci_values;
658 for (; *ap; ap++, ep++) {
659 msg = add(concat("\n\t", *ap, ": ", *ep, NULL), msg);
661 if (!(fp = fopen(ct->c_file, "r"))) {
662 adios(EX_IOERR, ct->c_file, "unable to open");
664 fseek(fp, ct->c_begin, SEEK_SET);
665 while (!feof(fp) && ftell(fp) < ct->c_end) {
666 if (!fgets(buf, sizeof buf, fp)) {
667 adios(EX_IOERR, ct->c_file, "unable to read");
669 *strchr(buf, '\n') = '\0';
670 msg = add(concat("\n\t", buf, NULL), msg);
679 ** Compare the numbering from two different
680 ** message/partials (needed for sorting).
684 ct_compar(CT *a, CT *b)
686 struct partial *am = (struct partial *) ((*a)->c_ctparams);
687 struct partial *bm = (struct partial *) ((*b)->c_ctparams);
689 return (am->pm_marked - bm->pm_marked);
694 ** Store contents of a message or message part to
695 ** a folder, a file, the standard output, or pass
696 ** the contents to a command.
698 ** If the current content to be saved is a followup part
699 ** to a collection of messages of type "message/partial",
700 ** then field "p" is a pointer to the Content structure
701 ** to the first message/partial in the group.
705 store_content(CT ct, CT p)
707 int appending = 0, msgnum = 0;
708 int is_partial = 0, first_partial = 0;
709 int last_partial = 0;
710 char *cp, buffer[BUFSIZ];
713 ** Do special processing for messages of
714 ** type "message/partial".
716 ** We first check if this content is of type
717 ** "message/partial". If it is, then we need to check
718 ** whether it is the first and/or last in the group.
720 ** Then if "p" is a valid pointer, it points to the Content
721 ** structure of the first partial in the group. So we copy
722 ** the file name and/or folder name from that message. In
723 ** this case, we also note that we will be appending.
725 if (ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_PARTIAL) {
726 struct partial *pm = (struct partial *) ct->c_ctparams;
728 /* Yep, it's a message/partial */
731 /* But is it the first and/or last in the collection? */
732 if (pm->pm_partno == 1)
734 if (pm->pm_maxno && pm->pm_partno == pm->pm_maxno)
738 ** If "p" is a valid pointer, then it points to the
739 ** Content structure for the first message in the group.
740 ** So we just copy the filename or foldername information
741 ** from the previous iteration of this function.
745 ct->c_storage = mh_xstrdup(p->c_storage);
747 /* record the folder name */
749 ct->c_folder = mh_xstrdup(p->c_folder);
756 ** Get storage formatting string.
758 ** 1) If we have storeproc defined, then use that
759 ** 2) Else check for a mhstore-store-<type>/<subtype> entry
760 ** 3) Else check for a mhstore-store-<type> entry
761 ** 4) Else if content is "message", use "+" (current folder)
762 ** 5) Else use string "%m%P.%s".
764 if (!(cp = ct->c_storeproc) || !*cp) {
765 CI ci = &ct->c_ctinfo;
767 snprintf(buffer, sizeof(buffer), "%s-store-%s/%s",
768 invo_name, ci->ci_type, ci->ci_subtype);
769 if ((cp = context_find(buffer)) == NULL || *cp == '\0') {
770 snprintf(buffer, sizeof(buffer), "%s-store-%s",
771 invo_name, ci->ci_type);
772 if ((cp = context_find(buffer)) == NULL ||
774 cp = ct->c_type == CT_MESSAGE ?
781 ** Check the beginning of storage formatting string
782 ** to see if we are saving content to a folder.
784 if (*cp == '+' || *cp == '@') {
785 char *tmpfilenam, *folder;
787 /* Store content in temporary file for now */
788 tmpfilenam = m_mktemp(invo_name, NULL, NULL);
789 ct->c_storage = mh_xstrdup(tmpfilenam);
791 /* Get the folder name */
793 folder = mh_xstrdup(expandfol(cp));
795 folder = getcurfol();
797 /* Check if folder exists */
798 create_folder(toabsdir(folder), 0, exit);
800 /* Record the folder name */
801 ct->c_folder = mh_xstrdup(folder);
810 ** Parse and expand the storage formatting string
811 ** in `cp' into `buffer'.
813 parse_format_string(ct, cp, buffer, sizeof(buffer), dir);
816 ** If formatting begins with '|' or '!', then pass
817 ** content to standard input of a command and return.
819 if (buffer[0] == '|' || buffer[0] == '!')
820 return show_content_aux(ct, 0, buffer + 1, dir);
822 /* record the filename */
823 ct->c_storage = mh_xstrdup(buffer);
826 /* flush the output stream */
829 /* Now save or append the content to a file */
830 if (output_content_file(ct, appending) == NOTOK)
834 ** If necessary, link the file into a folder and remove
835 ** the temporary file. If this message is a partial,
836 ** then only do this if it is the last one in the group.
838 if (ct->c_folder && (!is_partial || last_partial)) {
839 msgnum = output_content_folder(ct->c_folder, ct->c_storage);
840 unlink(ct->c_storage);
846 ** Now print out the name/number of the message
847 ** that we are storing.
851 fprintf(stderr, "reassembling partials ");
853 fprintf(stderr, "%s", ct->c_file);
855 fprintf(stderr, "%s,", ct->c_file);
857 fprintf(stderr, "storing message %s", ct->c_file);
859 fprintf(stderr, " part %s", ct->c_partno);
863 ** Unless we are in the "middle" of group of message/partials,
864 ** we now print the name of the file, folder, and/or message
865 ** to which we are storing the content.
867 if (!is_partial || last_partial) {
869 fprintf(stderr, " to folder %s as message %d\n",
870 ct->c_folder, msgnum);
871 } else if (strcmp(ct->c_storage, "-")==0) {
872 fprintf(stderr, " to stdout\n");
876 cwdlen = strlen(cwd);
877 fprintf(stderr, " as file %s\n",
878 strncmp(ct->c_storage, cwd,
880 ct->c_storage[cwdlen] != '/' ?
882 ct->c_storage + cwdlen + 1);
891 ** Output content to a file
895 output_content_file(CT ct, int appending)
898 char *file, buffer[BUFSIZ];
903 ** If the pathname contains directories, make sure
904 ** all of them exist.
906 if (strchr(ct->c_storage, '/') && make_intermediates(ct->c_storage)
910 if (ct->c_encoding != CE_7BIT) {
913 if (!ct->c_ceopenfnx) {
914 advise(NULL, "don't know how to decode part %s of message %s", ct->c_partno, ct->c_file);
918 file = appending || strcmp(ct->c_storage, "-")==0 ?
919 NULL : ct->c_storage;
920 if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
922 if (strcmp(file, ct->c_storage)==0) {
923 (*ct->c_ceclosefnx) (ct);
928 ** Send to standard output
930 if (strcmp(ct->c_storage, "-")==0) {
933 if ((gd = dup(fileno(stdout))) == NOTOK) {
934 advise("stdout", "unable to dup");
936 (*ct->c_ceclosefnx) (ct);
939 if ((fp = fdopen(gd, appending ? "a" : "w")) == NULL) {
940 advise("stdout", "unable to fdopen (%d, \"%s\") from", gd, appending ? "a" : "w");
948 if ((fp = fopen(ct->c_storage, appending ? "a" : "w"))
950 advise(ct->c_storage, "unable to fopen for %s",
952 "appending" : "writing");
958 ** Filter the header fields of the initial enclosing
959 ** message/partial into the file.
961 if (ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_PARTIAL) {
962 struct partial *pm = (struct partial *) ct->c_ctparams;
964 if (pm->pm_partno == 1)
965 copy_some_headers(fp, ct);
969 switch (cc = read(fd, buffer, sizeof(buffer))) {
971 advise(file, "error reading content from");
978 fwrite(buffer, sizeof(*buffer), cc, fp);
984 (*ct->c_ceclosefnx) (ct);
986 if (cc != NOTOK && fflush(fp))
987 advise(ct->c_storage, "error writing to");
991 return (cc != NOTOK ? OK : NOTOK);
994 if (!ct->c_fp && (ct->c_fp = fopen(ct->c_file, "r")) == NULL) {
995 advise(ct->c_file, "unable to open for reading");
1001 fseek(ct->c_fp, pos, SEEK_SET);
1003 if (strcmp(ct->c_storage, "-")==0) {
1006 if ((gd = dup(fileno(stdout))) == NOTOK) {
1007 advise("stdout", "unable to dup");
1010 if ((fp = fdopen(gd, appending ? "a" : "w")) == NULL) {
1011 advise("stdout", "unable to fdopen (%d, \"%s\") from",
1012 gd, appending ? "a" : "w");
1017 if ((fp = fopen(ct->c_storage, appending ? "a" : "w"))
1019 advise(ct->c_storage, "unable to fopen for %s",
1020 appending ? "appending" : "writing");
1026 ** Copy a few of the header fields of the initial
1027 ** enclosing message/partial into the file.
1030 if (ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_PARTIAL) {
1031 struct partial *pm = (struct partial *) ct->c_ctparams;
1033 if (pm->pm_partno == 1) {
1034 copy_some_headers(fp, ct);
1039 while (fgets(buffer, sizeof(buffer) - 1, ct->c_fp)) {
1040 if ((pos += strlen(buffer)) > last) {
1043 diff = strlen(buffer) - (pos - last);
1045 buffer[diff] = '\0';
1048 ** If this is the first content of a group of
1049 ** message/partial contents, then we only copy a few
1050 ** of the header fields of the enclosed message.
1053 switch (buffer[0]) {
1056 if (filterstate < 0)
1065 if (!uprf(buffer, XXX_FIELD_PRF) && !uprf(buffer, VRSN_FIELD) && !uprf(buffer, "Subject:") && !uprf(buffer, "Message-ID:")) {
1080 advise(ct->c_storage, "error writing to");
1090 ** Add a file to a folder.
1092 ** Return the new message number of the file
1093 ** when added to the folder. Return -1, if
1094 ** there is an error.
1098 output_content_folder(char *folder, char *filename)
1103 /* Read the folder. */
1104 if ((mp = folder_read(folder))) {
1105 /* Link file into folder */
1106 msgnum = folder_addmsg(&mp, filename, 0, 0, 0, 0, NULL);
1108 advise(NULL, "unable to read folder %s", folder);
1112 /* free folder structure */
1116 ** Return msgnum. We are relying on the fact that
1117 ** msgnum will be -1, if folder_addmsg() had an error.
1124 ** Parse and expand the storage formatting string
1125 ** pointed to by "cp" into "buffer".
1129 parse_format_string(CT ct, char *cp, char *buffer, int buflen, char *dir)
1133 CI ci = &ct->c_ctinfo;
1136 ** If storage string is "-", just copy it, and
1137 ** return (send content to standard output).
1139 if (cp[0] == '-' && cp[1] == '\0') {
1140 strncpy(buffer, cp, buflen);
1148 ** If formatting string is a pathname that doesn't
1149 ** begin with '/', then preface the path with the
1150 ** appropriate directory.
1152 if (*cp != '/' && *cp != '|' && *cp != '!') {
1153 snprintf(bp, buflen, "%s/", dir[1] ? dir : "");
1161 /* We are processing a storage escape */
1166 ** Insert parameters from Content-Type.
1167 ** This is only valid for '|' commands.
1169 if (buffer[0] != '|' && buffer[0] != '!') {
1178 for (ap=ci->ci_attrs, ep=ci->ci_values;
1180 snprintf(bp, buflen,
1192 /* insert message number */
1193 snprintf(bp, buflen, "%s",
1194 mhbasename(ct->c_file));
1198 /* insert part number with leading dot */
1200 snprintf(bp, buflen, ".%s",
1205 /* insert part number withouth leading dot */
1207 strncpy(bp, ct->c_partno, buflen);
1211 /* insert content type */
1212 strncpy(bp, ci->ci_type, buflen);
1216 /* insert content subtype */
1217 strncpy(bp, ci->ci_subtype, buflen);
1221 /* insert the character % */
1231 /* Advance bp and decrement buflen */
1249 ** Copy some of the header fields of the initial message/partial
1250 ** message into the header of the reassembled message.
1254 copy_some_headers(FILE *out, CT ct)
1258 hp = ct->c_first_hf; /* start at first header field */
1262 ** A few of the header fields of the enclosing
1263 ** messages are not copied.
1265 if (!uprf(hp->name, XXX_FIELD_PRF) &&
1266 mh_strcasecmp(hp->name, VRSN_FIELD) &&
1267 mh_strcasecmp(hp->name, "Subject") &&
1268 mh_strcasecmp(hp->name, "Message-ID"))
1269 fprintf(out, "%s:%s", hp->name, hp->value);
1270 hp = hp->next; /* next header field */