/*
* mhstoresbr.c -- routines to save/store the contents of MIME messages
*
- * $Id$
+ * 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.
*/
#include <h/mh.h>
#include <fcntl.h>
#include <h/signals.h>
#include <h/md5.h>
-#include <errno.h>
-#include <setjmp.h>
-#include <signal.h>
-#include <zotnet/mts/mts.h>
-#include <zotnet/tws/tws.h>
+#include <h/mts.h>
+#include <h/tws.h>
#include <h/mime.h>
#include <h/mhparse.h>
+#include <h/utils.h>
-extern int errno;
/*
* The list of top-level contents to display
/* mhmisc.c */
int part_ok (CT, int);
int type_ok (CT, int);
-int make_intermediates (char *);
void flush_errors (void);
/* mhshowsbr.c */
static int ct_compar (CT *, CT *);
static int store_content (CT, CT);
static int output_content_file (CT, int);
-static int check_folder (char *);
static int output_content_folder (char *, char *);
static int parse_format_string (CT, char *, char *, int, char *);
static void get_storeproc (CT);
static int copy_some_headers (FILE *, CT);
-
+static char *clobber_check (char *);
/*
* Main entry point to store content
* Check for the directory in which to
* store any contents.
*/
- if (autosw)
- dir = getcpy (cwd);
- else if ((cp = context_find (nmhstorage)) && *cp)
+ if ((cp = context_find (nmhstorage)) && *cp)
dir = getcpy (cp);
else
dir = getcpy (cwd);
switch (ct->c_type) {
case CT_MULTIPART:
return store_multi (ct);
- break;
case CT_MESSAGE:
switch (ct->c_subtype) {
case MESSAGE_PARTIAL:
return store_partial (ct);
- break;
case MESSAGE_EXTERNAL:
return store_external (ct);
case MESSAGE_RFC822:
default:
return store_generic (ct);
- break;
}
- break;
case CT_APPLICATION:
return store_application (ct);
- break;
case CT_TEXT:
case CT_AUDIO:
case CT_IMAGE:
case CT_VIDEO:
return store_generic (ct);
- break;
default:
adios (NULL, "unknown content type %d", ct->c_type);
- break;
}
return OK; /* NOT REACHED */
/*
* Generic routine to store a MIME content.
- * (audio, video, image, text, message/rfc922)
+ * (audio, video, image, text, message/rfc822)
*/
static int
CT p = part->mp_part;
if (part_ok (p, 1) && type_ok (p, 1)) {
+ if (ct->c_storage) {
+ /* Support mhstore -outfile. The MIME parser doesn't
+ load c_storage, so we know that p->c_storage is
+ NULL here. */
+ p->c_storage = ct->c_storage;
+ }
result = store_switch (p);
+ p->c_storage = NULL;
+
if (result == OK && ct->c_subtype == MULTI_ALTERNATE)
break;
}
p->c_partno = ct->c_partno;
/* we probably need to check if content is really there */
+ if (ct->c_storage) {
+ /* Support mhstore -outfile. The MIME parser doesn't load
+ c_storage, so we know that p->c_storage is NULL here. */
+ p->c_storage = ct->c_storage;
+ }
result = store_switch (p);
+ p->c_storage = NULL;
p->c_partno = NULL;
return result;
static int
store_content (CT ct, CT p)
{
- int appending = 0, msgnum;
+ int appending = 0, msgnum = 0;
int is_partial = 0, first_partial = 0;
int last_partial = 0;
char *cp, buffer[BUFSIZ];
*/
if (p) {
appending = 1;
- ct->c_storage = add (p->c_storage, NULL);
+ if (! ct->c_storage) {
+ ct->c_storage = add (p->c_storage, NULL);
- /* record the folder name */
- if (p->c_folder) {
- ct->c_folder = add (p->c_folder, NULL);
+ /* record the folder name */
+ if (p->c_folder) {
+ ct->c_folder = add (p->c_folder, NULL);
+ }
}
goto got_filename;
}
}
}
- /*
- * Check the beginning of storage formatting string
- * to see if we are saving content to a folder.
- */
- if (*cp == '+' || *cp == '@') {
- char *tmpfilenam, *folder;
+ if (! ct->c_storage) {
+ /*
+ * Check the beginning of storage formatting string
+ * to see if we are saving content to a folder.
+ */
+ if (*cp == '+' || *cp == '@') {
+ char *tmpfilenam, *folder;
- /* Store content in temporary file for now */
- tmpfilenam = m_scratch ("", invo_name);
- ct->c_storage = add (tmpfilenam, NULL);
+ /* Store content in temporary file for now */
+ tmpfilenam = m_mktemp(invo_name, NULL, NULL);
+ ct->c_storage = add (tmpfilenam, NULL);
- /* Get the folder name */
- if (cp[1])
- folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF);
- else
- folder = getfolder (1);
+ /* Get the folder name */
+ if (cp[1])
+ folder = pluspath (cp);
+ else
+ folder = getfolder (1);
- /* Check if folder exists */
- if (check_folder (folder) == NOTOK)
- return NOTOK;
+ /* Check if folder exists */
+ create_folder(m_mailpath(folder), 0, exit);
- /* Record the folder name */
- ct->c_folder = add (folder, NULL);
+ /* Record the folder name */
+ ct->c_folder = add (folder, NULL);
- if (cp[1])
- free (folder);
+ if (cp[1])
+ free (folder);
- goto got_filename;
- }
+ goto got_filename;
+ }
- /*
- * Parse and expand the storage formatting string
- * in `cp' into `buffer'.
- */
- parse_format_string (ct, cp, buffer, sizeof(buffer), dir);
+ /*
+ * Parse and expand the storage formatting string
+ * in `cp' into `buffer'.
+ */
+ parse_format_string (ct, cp, buffer, sizeof(buffer), dir);
- /*
- * If formatting begins with '|' or '!', then pass
- * content to standard input of a command and return.
- */
- if (buffer[0] == '|' || buffer[0] == '!')
- return show_content_aux (ct, 1, 0, buffer + 1, dir);
+ /*
+ * If formatting begins with '|' or '!', then pass
+ * content to standard input of a command and return.
+ */
+ if (buffer[0] == '|' || buffer[0] == '!')
+ return show_content_aux (ct, 1, 0, buffer + 1, dir);
- /* record the filename */
- ct->c_storage = add (buffer, NULL);
+ /* record the filename */
+ if ((ct->c_storage = clobber_check (add (buffer, NULL))) == NULL) {
+ return NOTOK;
+ }
+ } else {
+ /* The output filename was explicitly specified, so use it. */
+ if ((ct->c_storage = clobber_check (add (ct->c_storage, NULL))) ==
+ NULL) {
+ return NOTOK;
+ }
+ }
got_filename:
/* flush the output stream */
}
-/*
- * Check if folder exists, and create
- * if necessary.
- */
-
-static int
-check_folder (char *folder)
-{
- char *folderdir;
- struct stat st;
-
- /* expand path to the folder */
- folderdir = m_mailpath (folder);
-
- /* Check if folder exists */
- if (stat (folderdir, &st) == NOTOK) {
- int answer;
- char *ep;
-
- if (errno != ENOENT) {
- advise (folderdir, "error on folder");
- return NOTOK;
- }
-
- ep = concat ("Create folder \"", folderdir, "\"? ", NULL);
- answer = getanswer (ep);
- free (ep);
-
- if (!answer)
- return NOTOK;
-
- if (!makedir (folderdir)) {
- advise (NULL, "unable to create folder %s", folderdir);
- return NOTOK;
- }
- }
-
- return OK;
-}
-
-
/*
* Add a file to a folder.
*
struct msgs *mp;
/* Read the folder. */
- if ((mp = folder_read (folder))) {
+ if ((mp = folder_read (folder, 0))) {
/* Link file into folder */
- msgnum = folder_addmsg (&mp, filename, 0, 0, 0);
+ msgnum = folder_addmsg (&mp, filename, 0, 0, 0, 0, (char *)0);
} else {
advise (NULL, "unable to read folder %s", folder);
return NOTOK;
get_storeproc (CT ct)
{
char **ap, **ep, *cp;
- CI ci = &ct->c_ctinfo;
+ CI ci;
/*
* If the storeproc has already been defined,
if (ct->c_storeproc)
return;
+ /*
+ * If there's a Content-Disposition header and it has a filename,
+ * use that (RFC-2183).
+ */
+ if (ct->c_dispo) {
+ char *cp = strchr (ct->c_dispo, ';');
+ CI ci = calloc (1, sizeof *ci);
+ int status;
+ int found_filename = 0;
+
+ if (cp && parse_header_attrs (ct->c_file, strlen (invo_name) + 2, &cp,
+ ci, &status) == OK) {
+ for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
+ if (! strcasecmp (*ap, "filename")
+ && *(cp = *ep) != '/'
+ && *cp != '.'
+ && *cp != '|'
+ && *cp != '!'
+ && !strchr (cp, '%')) {
+ ct->c_storeproc = add (cp, NULL);
+ found_filename = 1;
+ }
+ free (*ap);
+ }
+ }
+
+ free (ci);
+ if (found_filename) return;
+ }
+
/*
* Check the attribute/value pairs, for the attribute "name".
* If found, do a few sanity checks and copy the value into
* the storeproc.
*/
+ ci = &ct->c_ctinfo;
for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
- if (!strcasecmp (*ap, "name")
+ if (! strcasecmp (*ap, "name")
&& *(cp = *ep) != '/'
&& *cp != '.'
&& *cp != '|'
return OK;
}
+
+/******************************************************************************/
+/* -clobber support */
+
+enum clobber_policy_t {
+ NMH_CLOBBER_ALWAYS,
+ NMH_CLOBBER_AUTO,
+ NMH_CLOBBER_SUFFIX,
+ NMH_CLOBBER_ASK,
+ NMH_CLOBBER_NEVER
+};
+
+static enum clobber_policy_t clobber_policy = NMH_CLOBBER_ALWAYS;
+
+int files_not_clobbered = 0;
+
+int
+save_clobber_policy (const char *value) {
+ if (! strcasecmp (value, "always")) {
+ clobber_policy = NMH_CLOBBER_ALWAYS;
+ } else if (! strcasecmp (value, "auto")) {
+ clobber_policy = NMH_CLOBBER_AUTO;
+ } else if (! strcasecmp (value, "suffix")) {
+ clobber_policy = NMH_CLOBBER_SUFFIX;
+ } else if (! strcasecmp (value, "ask")) {
+ clobber_policy = NMH_CLOBBER_ASK;
+ } else if (! strcasecmp (value, "never")) {
+ clobber_policy = NMH_CLOBBER_NEVER;
+ } else {
+ return 1;
+ }
+
+ return 0;
+}
+
+
+static char *
+next_version (char *file, enum clobber_policy_t clobber_policy) {
+ const size_t max_versions = 1000000;
+ /* 8 = log max_versions + one for - or . + one for null terminator */
+ const size_t buflen = strlen (file) + 8;
+ char *buffer = mh_xmalloc (buflen);
+ size_t version;
+
+ char *extension = NULL;
+ if (clobber_policy == NMH_CLOBBER_AUTO &&
+ ((extension = strrchr (file, '.')) != NULL)) {
+ *extension++ = '\0';
+ }
+
+ for (version = 1; version < max_versions; ++version) {
+ int fd;
+
+ switch (clobber_policy) {
+ case NMH_CLOBBER_AUTO: {
+ snprintf (buffer, buflen, "%s-%ld%s%s", file, (long) version,
+ extension == NULL ? "" : ".",
+ extension == NULL ? "" : extension);
+ break;
+ }
+
+ case NMH_CLOBBER_SUFFIX:
+ snprintf (buffer, buflen, "%s.%ld", file, (long) version);
+ break;
+
+ default:
+ /* Should never get here. */
+ advise (NULL, "will not overwrite %s, invalid clobber policy", buffer);
+ free (buffer);
+ ++files_not_clobbered;
+ return NULL;
+ }
+
+ /* Actually (try to) create the file here to avoid a race
+ condition on file naming + creation. This won't solve the
+ problem with old NFS that doesn't support O_EXCL, though.
+ Let the umask strip off permissions from 0666 as desired.
+ That's what fopen () would do if it was creating the file. */
+ if ((fd = open (buffer, O_CREAT | O_EXCL,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
+ S_IROTH | S_IWOTH)) >= 0) {
+ close (fd);
+ break;
+ }
+ }
+
+ free (file);
+
+ if (version >= max_versions) {
+ advise (NULL, "will not overwrite %s, too many versions", buffer);
+ free (buffer);
+ buffer = NULL;
+ ++files_not_clobbered;
+ }
+
+ return buffer;
+}
+
+
+static char *
+clobber_check (char *original_file) {
+ /* clobber policy return value
+ * -------------- ------------
+ * -always original_file
+ * -auto original_file-<digits>.extension
+ * -suffix original_file.<digits>
+ * -ask original_file, 0, or another filename/path
+ * -never 0
+ */
+
+ char *file;
+ char *cwd = NULL;
+ int check_again;
+
+ if (clobber_policy == NMH_CLOBBER_ASK) {
+ /* Save cwd for possible use in loop below. */
+ char *slash;
+
+ cwd = add (original_file, NULL);
+ slash = strrchr (cwd, '/');
+
+ if (slash) {
+ *slash = '\0';
+ } else {
+ /* original_file wasn't a full path, which shouldn't happen. */
+ cwd = NULL;
+ }
+ }
+
+ do {
+ struct stat st;
+
+ file = original_file;
+ check_again = 0;
+
+ switch (clobber_policy) {
+ case NMH_CLOBBER_ALWAYS:
+ break;
+
+ case NMH_CLOBBER_SUFFIX:
+ case NMH_CLOBBER_AUTO:
+ if (stat (file, &st) == OK) {
+ file = next_version (original_file, clobber_policy);
+ }
+ break;
+
+ case NMH_CLOBBER_ASK:
+ if (stat (file, &st) == OK) {
+ enum answers { NMH_YES, NMH_NO, NMH_RENAME };
+ static struct swit answer[4] = {
+ { "yes", 0, NMH_YES }, { "no", 0, NMH_NO }, { "rename", 0, NMH_RENAME }, { NULL, 0, 0 } };
+ char **ans;
+
+ if (isatty (fileno (stdin))) {
+ char *prompt =
+ concat ("Overwrite \"", file, "\" [y/n/rename]? ", NULL);
+ ans = getans (prompt, answer);
+ free (prompt);
+ } else {
+ /* Overwrite, that's what nmh used to do. And warn. */
+ advise (NULL, "-clobber ask but no tty, so overwrite %s", file);
+ break;
+ }
+
+ switch ((enum answers) smatch (*ans, answer)) {
+ case NMH_YES:
+ break;
+ case NMH_NO:
+ free (file);
+ file = NULL;
+ ++files_not_clobbered;
+ break;
+ case NMH_RENAME: {
+ char buf[PATH_MAX];
+ printf ("Enter filename or full path of the new file: ");
+ if (fgets (buf, sizeof buf, stdin) == NULL ||
+ buf[0] == '\0') {
+ file = NULL;
+ ++files_not_clobbered;
+ } else {
+ char *newline = strchr (buf, '\n');
+ if (newline) {
+ *newline = '\0';
+ }
+ }
+
+ free (file);
+
+ if (buf[0] == '/') {
+ /* Full path, use it. */
+ file = add (buf, NULL);
+ } else {
+ /* Relative path. */
+ file = cwd ? concat (cwd, "/", buf, NULL) : add (buf, NULL);
+ }
+
+ check_again = 1;
+ break;
+ }
+ }
+ }
+ break;
+
+ case NMH_CLOBBER_NEVER:
+ if (stat (file, &st) == OK) {
+ /* Keep count of files that would have been clobbered,
+ and return that as process exit status. */
+ advise (NULL, "will not overwrite %s with -clobber never", file);
+ free (file);
+ file = NULL;
+ ++files_not_clobbered;
+ }
+ break;
+ }
+
+ original_file = file;
+ } while (check_again);
+
+ if (cwd) {
+ free (cwd);
+ }
+
+ return file;
+}
+
+/* -clobber support */
+/******************************************************************************/