X-Git-Url: https://diplodocus.org/git/nmh/blobdiff_plain/2da3024caea2be71550f9eabd2fbc08fefe29bb5..167e542b:/uip/mhstoresbr.c?ds=inline diff --git a/uip/mhstoresbr.c b/uip/mhstoresbr.c index 41aa1a4c..93d3e902 100644 --- a/uip/mhstoresbr.c +++ b/uip/mhstoresbr.c @@ -2,22 +2,21 @@ /* * 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 #include #include #include -#include -#include -#include -#include -#include +#include +#include #include #include +#include -extern int errno; /* * The list of top-level contents to display @@ -47,7 +46,6 @@ typedef int (*qsort_comp) (const void *, const void *); /* mhmisc.c */ int part_ok (CT, int); int type_ok (CT, int); -int make_intermediates (char *); void flush_errors (void); /* mhshowsbr.c */ @@ -71,12 +69,11 @@ static int store_external (CT); 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 @@ -93,9 +90,7 @@ store_all_messages (CT *cts) * 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); @@ -140,13 +135,11 @@ store_switch (CT ct) 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); @@ -154,24 +147,19 @@ store_switch (CT 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 */ @@ -180,7 +168,7 @@ store_switch (CT ct) /* * Generic routine to store a MIME content. - * (audio, video, image, text, message/rfc922) + * (audio, video, image, text, message/rfc822) */ static int @@ -283,7 +271,15 @@ store_multi (CT ct) 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; } @@ -443,7 +439,13 @@ store_external (CT ct) 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; @@ -479,7 +481,7 @@ ct_compar (CT *a, CT *b) 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]; @@ -517,11 +519,13 @@ store_content (CT ct, CT p) */ 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; } @@ -549,51 +553,60 @@ store_content (CT ct, CT p) } } - /* - * 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 */ @@ -859,47 +872,6 @@ losing: } -/* - * 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. * @@ -915,9 +887,9 @@ output_content_folder (char *folder, char *filename) 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; @@ -1064,7 +1036,7 @@ static void get_storeproc (CT ct) { char **ap, **ep, *cp; - CI ci = &ct->c_ctinfo; + CI ci; /* * If the storeproc has already been defined, @@ -1074,13 +1046,44 @@ get_storeproc (CT ct) 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 != '|' @@ -1121,3 +1124,230 @@ copy_some_headers (FILE *out, CT ct) 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-.extension + * -suffix original_file. + * -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 */ +/******************************************************************************/