"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.6.2/src/save.c" (9 Dec 2022, 58688 Bytes) of package /linux/misc/tin-2.6.2.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "save.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.6.1_vs_2.6.2.

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : save.c
    4  *  Author    : I. Lea & R. Skrenta
    5  *  Created   : 1991-04-01
    6  *  Updated   : 2022-08-29
    7  *  Notes     :
    8  *
    9  * Copyright (c) 1991-2023 Iain Lea <iain@bricbrac.de>, Rich Skrenta <skrenta@pbm.com>
   10  * All rights reserved.
   11  *
   12  * Redistribution and use in source and binary forms, with or without
   13  * modification, are permitted provided that the following conditions
   14  * are met:
   15  *
   16  * 1. Redistributions of source code must retain the above copyright notice,
   17  *    this list of conditions and the following disclaimer.
   18  *
   19  * 2. Redistributions in binary form must reproduce the above copyright
   20  *    notice, this list of conditions and the following disclaimer in the
   21  *    documentation and/or other materials provided with the distribution.
   22  *
   23  * 3. Neither the name of the copyright holder nor the names of its
   24  *    contributors may be used to endorse or promote products derived from
   25  *    this software without specific prior written permission.
   26  *
   27  * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   28  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   29  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   30  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
   31  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   32  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   33  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   34  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   35  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   36  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   37  * POSSIBILITY OF SUCH DAMAGE.
   38  */
   39 
   40 
   41 #ifndef TIN_H
   42 #   include "tin.h"
   43 #endif /* !TIN_H */
   44 #ifndef TCURSES_H
   45 #   include "tcurses.h"
   46 #endif /* !TCURSES_H */
   47 
   48 #ifdef HAVE_UUDEVIEW_H
   49 #   ifndef __UUDEVIEW_H__
   50 #       include <uudeview.h>
   51 #   endif /* !__UUDEVIEW_H__ */
   52 #endif /* HAVE_UUDEVIEW_H */
   53 
   54 #ifndef HAVE_LIBUU
   55 #   undef OFF
   56     enum state {
   57         INITIAL,
   58         MIDDLE,
   59         OFF,
   60         END
   61     };
   62 #endif /* !HAVE_LIBUU */
   63 
   64 enum action {
   65     VIEW,
   66     SAVE,
   67     SAVE_TAGGED,
   68     PIPE_RAW
   69 #ifndef DONT_HAVE_PIPING
   70     , PIPE
   71 #endif /* !DONT_HAVE_PIPING */
   72 };
   73 
   74 /*
   75  * Local prototypes
   76  */
   77 static FILE *open_save_filename(const char *path, t_bool mbox);
   78 static char *build_tree(int depth, int maxlen, int i);
   79 static char *generate_savepath(t_part *part);
   80 static int build_part_list(t_openartinfo *art);
   81 static int get_tagged(int n);
   82 static int match_content_type(t_part *part, char *type);
   83 static t_bool check_save_mime_type(t_part *part, const char *mime_types);
   84 static t_bool decode_save_one(t_part *part, FILE *rawfp, t_bool postproc);
   85 static t_bool expand_save_filename(char *outpath, size_t outpath_len, const char *path);
   86 static t_bool tag_part(int n);
   87 static t_function attachment_left(void);
   88 static t_function attachment_right(void);
   89 static t_partl *find_part(int n);
   90 static void build_attachment_line(int i);
   91 static void draw_attachment_arrow(void);
   92 static void free_part_list(t_partl *list);
   93 static void generate_filename(char *buf, int buflen, const char *suffix);
   94 #ifndef DONT_HAVE_PIPING
   95     static void pipe_part(const char *savepath);
   96 #endif /* !DONT_HAVE_PIPING */
   97 static void post_process_uud(void);
   98 static void post_process_sh(void);
   99 static void process_part(t_part *part, t_openartinfo *art, FILE *outfile, const char *savepath, enum action what);
  100 static void process_parts(t_part *part, t_openartinfo *art, enum action what);
  101 static void show_attachment_page(void);
  102 static void start_viewer(t_part *part, const char *path);
  103 static void tag_pattern(void);
  104 static void untag_all_parts(void);
  105 static void untag_part(int n);
  106 static void uudecode_line(const char *buf, FILE *fp);
  107 static void view_file(const char *path, const char *file);
  108 #ifndef HAVE_LIBUU
  109     static void sum_file(const char *path, const char *file);
  110 #endif /* !HAVE_LIBUU */
  111 
  112 static int num_of_tagged_parts, info_len;
  113 static t_menu attmenu = { 0, 0, 0, show_attachment_page, draw_attachment_arrow, build_attachment_line };
  114 static t_partl *part_list;
  115 
  116 /*
  117  * Check for articles and say how many new/unread in each group.
  118  * or
  119  * Start if new/unread articles and return first group with new/unread.
  120  * or
  121  * Save any new articles to savedir and mark arts read and mail user
  122  * and inform how many arts in which groups were saved.
  123  * or
  124  * Mail any new articles to specified user and mark arts read and mail
  125  * user and inform how many arts in which groups were mailed.
  126  * Return codes:
  127  *  CHECK_ANY_NEWS  - code to pass to exit() - see manpage for list
  128  *  START_ANY_NEWS  - index in my_group of first group with unread news or -1
  129  *  MAIL_ANY_NEWS   - not checked
  130  *  SAVE_ANY_NEWS   - not checked
  131  */
  132 int
  133 check_start_save_any_news(
  134     int function,
  135     t_bool catchup)
  136 {
  137     FILE *artfp, *savefp;
  138     FILE *fp_log = (FILE *) 0;
  139     char *line;
  140     char buf[LEN];
  141     char path[PATH_LEN];
  142     char logfile[PATH_LEN], savefile[PATH_LEN];
  143     char subject[HEADER_LEN];
  144     int group_count = 0;
  145     int i, j;
  146     int art_count, hot_count;
  147     int saved_arts = 0;                 /* Total # saved arts */
  148     struct t_article *art;
  149     struct t_group *group;
  150     t_bool log_opened = TRUE;
  151     t_bool print_first = (t_bool) verbose;
  152     t_bool unread_news = FALSE;
  153     time_t epoch;
  154 
  155     switch (function) {
  156         case CHECK_ANY_NEWS:
  157         case START_ANY_NEWS:
  158             if (verbose)
  159                 wait_message(0, _(txt_checking_for_news));
  160             break;
  161 
  162         case MAIL_ANY_NEWS:
  163             joinpath(savefile, sizeof(savefile), tmpdir, "tin");
  164 #ifdef APPEND_PID
  165             snprintf(savefile + strlen(savefile), sizeof(savefile) - strlen(savefile), ".%ld", (long) process_id);
  166 #endif /* APPEND_PID */
  167             /* FALLTHROUGH */
  168 
  169         case SAVE_ANY_NEWS:
  170             joinpath(logfile, sizeof(logfile), rcdir, "log");
  171 
  172             if (no_write || (fp_log = fopen(logfile, "w")) == NULL) {
  173                 perror_message(_(txt_cannot_open), logfile);
  174                 fp_log = stdout;
  175                 verbose = FALSE;
  176                 log_opened = FALSE;
  177             }
  178             fprintf(fp_log, "To: %s\n", userid);
  179             (void) time(&epoch);
  180             snprintf(subject, sizeof(subject), "Subject: NEWS LOG %s", ctime(&epoch));
  181             fprintf(fp_log, "%s\n", subject);   /* ctime() includes a \n too */
  182             break;
  183 
  184         default:
  185             break;
  186     }
  187 
  188     /*
  189      * For each group we subscribe to...
  190      */
  191     for (i = 0; i < selmenu.max; i++) {
  192         art_count = hot_count = 0;
  193         group = &active[my_group[i]];
  194         /*
  195          * FIXME: workaround to get a valid CURR_GROUP
  196          * it also points to the currently processed group so that
  197          * the correct attributes are used
  198          * The correct fix is to get rid of CURR_GROUP
  199          */
  200         selmenu.curr = i;
  201 
  202         if (group->bogus || !group->subscribed)
  203             continue;
  204 
  205         if (function == MAIL_ANY_NEWS || function == SAVE_ANY_NEWS) {
  206             if (!group->attribute->batch_save)
  207                 continue;
  208 
  209             group_count++;
  210             snprintf(buf, sizeof(buf), _(txt_saved_groupname), group->name);
  211             fprintf(fp_log, "%s", buf);
  212             if (verbose)
  213                 wait_message(0, "%s", buf);
  214 
  215             if (function == SAVE_ANY_NEWS) {
  216                 char tmp[PATH_LEN];
  217                 char *group_path = my_malloc(strlen(group->name) + 2); /* trailing "/\0" */
  218 
  219                 make_group_path(group->name, group_path);
  220                 if (!strfpath((cmdline.args & CMDLINE_SAVEDIR) ? cmdline.savedir : tinrc.savedir, tmp, sizeof(tmp), group, FALSE))
  221                     joinpath(tmp, sizeof(tmp), homedir, DEFAULT_SAVEDIR);
  222 
  223                 joinpath(path, sizeof(path), tmp, group_path);
  224                 free(group_path);
  225                 create_path(path);  /* TODO error handling */
  226             }
  227         }
  228 
  229         if (!index_group(group)) {
  230             for_each_art(j) {
  231                 art = &arts[j];
  232                 FreeAndNull(art->refs);
  233                 FreeAndNull(art->msgid);
  234             }
  235             continue;
  236         }
  237 
  238         /*
  239          * For each article in this group...
  240          */
  241         for_each_art(j) {
  242             if (arts[j].status != ART_UNREAD)
  243                 continue;
  244 
  245             switch (function) {
  246                 case CHECK_ANY_NEWS:
  247                     if (print_first) {
  248                         my_fputc('\n', stdout);
  249                         print_first = FALSE;
  250                     }
  251                     if (!verbose && !catchup) /* we don't need details */
  252                         return NEWS_AVAIL_EXIT;
  253                     art_count++;
  254                     if (arts[j].score >= tinrc.score_select)
  255                         hot_count++;
  256                     if (catchup)
  257                         art_mark(group, &arts[j], ART_READ);
  258                     break;
  259 
  260                 case START_ANY_NEWS:
  261                     return i;   /* return first group with unread news */
  262                     /* NOTREACHED */
  263 
  264                 case MAIL_ANY_NEWS:
  265                 case SAVE_ANY_NEWS:
  266                     if ((artfp = open_art_fp(group, arts[j].artnum)) == NULL)
  267                         continue;
  268 
  269                     if (function == SAVE_ANY_NEWS) {
  270                         snprintf(buf, sizeof(buf), "%"T_ARTNUM_PFMT, arts[j].artnum);
  271                         joinpath(savefile, sizeof(savefile), path, buf);
  272                     }
  273 
  274                     if ((savefp = fopen(savefile, "w")) == NULL) {
  275                         fprintf(fp_log, _(txt_cannot_open), savefile);
  276                         if (verbose)
  277                             perror_message(_(txt_cannot_open), savefile);
  278                         TIN_FCLOSE(artfp);
  279                         continue;
  280                     }
  281 
  282                     if ((function == MAIL_ANY_NEWS) && ((INTERACTIVE_NONE == tinrc.interactive_mailer) || (INTERACTIVE_WITH_HEADERS == tinrc.interactive_mailer))) {
  283                         fprintf(savefp, "To: %s\n", mail_news_user);
  284                         fprintf(savefp, "Subject: %s\n", arts[j].subject);
  285                         /*
  286                          * Reuse some headers to allow threading in mail reader
  287                          */
  288                         if (arts[j].msgid)
  289                             fprintf(savefp, "Message-ID: %s\n", arts[j].msgid);
  290                         /* fprintf(savefp, "References: %s\n", arts[j].refs); */
  291                         /*
  292                          * wrap article in appropriate MIME type
  293                          */
  294                         fprintf(savefp, "MIME-Version: %s\n", MIME_SUPPORTED_VERSION);
  295                         fprintf(savefp, "Content-Type: message/rfc822\n");
  296                         /*
  297                          * CTE should be 7bit if the article is in pure
  298                          * US-ASCII, but this requires previous parsing
  299                          */
  300                         fprintf(savefp, "Content-Transfer-Encoding: 8bit\n\n");
  301                     }
  302 
  303                     snprintf(buf, sizeof(buf), "[%5"T_ARTNUM_PFMT"]  %s\n", arts[j].artnum, arts[j].subject);
  304                     fprintf(fp_log, "%s", buf); /* buf may contain % */
  305                     if (verbose)
  306                         wait_message(0, "%s", buf);
  307 
  308                     while ((line = tin_fgets(artfp, FALSE)) != NULL)
  309                         fprintf(savefp, "%s\n", line);      /* TODO: error handling */
  310 
  311                     TIN_FCLOSE(artfp);
  312                     fclose(savefp);
  313                     saved_arts++;
  314 
  315                     if (function == MAIL_ANY_NEWS) {
  316                         strfmailer(mailer, arts[j].subject, mail_news_user, savefile, buf, sizeof(buf), tinrc.mailer_format);
  317                         invoke_cmd(buf);        /* Keep trying after errors */
  318                         unlink(savefile);
  319                     }
  320                     if (catchup)
  321                         art_mark(group, &arts[j], ART_READ);
  322                     break;
  323 
  324                 default:
  325                     break;
  326             }
  327         }
  328 
  329         if (art_count) {
  330             unread_news = TRUE;
  331             if (verbose)
  332                 wait_message(0, _(txt_saved_group), art_count, hot_count,
  333                     PLURAL(art_count, txt_article), group->name);
  334         }
  335     }
  336 
  337     switch (function) {
  338         case CHECK_ANY_NEWS:
  339             /*
  340              * TODO: shall we return 2 or 0 in the -cZ case?
  341              */
  342             if (unread_news && !catchup)
  343                 return NEWS_AVAIL_EXIT;
  344             else {
  345                 if (verbose)
  346                     wait_message(1, _(txt_there_is_no_news));
  347                 return EXIT_SUCCESS;
  348             }
  349             /* NOTREACHED */
  350 
  351         case START_ANY_NEWS:
  352             wait_message(1, _(txt_there_is_no_news));
  353             return -1;
  354             /* NOTREACHED */
  355 
  356         case MAIL_ANY_NEWS:
  357         case SAVE_ANY_NEWS:
  358             snprintf(buf, sizeof(buf), _(txt_saved_summary), (function == MAIL_ANY_NEWS ? _(txt_mailed) : _(txt_saved)),
  359                     saved_arts, PLURAL(saved_arts, txt_article),
  360                     group_count, PLURAL(group_count, txt_group));
  361             fprintf(fp_log, "%s", buf);
  362             if (verbose)
  363                 wait_message(0, "%s", buf);
  364 
  365             if (log_opened) {
  366                 fclose(fp_log);
  367                 if (verbose)
  368                     wait_message(0, _(txt_mail_log_to), (function == MAIL_ANY_NEWS ? mail_news_user : userid));
  369                 strfmailer(mailer, subject, (function == MAIL_ANY_NEWS ? mail_news_user : userid), logfile, buf, sizeof(buf), tinrc.mailer_format);
  370                 if (invoke_cmd(buf))
  371                     unlink(logfile);
  372             }
  373             break;
  374 
  375         default:
  376             break;
  377     }
  378     return 0;
  379 }
  380 
  381 
  382 /*
  383  * Do basic validation of a save-to path, handle append/overwrite semantics
  384  * and return an opened file handle or NULL if user aborted etc..
  385  */
  386 static FILE *
  387 open_save_filename(
  388     const char *path,
  389     t_bool mbox)
  390 {
  391     FILE *fp;
  392     char keyappend[MAXKEYLEN], keyoverwrite[MAXKEYLEN], keyquit[MAXKEYLEN];
  393     char mode[3];
  394     struct stat st;
  395     t_function func;
  396 
  397     strcpy(mode, "a+");
  398 
  399     /*
  400      * Mailboxes will always be appended to
  401      */
  402     if (!mbox && stat(path, &st) != -1) {
  403         /*
  404          * Admittedly a special case hack, but it saves failing later on
  405          */
  406         if (S_ISDIR(st.st_mode)) {
  407             wait_message(2, _(txt_cannot_write_to_directory), path);
  408             return NULL;
  409         }
  410 /* TODO: will this get called every art? Should only be done once/batch */
  411 /* TODO: or add an option for defaulting on all future queries */
  412 /* TODO: 'truncate' path if query exceeds screen-width */
  413         func = prompt_slk_response((tinrc.default_save_mode == 'a' ? SAVE_APPEND_FILE : SAVE_OVERWRITE_FILE),
  414                 save_append_overwrite_keys,
  415                 _(txt_append_overwrite_quit), path,
  416                 PrintFuncKey(keyappend, SAVE_APPEND_FILE, save_append_overwrite_keys),
  417                 PrintFuncKey(keyoverwrite, SAVE_OVERWRITE_FILE, save_append_overwrite_keys),
  418                 PrintFuncKey(keyquit, GLOBAL_QUIT, save_append_overwrite_keys));
  419 
  420         switch (func) {
  421             case SAVE_OVERWRITE_FILE:
  422                 strcpy(mode, "w");
  423                 break;
  424 
  425             case GLOBAL_ABORT:
  426             case GLOBAL_QUIT:
  427                 wait_message(1, _(txt_art_not_saved));
  428                 return NULL;
  429 
  430             default:    /* SAVE_APPEND_FILE */
  431                 break;
  432         }
  433         if (func == SAVE_OVERWRITE_FILE)
  434             tinrc.default_save_mode = 'o';
  435         else
  436             tinrc.default_save_mode = 'a';
  437     }
  438 
  439     if ((fp = fopen(path, mode)) == NULL) {
  440         error_message(2, _(txt_cannot_open_for_saving), path);
  441         return NULL;
  442     }
  443 
  444     return fp;
  445 }
  446 
  447 
  448 /*
  449  * This is where an article is actually copied to disk and processed
  450  * We only need to copy the art to disk if we are doing post-processing
  451  * 'artinfo' is parsed/cooked article to be saved
  452  * 'mailbox' is set if we are saving to a =mailbox
  453  * 'inpath' is the template save path/file to save to
  454  * 'max' is the number of articles we are saving
  455  * 'post_process' is set if we want post-processing
  456  * Expand the path appropriately, taking account of multiple file
  457  * extensions.
  458  *
  459  * Extract binary attachments if !LIBUU
  460  * Start viewer if requested
  461  * If successful, add entry to the save[] array
  462  * Returns:
  463  *     TRUE or FALSE depending on whether article was saved okay.
  464  *
  465  * TODO: could we use append_mail() here
  466  */
  467 t_bool
  468 save_and_process_art(
  469     t_openartinfo *artinfo,
  470     t_bool is_mailbox,
  471     const char *inpath,
  472     int max,
  473     t_bool post_process)
  474 {
  475     FILE *fp;
  476     char from[HEADER_LEN];
  477     char path[PATH_LEN];
  478     time_t epoch;
  479     t_bool mmdf = (is_mailbox && !strcasecmp(txt_mailbox_formats[tinrc.mailbox_format], "MMDF"));
  480 
  481     if (fseek(artinfo->raw, 0L, SEEK_SET) == -1) {
  482         perror_message(txt_error_fseek, artinfo->hdr.subj);
  483         return FALSE;
  484     }
  485 
  486     /* The first task is to fixup the filename to be saved too. This is context dependent */
  487     strncpy(path, inpath, sizeof(path) - 1);
  488 /* fprintf(stderr, "save_and_process_art max=%d num_save=%d starting path=(%s) postproc=%s\n", max, num_save, path, bool_unparse(post_process)); */
  489 
  490     /*
  491      * Mailbox saves are by definition to a single file as are single file
  492      * saves. Multiple file saves append a .NNN sequence number to the path
  493      * This is backward-contemptibility with older versions of tin
  494      */
  495     if (!is_mailbox && max > 1) {
  496         const char suffixsep = '.';
  497 
  498         sprintf(&path[strlen(path)], "%c%03d", suffixsep, num_save + 1);
  499     }
  500 
  501 /* fprintf(stderr, "save_and_process_art expanded path now=(%s)\n", path); */
  502 
  503     if ((fp = open_save_filename(path, is_mailbox)) == NULL)
  504         return FALSE;
  505 
  506     if (mmdf)
  507         fprintf(fp, "%s", MMDFHDRTXT);
  508     else {
  509         if (artinfo->hdr.from)
  510             strip_name(artinfo->hdr.from, from);
  511         else /* shouldn't show up */
  512             snprintf(from, sizeof(from), "%s@%s", PATHMASTER, get_host_name());
  513 
  514         (void) time(&epoch);
  515         fprintf(fp, "From %s %s", from, ctime(&epoch));
  516         /*
  517          * TODO: add Content-Length: header when using MBOXO
  518          *       so tin actually write MBOXCL instead of MBOXO?
  519          */
  520     }
  521 
  522     /*
  523      * TODO: currently does not quote From_ line in the !mmdf case
  524      *       like append_mail() (may it break postprocessing?) but
  525      *       then at least in the (is_mailbox && !post_process && !mmdf)
  526      *       case it should be done.
  527      */
  528     if (copy_fp(artinfo->raw, fp)) /* Write tailing newline or MMDF-mailbox separator */
  529         print_art_separator_line(fp, is_mailbox);
  530     else {
  531         fclose(fp);
  532         unlink(path);
  533         return FALSE;
  534     }
  535 
  536     fclose(fp);
  537 
  538     /*
  539      * Saved ok, so fill out a save[] record
  540      */
  541     if (num_save == max_save - 1)
  542         expand_save();
  543     save[num_save].path = my_strdup(path);
  544     save[num_save].file = strrchr(save[num_save].path, DIRSEP) + 1; /* ptr to filename portion */
  545     save[num_save].mailbox = CAST_BOOL(is_mailbox);
  546 /* fprintf(stderr, "SAPA (%s) (%s) mbox=%s\n", save[num_save].path, save[num_save].file, bool_unparse(save[num_save].mailbox)); */
  547     num_save++;         /* NB: num_save is bumped here only */
  548 
  549     /*
  550      * Extract/view parts from multipart articles if required
  551      * libuu does this as part of its own processing
  552      */
  553 #ifndef HAVE_LIBUU
  554     if (post_process) {
  555 #   ifdef USE_CURSES
  556         scrollok(stdscr, TRUE);
  557 #   endif /* USE_CURSES */
  558         decode_save_mime(artinfo, TRUE);
  559 #   ifdef USE_CURSES
  560         scrollok(stdscr, FALSE);
  561 #   endif /* USE_CURSES */
  562     }
  563 #endif /* !HAVE_LIBUU */
  564 
  565     return TRUE;
  566 }
  567 
  568 
  569 /*
  570  * Create the supplied path. Create intermediate directories as needed
  571  * Don't create the last component (which would be the filename) unless the
  572  * path is / terminated.
  573  * Return FALSE if it somehow fails.
  574  */
  575 t_bool
  576 create_path(
  577     const char *path)
  578 {
  579     char *buf, *p;
  580     struct stat st;
  581 
  582     if (!strlen(path))
  583         return FALSE;
  584 
  585     buf = my_strdup(path);
  586     p = buf + 1;
  587 
  588     if (!strlen(p)) {
  589         free(buf);
  590         return FALSE;
  591     }
  592 
  593     while ((p = strchr(p, DIRSEP)) != NULL) {
  594         *p = '\0';
  595         if (stat(buf, &st) == -1) {
  596             if (my_mkdir(buf, (mode_t) (S_IRWXU|S_IRUGO|S_IXUGO)) == -1) {
  597                 if (errno != EEXIST) {
  598                     perror_message(_(txt_cannot_create), buf);
  599                     free(buf);
  600                     return FALSE;
  601                 }
  602             }
  603         }
  604         *p++ = DIRSEP;
  605     }
  606     free(buf);
  607     return TRUE;
  608 }
  609 
  610 
  611 /*
  612  * Generate semi-meaningful filename based on sequence number and
  613  * Content-(sub)type
  614  */
  615 static void
  616 generate_filename(
  617     char *buf,
  618     int buflen,
  619     const char *suffix)
  620 {
  621     static int seqno = 0;
  622 
  623     snprintf(buf, (size_t) buflen, "%s-%03d.%s", SAVEFILE_PREFIX, seqno++, suffix);
  624 }
  625 
  626 
  627 /*
  628  * Generate /save/to/path name.
  629  *
  630  * Return pointer to allocated memory which the caller must free or
  631  * NULL if something went wrong.
  632  */
  633 static char *
  634 generate_savepath(
  635     t_part *part)
  636 {
  637     char buf[2048];
  638     char *savepath;
  639     const char *name;
  640     t_bool mbox;
  641 
  642     savepath = my_malloc(PATH_LEN);
  643     /*
  644      * Get the filename to save to in 'savepath'
  645      */
  646     if ((name = get_filename(part->params)) == NULL) {
  647         char extension[NAME_LEN + 1];
  648 
  649         lookup_extension(extension, sizeof(extension), content_types[part->type], part->subtype);
  650         generate_filename(buf, sizeof(buf), extension);
  651         mbox = expand_save_filename(savepath, PATH_LEN, buf);
  652     } else
  653         mbox = expand_save_filename(savepath, PATH_LEN, name);
  654 
  655     /*
  656      * Not a good idea to dump attachments over a mailbox
  657      */
  658     if (mbox) {
  659         wait_message(2, _(txt_is_mailbox), content_types[part->type], part->subtype);
  660         free(savepath);
  661         return NULL;
  662     }
  663 
  664     if (!(create_path(savepath))) {
  665         error_message(2, _(txt_cannot_open_for_saving), savepath);
  666         free(savepath);
  667         return NULL;
  668     }
  669 
  670     return savepath;
  671 }
  672 
  673 
  674 /*
  675  * Generate a path/filename to save to, using 'path' as input.
  676  * The pathname is stored in 'outpath', which should be PATH_LEN in size
  677  * Expand metacharacters and use defaults as needed.
  678  * Return TRUE if the path is a mailbox, or FALSE otherwise.
  679  */
  680 static t_bool
  681 expand_save_filename(
  682     char *outpath,
  683     size_t outpath_len,
  684     const char *path)
  685 {
  686     char base_filename[PATH_LEN];
  687     char buf[PATH_LEN];
  688     char buf_path[PATH_LEN];
  689     int ret;
  690 
  691     /*
  692      * Make sure that externally supplied filename is a filename only and fits
  693      * into buffer
  694      */
  695     STRCPY(buf_path, path);
  696     base_name(buf_path, base_filename);
  697 
  698     /* Build default path to save to */
  699     if (!(ret = strfpath((cmdline.args & CMDLINE_SAVEDIR) ? cmdline.savedir : curr_group->attribute->savedir, buf, sizeof(buf), curr_group, FALSE)))
  700         joinpath(buf, sizeof(buf), homedir, DEFAULT_SAVEDIR);
  701 
  702     /* Join path and filename */
  703     joinpath(outpath, outpath_len, buf, base_filename);
  704 
  705     return (ret == 1);  /* should now always evaluate to FALSE */
  706 }
  707 
  708 
  709 /*
  710  * Post process the articles in save[] according to proc_type_ch
  711  * auto_delete is set if we should remove the saved files after processing
  712  * This stage can produce a fair bit of output so we allow it to
  713  * scroll up the screen rather than waste time displaying it in the
  714  * message bar
  715  */
  716 t_bool
  717 post_process_files(
  718     t_function proc_type_func,
  719     t_bool auto_delete)
  720 {
  721     if (num_save < 1)
  722         return FALSE;
  723 
  724     EndWin();
  725     Raw(FALSE);
  726     my_printf("%s%s", _(txt_post_processing), cCRLF);
  727 
  728     switch (proc_type_func) {
  729         case POSTPROCESS_SHAR:
  730             post_process_sh();
  731             break;
  732 
  733         /* This is the default, eg, with AUTOSAVE */
  734         case POSTPROCESS_YES:
  735         default:
  736             post_process_uud();
  737             break;
  738     }
  739 
  740     my_printf("%s%s%s", _(txt_post_processing_finished), cCRLF, cCRLF);
  741     my_flush();
  742 #ifdef USE_CURSES
  743     Raw(TRUE);
  744     InitWin();
  745 #endif /* USE_CURSES */
  746     prompt_continue();
  747 #ifndef USE_CURSES
  748     Raw(TRUE);
  749     InitWin();
  750 #endif /* !USE_CURSES */
  751 
  752     /*
  753      * Remove the post-processed files if required
  754      */
  755     if (auto_delete) {
  756         int i;
  757 
  758         wait_message((tinrc.beginner_level) ? 2 : 1, "%s", _(txt_deleting));
  759         cursoroff();
  760 
  761         for (i = 0; i < num_save; i++)
  762             unlink(save[i].path);
  763     }
  764 
  765     return TRUE;
  766 }
  767 
  768 
  769 /*
  770  * Two implementations .....
  771  * The LIBUU case performs multi-file decoding for uue, base64
  772  * binhex, qp. This is handled entirely during the post processing phase
  773  *
  774  * The !LIBUU case only handles multi-file uudecoding, the other MIME
  775  * types were handled using the internal MIME parser when the articles
  776  * were originally saved
  777  */
  778 #ifdef HAVE_LIBUU
  779 static void
  780 post_process_uud(
  781     void)
  782 {
  783     FILE *fp_in;
  784     char file_out_dir[PATH_LEN];
  785     const char *eptr;
  786     int i;
  787     int count;
  788     int errors = 0;
  789     uulist *item;
  790 
  791     /*
  792      * Grab the dirname portion
  793      */
  794     my_strncpy(file_out_dir, save[0].path, save[0].file - save[0].path);
  795 
  796     UUInitialize();
  797 
  798     UUSetOption(UUOPT_SAVEPATH, 0, file_out_dir);
  799     for (i = 0; i < num_save; i++) {
  800         if ((fp_in = fopen(save[i].path, "r")) != NULL) {
  801             UULoadFile(save[i].path, NULL, 0);  /* Scans file for encoded data */
  802             fclose(fp_in);
  803         }
  804     }
  805 
  806 #   if 0
  807     /*
  808      * uudeview's "intelligent" multi-part detection
  809      * From the uudeview docs: This function is a bunch of heuristics, and I
  810      * don't really trust them... should only be called as a last resort on
  811      * explicit user request
  812      */
  813     UUSmerge(0);
  814     UUSmerge(1);
  815     UUSmerge(99);
  816 #   endif /* 0 */
  817 
  818     i = count = 0;
  819     item = UUGetFileListItem(i);
  820     my_printf(cCRLF);
  821 
  822     while (item != NULL) {
  823         if (UUDecodeFile(item, NULL) == UURET_OK) {
  824             char path[PATH_LEN];
  825 
  826 /* TODO: test for multiple things per article decoded okay? */
  827             count++;
  828             my_printf(_(txt_uu_success), item->filename);
  829             my_printf(cCRLF);
  830 
  831             /* item->mimetype seems not to be available for uudecoded files etc */
  832             if (curr_group->attribute->post_process_view) {
  833                 joinpath(path, sizeof(path), file_out_dir, item->filename);
  834                 view_file(path, strrchr(path, DIRSEP) + 1);
  835             }
  836         } else {
  837             errors++;
  838             if (item->state & UUFILE_MISPART)
  839                 eptr = _(txt_libuu_error_missing);
  840             else if (item->state & UUFILE_NOBEGIN)
  841                 eptr = _(txt_libuu_error_no_begin);
  842             else if (item->state & UUFILE_NOEND)
  843                 eptr = _(txt_uu_error_no_end);
  844             else if (item->state & UUFILE_NODATA)
  845                 eptr = _(txt_libuu_error_no_data);
  846             else
  847                 eptr = _(txt_libuu_error_unknown);
  848 
  849             my_printf(_(txt_uu_error_decode), (item->filename) ? item->filename : item->subfname, eptr);
  850             my_printf(cCRLF);
  851         }
  852         i++;
  853         item = UUGetFileListItem(i);
  854         my_flush();
  855     }
  856 
  857     my_printf(_(txt_libuu_saved), count, num_save, errors, PLURAL(errors, txt_error));
  858     my_printf(cCRLF);
  859     UUCleanUp();
  860 }
  861 
  862 #else
  863 
  864 /*
  865  * Open and read all the files in save[]
  866  * Scan for uuencode BEGIN lines, decode input as we go along
  867  * uuencoded data can span multiple files, and multiple uuencoded
  868  * files are supported per batch
  869  */
  870 static void
  871 post_process_uud(
  872     void)
  873 {
  874     FILE *fp_in;
  875     FILE *fp_out = NULL;
  876     char *filename = NULL;
  877     char file_out_dir[PATH_LEN];
  878     char path[PATH_LEN];
  879     char s[LEN], t[LEN], u[LEN];
  880     int state = INITIAL;
  881     int i;
  882     mode_t mode = 0;
  883 
  884     /*
  885      * Grab the dirname portion
  886      */
  887     my_strncpy(file_out_dir, save[0].path, (size_t) (save[0].file - save[0].path));
  888 
  889     t[0] = '\0';
  890     u[0] = '\0';
  891 
  892     for (i = 0; i < num_save; i++) {
  893         if ((fp_in = fopen(save[i].path, "r")) == NULL)
  894             continue;
  895 
  896         while (fgets(s, (int) sizeof(s), fp_in) != NULL) {
  897             switch (state) {
  898                 case INITIAL:
  899                     if (strncmp("begin ", s, 6) == 0) {
  900                         char fmt[15];
  901                         char name[PATH_LEN];
  902                         char buf[PATH_LEN];
  903 
  904                         snprintf(fmt, sizeof(fmt), "%%o %%%dc\\n", PATH_LEN - 1);
  905                         if (sscanf(s + 6, fmt, &mode, name) == 2) {
  906                             strtok(name, "\n");
  907                             my_strncpy(buf, name, sizeof(buf) - 1);
  908                             str_trim(buf);
  909                             base_name(buf, name);
  910                         } else
  911                             name[0] = '\0';
  912 
  913                         if (!mode && !*name) { /* not a valid uu-file at all */
  914                             state = INITIAL;
  915                             continue;
  916                         }
  917 
  918                         if (!*name)
  919                             generate_filename(name, sizeof(name), "uue");
  920 
  921                         filename = name;
  922                         expand_save_filename(path, sizeof(path), filename);
  923                         filename = strrchr(path, DIRSEP) + 1;   /* ptr to filename portion */
  924                         if ((fp_out = fopen(path, "w")) == NULL) {
  925                             perror_message(_(txt_cannot_open), path);
  926                             fclose(fp_in);
  927                             return;
  928                         }
  929                         state = MIDDLE;
  930                     }
  931                     break;
  932 
  933                 case MIDDLE:
  934                     /*
  935                      * TODO: replace hard coded length check (uue lines are not
  936                      *       required to be 60 chars long (45 encoded chars)
  937                      *       ('M' == 60 * 3 / 4 + ' ' == 77))
  938                      */
  939                     if (s[0] == 'M')
  940                         uudecode_line(s, fp_out);
  941                     else if (STRNCMPEQ("end", s, 3)) {
  942                         state = END;
  943                         if (u[0] != 'M')
  944                             uudecode_line(u, fp_out);
  945                         if (t[0] != 'M')
  946                             uudecode_line(t, fp_out);
  947                     } else  /* end */
  948                         state = OFF;    /* OFF => a break in the uuencoded data */
  949                     break;
  950 
  951                 case OFF:
  952                     if ((s[0] == 'M') && (t[0] == 'M') && (u[0] == 'M')) {
  953                         uudecode_line(u, fp_out);
  954                         uudecode_line(t, fp_out);
  955                         uudecode_line(s, fp_out);
  956                         state = MIDDLE; /* Continue output of previously suspended data */
  957                     } else if (STRNCMPEQ("end", s, 3)) {
  958                         state = END;
  959                         if (u[0] != 'M')
  960                             uudecode_line(u, fp_out);
  961                         if (t[0] != 'M')
  962                             uudecode_line(t, fp_out);
  963                     }
  964                     break;
  965 
  966                 case END:
  967                 default:
  968                     break;
  969             }   /* switch (state) */
  970 
  971             if (state == END) {
  972                 /* set the mode after getting rid of dangerous bits */
  973                 if (!(mode &= ~(S_ISUID|S_ISGID|S_ISVTX)))
  974                     mode = (S_IRUSR|S_IWUSR);
  975 
  976 #   ifdef HAVE_FCHMOD
  977                 fchmod(fileno(fp_out), mode);
  978 #   else
  979 #       ifdef HAVE_CHMOD
  980                 chmod(path, mode);
  981 #       endif /* HAVE_CHMOD */
  982 #   endif /* HAVE_FCHMOD */
  983 
  984                 fclose(fp_out);
  985                 fp_out = NULL;
  986 
  987                 my_printf(_(txt_uu_success), filename);
  988                 my_printf(cCRLF);
  989                 sum_file(path, filename);
  990                 if (curr_group->attribute->post_process_view)
  991                     view_file(path, filename);
  992                 state = INITIAL;
  993                 continue;
  994             }
  995 
  996             strcpy(u, t);   /* Keep tabs on the last two lines, which typically do not start with M */
  997             strcpy(t, s);
  998 
  999         }   /* while (fgets) ... */
 1000 
 1001         fclose(fp_in);
 1002 
 1003     } /* for i...num_save */
 1004 
 1005     /*
 1006      * Check if we ran out of data
 1007      */
 1008     if (fp_out) {
 1009         fclose(fp_out);
 1010         my_printf(_(txt_uu_error_decode), filename, _(txt_uu_error_no_end));
 1011         my_printf(cCRLF);
 1012     }
 1013     return;
 1014 }
 1015 
 1016 
 1017 /*
 1018  * Sum file - why do we bother to do this?
 1019  * nuke code or add DONT_HAVE_PIPING -tree
 1020  */
 1021 static void
 1022 sum_file(
 1023     const char *path,
 1024     const char *file)
 1025 {
 1026 #   if defined(HAVE_SUM) && !defined(DONT_HAVE_PIPING)
 1027     FILE *fp_in;
 1028     char *ext;
 1029     char buf[LEN];
 1030 
 1031     sh_format(buf, sizeof(buf), "%s \"%s\"", DEFAULT_SUM, path);
 1032     if ((fp_in = popen(buf, "r")) != NULL) {
 1033         buf[0] = '\0';
 1034 
 1035         /*
 1036          * You can't do this with (fgets != NULL)
 1037          */
 1038         while (!feof(fp_in)) {
 1039             fgets(buf, (int) sizeof(buf), fp_in);
 1040             if ((ext = strchr(buf, '\n')) != NULL)
 1041                 *ext = '\0';
 1042         }
 1043         fflush(fp_in);
 1044         pclose(fp_in);
 1045 
 1046         my_printf(_(txt_checksum_of_file), file, file_size(path), _("bytes"));
 1047         my_printf(cCRLF);
 1048         my_printf("\t%s%s", buf, cCRLF);
 1049     } else {
 1050         my_printf(_(txt_command_failed), buf);
 1051         my_printf(cCRLF);
 1052     }
 1053     my_flush();
 1054 #   endif /* HAVE SUM && !DONT_HAVE_PIPING */
 1055 }
 1056 #endif /* HAVE_LIBUU */
 1057 
 1058 
 1059 /*
 1060  * If defined, invoke post processor command
 1061  * Create a part structure, with defaults, insert a parameter for the name
 1062  */
 1063 static void
 1064 view_file(
 1065     const char *path,
 1066     const char *file)
 1067 {
 1068     char *ext;
 1069     t_part *part;
 1070 
 1071     part = new_part(NULL);
 1072 
 1073     if ((ext = strrchr(file, '.')) != NULL)
 1074         lookup_mimetype(ext + 1, part);             /* Get MIME type/subtype */
 1075 
 1076     /*
 1077      * Needed for the mime-type processor
 1078      */
 1079     part->params = new_params();
 1080     part->params->name = my_strdup("name");
 1081     part->params->value = my_strdup(file);
 1082 
 1083     start_viewer(part, path);
 1084     my_printf(cCRLF);
 1085 
 1086     free_parts(part);
 1087 }
 1088 
 1089 
 1090 /* Single character decode. */
 1091 #define DEC(Char) (((Char) - ' ') & 077)
 1092 /*
 1093  * Decode 'buf' - write the uudecoded output to 'fp'
 1094  */
 1095 static void
 1096 uudecode_line(
 1097     const char *buf,
 1098     FILE *fp)
 1099 {
 1100     const char *p = buf;
 1101     char ch;
 1102     int n;
 1103 
 1104     n = DEC(*p);
 1105 
 1106     for (++p; n > 0; p += 4, n -= 3) {
 1107         if (n >= 3) {
 1108             ch = (char) ((DEC(p[0]) << 2) | (DEC(p[1]) >> 4));
 1109             fputc(ch, fp);
 1110             ch = (char) ((DEC(p[1]) << 4) | (DEC(p[2]) >> 2));
 1111             fputc(ch, fp);
 1112             ch = (char) ((DEC(p[2]) << 6) | DEC(p[3]));
 1113             fputc(ch, fp);
 1114         } else {
 1115             if (n >= 1) {
 1116                 ch = (char) ((DEC(p[0]) << 2) | (DEC(p[1]) >> 4));
 1117                 fputc(ch, fp);
 1118             }
 1119             if (n >= 2) {
 1120                 ch = (char) ((DEC(p[1]) << 4) | (DEC(p[2]) >> 2));
 1121                 fputc(ch, fp);
 1122             }
 1123         }
 1124     }
 1125 }
 1126 
 1127 
 1128 /*
 1129  * Unpack /bin/sh archives
 1130  * There is no end-of-shar marker so the code reads everything after
 1131  * the start marker. This is why shar is handled separately.
 1132  * The code assumes shar archives do not span articles
 1133  */
 1134 static void
 1135 post_process_sh(
 1136     void)
 1137 {
 1138     FILE *fp_in, *fp_out = NULL;
 1139     char buf[LEN];
 1140     char file_out[PATH_LEN];
 1141     char file_out_dir[PATH_LEN];
 1142     int i;
 1143 
 1144     /*
 1145      * Grab the dirname portion
 1146      */
 1147     my_strncpy(file_out_dir, save[0].path, (size_t) (save[0].file - save[0].path));
 1148     snprintf(file_out, sizeof(file_out), "%ssh%ld", file_out_dir, (long) process_id);
 1149 
 1150     for (i = 0; i < num_save; i++) {
 1151         if ((fp_in = fopen(save[i].path, "r")) == NULL)
 1152             continue;
 1153 
 1154         wait_message(0, _(txt_extracting_shar), save[i].path);
 1155 
 1156         while (fgets(buf, (int) sizeof(buf), fp_in) != NULL) {
 1157             /* find #!/bin/sh style patterns */
 1158             if ((fp_out == NULL) && match_regex_ex(buf, (int) strlen(buf), 0, 0, &shar_regex) >= 0)
 1159                 fp_out = fopen(file_out, "w");
 1160 
 1161             /* write to temp file */
 1162             if (fp_out != NULL)
 1163                 fputs(buf, fp_out);
 1164         }
 1165         fclose(fp_in);
 1166 
 1167         if (fp_out == NULL) {           /* Didn't extract any shar */
 1168             my_fputs(cCRLF, stdout);
 1169             continue;
 1170         }
 1171 
 1172         fclose(fp_out);
 1173         fp_out = NULL;
 1174         sh_format(buf, sizeof(buf), "cd %s; sh %s", file_out_dir, file_out);
 1175         my_fputs(cCRLF, stdout);
 1176         my_flush();
 1177         invoke_cmd(buf);            /* Handles its own errors */
 1178         unlink(file_out);
 1179     }
 1180 }
 1181 
 1182 
 1183 /*
 1184  * write tailing (MMDF)-mailbox separator
 1185  */
 1186 void
 1187 print_art_separator_line(
 1188     FILE *fp,
 1189     t_bool is_mailbox)
 1190 {
 1191 #ifdef DEBUG
 1192     if (debug & DEBUG_MISC)
 1193         error_message(2, "Mailbox=[%s], mailbox_format=[%s]", bool_unparse(is_mailbox), txt_mailbox_formats[tinrc.mailbox_format]);
 1194 #endif /* DEBUG */
 1195 
 1196     fprintf(fp, "%s", (is_mailbox && !strcasecmp(txt_mailbox_formats[tinrc.mailbox_format], "MMDF")) ? MMDFHDRTXT : "\n");
 1197 }
 1198 
 1199 
 1200 /*
 1201  * part needs to have at least content type/subtype and a filename
 1202  * path = full path/file (used for substitution in mailcap entries)
 1203  */
 1204 static void
 1205 start_viewer(
 1206     t_part *part,
 1207     const char *path)
 1208 {
 1209     t_mailcap *foo;
 1210 
 1211     if ((foo = get_mailcap_entry(part, path)) != NULL) {
 1212         if (foo->nametemplate)  /* honor nametemplate */
 1213             rename_file(path, foo->nametemplate);
 1214 
 1215         wait_message(0, _(txt_starting_command), foo->command);
 1216         if (foo->needsterminal) {
 1217             set_xclick_off();
 1218             fflush(stdout);
 1219         } else {
 1220             if (foo->description)
 1221                 info_message("%s", foo->description);
 1222         }
 1223         invoke_cmd(foo->command);
 1224         if (foo->needsterminal) {
 1225 #ifndef USE_CURSES
 1226             EndWin();
 1227             Raw(FALSE);
 1228 #endif /* !USE_CURSES */
 1229             prompt_continue();
 1230 #ifndef USE_CURSES
 1231             Raw(TRUE);
 1232             InitWin();
 1233 #endif /* !USE_CURSES */
 1234         }
 1235         if (foo->nametemplate) /* undo nametemplate, needed as 'save'-prompt is done outside start_viewer */
 1236             rename_file(foo->nametemplate, path);
 1237         free_mailcap(foo);
 1238     } else
 1239         wait_message(1, _(txt_no_viewer_found), content_types[part->type], part->subtype);
 1240 }
 1241 
 1242 
 1243 /*
 1244  * Decode and save the binary object pointed to in 'part'
 1245  * Optionally launch a viewer for it
 1246  * Return FALSE if Abort used to skip further viewing/saving
 1247  * or other terminal error occurs
 1248  */
 1249 static t_bool
 1250 decode_save_one(
 1251     t_part *part,
 1252     FILE *rawfp,
 1253     t_bool postproc)
 1254 {
 1255     FILE *fp;
 1256     char buf[2048], buf2[2048];
 1257     char *savepath;
 1258     int count;
 1259     int i;
 1260 
 1261     /*
 1262      * Decode this message part if appropriate
 1263      */
 1264     if (!(check_save_mime_type(part, curr_group->attribute->mime_types_to_save))) {
 1265         /* TODO: skip message if saving multiple files (e.g. save 't'agged) */
 1266         wait_message(1, "Skipped %s/%s", content_types[part->type], part->subtype); /* TODO: better msg */
 1267         return TRUE;
 1268     }
 1269 
 1270     if ((savepath = generate_savepath(part)) == NULL)
 1271         return FALSE;
 1272 
 1273     /*
 1274      * Decode/save the attachment
 1275      */
 1276     if ((fp = open_save_filename(savepath, FALSE)) == NULL) {
 1277         free(savepath);
 1278         return FALSE;
 1279     }
 1280 
 1281     if (part->encoding == ENCODING_BASE64)
 1282         mmdecode(NULL, 'b', 0, NULL);               /* flush */
 1283 
 1284     fseek(rawfp, part->offset, SEEK_SET);
 1285 
 1286     for (i = 0; i < part->line_count; i++) {
 1287         if ((fgets(buf, sizeof(buf), rawfp)) == NULL)
 1288             break;
 1289 
 1290         /* This should catch cases where people illegally append text etc */
 1291         if (buf[0] == '\0')
 1292             break;
 1293 
 1294         switch (part->encoding) {
 1295             case ENCODING_QP:
 1296             case ENCODING_BASE64:
 1297                 count = mmdecode(buf, part->encoding == ENCODING_QP ? 'q' : 'b', '\0', buf2);
 1298                 fwrite(buf2, (size_t) count, 1, fp);
 1299                 break;
 1300 
 1301             case ENCODING_UUE:
 1302                 /* TODO: if postproc, don't decode these since the traditional uudecoder will get them */
 1303                 /*
 1304                  * x-uuencode attachments have all the header info etc which we must ignore
 1305                  */
 1306                 if (strncmp(buf, "begin ", 6) != 0 && strncmp(buf, "end\n", 4) != 0 && buf[0] != '\n')
 1307                     uudecode_line(buf, fp);
 1308                 break;
 1309 
 1310             default:
 1311                 fputs(buf, fp);
 1312         }
 1313     }
 1314     fclose(fp);
 1315 
 1316     /*
 1317      * View the attachment
 1318      */
 1319     if ((postproc && curr_group->attribute->post_process_view) || !curr_group->attribute->ask_for_metamail) {
 1320         start_viewer(part, savepath);
 1321         my_printf(cCRLF);
 1322     } else {
 1323         snprintf(buf, sizeof(buf), _(txt_view_attachment), savepath, content_types[part->type], part->subtype);
 1324         if ((i = prompt_yn(buf, TRUE)) == 1)
 1325             start_viewer(part, savepath);
 1326         else if (i == -1) { /* Skip rest of attachments */
 1327             unlink(savepath);
 1328             free(savepath);
 1329             return FALSE;
 1330         }
 1331     }
 1332 
 1333     /*
 1334      * Save the attachment
 1335      */
 1336     if (postproc && curr_group->attribute->post_process_view) {
 1337         my_printf(_(txt_uu_success), savepath);
 1338         my_printf(cCRLF);
 1339     }
 1340     if (!postproc) {
 1341         snprintf(buf, sizeof(buf), _(txt_save_attachment), savepath, content_types[part->type], part->subtype);
 1342         if ((i = prompt_yn(buf, FALSE)) != 1) {
 1343             unlink(savepath);
 1344             if (i == -1) {  /* Skip rest of attachments */
 1345                 free(savepath);
 1346                 return FALSE;
 1347             }
 1348         }
 1349     }
 1350     free(savepath);
 1351     return TRUE;
 1352 }
 1353 
 1354 
 1355 enum match {
 1356     NO,
 1357     MATCH,
 1358     NOTMATCH
 1359 };
 1360 
 1361 /*
 1362  * Match a single type/subtype Content pair
 1363  * Returns:
 1364  * NO = Not matched
 1365  * MATCH = Matched
 1366  * NOTMATCH = Matched, but !negated
 1367  */
 1368 static int
 1369 match_content_type(
 1370     t_part *part,
 1371     char *type)
 1372 {
 1373     char *subtype;
 1374     int typeindex;
 1375     t_bool found = FALSE;
 1376     t_bool negate = FALSE;
 1377 
 1378     /* Check for negation */
 1379     if (*type == '!') {
 1380         if (!*(++type))             /* Invalid type */
 1381             return NO;
 1382 
 1383         negate = TRUE;
 1384     }
 1385 
 1386     /* Split type and subtype */
 1387     if ((subtype = strchr(type, '/')) == NULL)
 1388         return NO;
 1389     *(subtype++) = '\0';
 1390 
 1391     if (!*type || !*subtype)    /* Missing type or subtype */
 1392         return NO;
 1393 
 1394     /* Try and match major */
 1395     if (strcmp(type, "*") == 0)
 1396         found = TRUE;
 1397     else if (((typeindex = content_type(type)) != -1) && typeindex == part->type)
 1398         found = TRUE;
 1399 
 1400     if (!found)
 1401         return NO;
 1402 
 1403     /* Try and match subtype */
 1404     found = FALSE;
 1405     if (strcmp(subtype, "*") == 0)
 1406         found = TRUE;
 1407     else if (strcmp(subtype, part->subtype) == 0)
 1408         found = TRUE;
 1409 
 1410     if (!found)
 1411         return NO;
 1412 
 1413     /* We got a match */
 1414     if (negate)
 1415         return NOTMATCH;
 1416 
 1417     return MATCH;
 1418 }
 1419 
 1420 
 1421 /*
 1422  * See if the mime type of this part matches the list of content types to save
 1423  * or ignore. Return TRUE if there is a match
 1424  * mime_types is a comma separated list of type/subtype pairs. type and/or
 1425  * subtype can be a '*' to match any, and a pair can begin with a ! which
 1426  * will negate the meaning. We eval all pairs, the rightmost match will
 1427  * prevail
 1428  */
 1429 static t_bool
 1430 check_save_mime_type(
 1431     t_part *part,
 1432     const char *mime_types)
 1433 {
 1434     char *ptr, *pair;
 1435     int found;
 1436     int retcode;
 1437 
 1438     if (!mime_types)
 1439         return FALSE;
 1440 
 1441     ptr = my_strdup(mime_types);
 1442 
 1443     if ((pair = strtok(ptr, ",")) == NULL) {
 1444         free(ptr);
 1445         return FALSE;
 1446     }
 1447 
 1448     retcode = match_content_type(part, pair);
 1449 
 1450     while ((pair = strtok(NULL, ",")) != NULL) {
 1451         if ((found = match_content_type(part, pair)) != NO)
 1452             retcode = found;
 1453     }
 1454 
 1455     free(ptr);
 1456     return (retcode == MATCH);
 1457 }
 1458 
 1459 
 1460 /*
 1461  * decode and save binary MIME attachments from an open article context
 1462  * optionally locate and launch a viewer application
 1463  * 'postproc' determines the mode of the operation and will be set to
 1464  * TRUE when we're called during a [Ss]ave operation and FALSE when
 1465  * when just viewing
 1466  * When it is TRUE the view option will depend on post_process_view and
 1467  * the save is implicit. Feedback will also be printed.
 1468  * When it is FALSE then the view/save options will be queried
 1469  */
 1470 void
 1471 decode_save_mime(
 1472     t_openartinfo *art,
 1473     t_bool postproc)
 1474 {
 1475     t_part *ptr, *uueptr;
 1476 
 1477     /*
 1478      * Iterate over all the attachments
 1479      */
 1480     for (ptr = art->hdr.ext; ptr != NULL; ptr = ptr->next) {
 1481         /*
 1482          * Handle uuencoded sections in this message part.
 1483          * Only works when the uuencoded file is entirely within the current
 1484          * article.
 1485          * We don't do this when postprocessing as the generic uudecode code
 1486          * already handles uuencoded data, but TODO: review this
 1487          */
 1488         if (!postproc) {
 1489             for (uueptr = ptr->uue; uueptr != NULL; uueptr = uueptr->next) {
 1490                 if (!(decode_save_one(uueptr, art->raw, postproc)))
 1491                     break;
 1492             }
 1493         }
 1494 
 1495         /*
 1496          * TYPE_MULTIPART is an envelope type, don't process it.
 1497          * If we had an UUE part, the "surrounding" text/plain plays
 1498          * the role of a multipart part. Check to see if we want to
 1499          * save text and if not, skip this part.
 1500          */
 1501         /* check_save_mime_type() is done in decode_save_one() and the check for ptr->uue must be done unconditionally */
 1502         if (ptr->type == TYPE_MULTIPART || (NULL != ptr->uue /* && !check_save_mime_type(ptr, curr_group->attribute->mime_types_to_save) */ ))
 1503             continue;
 1504 
 1505         if (!(decode_save_one(ptr, art->raw, postproc)))
 1506             break;
 1507     }
 1508 }
 1509 
 1510 
 1511 /*
 1512  * Attachment menu
 1513  */
 1514 static void
 1515 show_attachment_page(
 1516     void)
 1517 {
 1518     char buf[BUFSIZ];
 1519     const char *charset;
 1520     int i, tmp_len, max_depth;
 1521     t_part *part;
 1522 
 1523     signal_context = cAttachment;
 1524     currmenu = &attmenu;
 1525     mark_offset = 0;
 1526 
 1527     if (attmenu.curr < 0)
 1528         attmenu.curr = 0;
 1529 
 1530     info_len = max_depth = 0;
 1531     for (i = 0; i < attmenu.max; ++i) {
 1532         part = get_part(i);
 1533         snprintf(buf, sizeof(buf), _(txt_attachment_lines), part->line_count);
 1534         tmp_len = strwidth(buf);
 1535         charset = get_param(part->params, "charset");
 1536         snprintf(buf, sizeof(buf), "  %s/%s, %s, %s%s", content_types[part->type], part->subtype, content_encodings[part->encoding], charset ? charset : "", charset ? ", " : "");
 1537         tmp_len += strwidth(buf);
 1538         if (tmp_len > info_len)
 1539             info_len = tmp_len;
 1540 
 1541         tmp_len = part->depth;
 1542         if (tmp_len > max_depth)
 1543             max_depth = tmp_len;
 1544     }
 1545     tmp_len = cCOLS - 13 - MIN((cCOLS - 13) / 2 + 10, max_depth * 2 + 1 + strwidth(_(txt_attachment_no_name)));
 1546     if (info_len > tmp_len)
 1547         info_len = tmp_len;
 1548 
 1549     ClearScreen();
 1550     set_first_screen_item();
 1551     center_line(0, TRUE, _(txt_attachment_menu));
 1552 
 1553     for (i = attmenu.first; i < attmenu.first + NOTESLINES && i < attmenu.max; ++i)
 1554         build_attachment_line(i);
 1555 
 1556     show_mini_help(ATTACHMENT_LEVEL);
 1557 
 1558     if (attmenu.max <= 0) {
 1559         info_message(_(txt_no_attachments));
 1560         return;
 1561     }
 1562 
 1563     draw_attachment_arrow();
 1564 }
 1565 
 1566 
 1567 void
 1568 attachment_page(
 1569     t_openartinfo *art)
 1570 {
 1571     char key[MAXKEYLEN];
 1572     t_function func;
 1573     t_menu *oldmenu = NULL;
 1574     t_part *part;
 1575 
 1576     if (currmenu)
 1577         oldmenu = currmenu;
 1578     num_of_tagged_parts = 0;
 1579     attmenu.curr = 0;
 1580     attmenu.max = build_part_list(art);
 1581     clear_note_area();
 1582     show_attachment_page();
 1583     set_xclick_off();
 1584 
 1585     forever {
 1586         switch ((func = handle_keypad(attachment_left, attachment_right, NULL, attachment_keys))) {
 1587             case GLOBAL_QUIT:
 1588                 free_part_list(part_list);
 1589                 if (oldmenu)
 1590                     currmenu = oldmenu;
 1591                 return;
 1592 
 1593             case DIGIT_1:
 1594             case DIGIT_2:
 1595             case DIGIT_3:
 1596             case DIGIT_4:
 1597             case DIGIT_5:
 1598             case DIGIT_6:
 1599             case DIGIT_7:
 1600             case DIGIT_8:
 1601             case DIGIT_9:
 1602                 if (attmenu.max)
 1603                     prompt_item_num(func_to_key(func, attachment_keys), _(txt_attachment_select));
 1604                 break;
 1605 
 1606 #ifndef NO_SHELL_ESCAPE
 1607             case GLOBAL_SHELL_ESCAPE:
 1608                 do_shell_escape();
 1609                 break;
 1610 #endif /* !NO_SHELL_ESCAPE */
 1611 
 1612             case GLOBAL_HELP:
 1613                 show_help_page(ATTACHMENT_LEVEL, _(txt_attachment_menu_com));
 1614                 show_attachment_page();
 1615                 break;
 1616 
 1617             case GLOBAL_BUGREPORT:
 1618                 bug_report();
 1619                 show_attachment_page();
 1620                 break;
 1621 
 1622             case GLOBAL_FIRST_PAGE:
 1623                 top_of_list();
 1624                 break;
 1625 
 1626             case GLOBAL_LAST_PAGE:
 1627                 end_of_list();
 1628                 break;
 1629 
 1630             case GLOBAL_REDRAW_SCREEN:
 1631                 my_retouch();
 1632                 show_attachment_page();
 1633                 break;
 1634 
 1635             case GLOBAL_LINE_DOWN:
 1636                 move_down();
 1637                 break;
 1638 
 1639             case GLOBAL_LINE_UP:
 1640                 move_up();
 1641                 break;
 1642 
 1643             case GLOBAL_PAGE_DOWN:
 1644                 page_down();
 1645                 break;
 1646 
 1647             case GLOBAL_PAGE_UP:
 1648                 page_up();
 1649                 break;
 1650 
 1651             case GLOBAL_SCROLL_DOWN:
 1652                 scroll_down();
 1653                 break;
 1654 
 1655             case GLOBAL_SCROLL_UP:
 1656                 scroll_up();
 1657                 break;
 1658 
 1659             case GLOBAL_TOGGLE_HELP_DISPLAY:
 1660                 toggle_mini_help(ATTACHMENT_LEVEL);
 1661                 show_attachment_page();
 1662                 break;
 1663 
 1664             case GLOBAL_TOGGLE_INFO_LAST_LINE:
 1665                 tinrc.info_in_last_line = bool_not(tinrc.info_in_last_line);
 1666                 show_attachment_page();
 1667                 break;
 1668 
 1669             case ATTACHMENT_SAVE:
 1670                 if (attmenu.max) {
 1671                     part = get_part(attmenu.curr);
 1672                     process_parts(part, art, num_of_tagged_parts ? SAVE_TAGGED : SAVE);
 1673                     show_attachment_page();
 1674                 }
 1675                 break;
 1676 
 1677             case ATTACHMENT_SELECT:
 1678                 if (attmenu.max) {
 1679                     part = get_part(attmenu.curr);
 1680                     process_parts(part, art, VIEW);
 1681                     show_attachment_page();
 1682                 }
 1683                 break;
 1684 
 1685             case ATTACHMENT_TAG:
 1686                 if (attmenu.max) {
 1687                     t_bool tagged;
 1688 
 1689                     tagged = tag_part(attmenu.curr);
 1690                     show_attachment_page();
 1691                     if (attmenu.curr + 1 < attmenu.max)
 1692                         move_down();
 1693                     info_message(tagged ? _(txt_attachment_tagged) : _(txt_attachment_untagged));
 1694                 }
 1695                 break;
 1696 
 1697             case ATTACHMENT_UNTAG:
 1698                 if (attmenu.max && num_of_tagged_parts) {
 1699                     untag_all_parts();
 1700                     show_attachment_page();
 1701                 }
 1702                 break;
 1703 
 1704             case ATTACHMENT_TAG_PATTERN:
 1705                 if (attmenu.max) {
 1706                     tag_pattern();
 1707                     show_attachment_page();
 1708                     info_message(_(txt_attachments_tagged), num_of_tagged_parts);
 1709                 }
 1710                 break;
 1711 
 1712             case ATTACHMENT_TOGGLE_TAGGED:
 1713                 if (attmenu.max) {
 1714                     int i;
 1715 
 1716                     for (i = attmenu.first; i < attmenu.max; ++i)
 1717                         tag_part(i);
 1718                     show_attachment_page();
 1719                     info_message(_(txt_attachments_tagged), num_of_tagged_parts);
 1720                 }
 1721                 break;
 1722 
 1723             case GLOBAL_SEARCH_SUBJECT_FORWARD:
 1724             case GLOBAL_SEARCH_SUBJECT_BACKWARD:
 1725             case GLOBAL_SEARCH_REPEAT:
 1726                 if (func == GLOBAL_SEARCH_REPEAT && last_search != GLOBAL_SEARCH_SUBJECT_FORWARD && last_search != GLOBAL_SEARCH_SUBJECT_BACKWARD)
 1727                     info_message(_(txt_no_prev_search));
 1728                 else if (attmenu.max) {
 1729                     int new_pos, old_pos = attmenu.curr;
 1730 
 1731                     new_pos = generic_search((func == GLOBAL_SEARCH_SUBJECT_FORWARD), (func == GLOBAL_SEARCH_REPEAT), attmenu.curr, attmenu.max - 1, ATTACHMENT_LEVEL);
 1732                     if (new_pos != old_pos)
 1733                         move_to_item(new_pos);
 1734                 }
 1735                 break;
 1736 
 1737 #ifndef DONT_HAVE_PIPING
 1738             case ATTACHMENT_PIPE:
 1739             case GLOBAL_PIPE:
 1740                 if (attmenu.max) {
 1741                     part = get_part(attmenu.curr);
 1742                     process_parts(part, art, func == GLOBAL_PIPE ? PIPE_RAW : PIPE);
 1743                     show_attachment_page();
 1744                 }
 1745                 break;
 1746 #endif /* !DONT_HAVE_PIPING */
 1747 
 1748             default:
 1749                 info_message(_(txt_bad_command), PrintFuncKey(key, GLOBAL_HELP, attachment_keys));
 1750                 break;
 1751         }
 1752     }
 1753 }
 1754 
 1755 
 1756 static t_function
 1757 attachment_left(
 1758     void)
 1759 {
 1760     return GLOBAL_QUIT;
 1761 }
 1762 
 1763 
 1764 static t_function
 1765 attachment_right(
 1766     void)
 1767 {
 1768     return ATTACHMENT_SELECT;
 1769 }
 1770 
 1771 
 1772 static void
 1773 draw_attachment_arrow(
 1774     void)
 1775 {
 1776     draw_arrow_mark(INDEX_TOP + attmenu.curr - attmenu.first);
 1777     if (tinrc.info_in_last_line) {
 1778         const char *name;
 1779         t_part *part;
 1780 
 1781         part = get_part(attmenu.curr);
 1782         name = get_filename(part->params);
 1783         info_message("%s  %s", name ? name : _(txt_attachment_no_name), BlankIfNull(part->description));
 1784     } else if (attmenu.curr == attmenu.max - 1)
 1785         info_message(_(txt_end_of_attachments));
 1786 }
 1787 
 1788 
 1789 static void
 1790 build_attachment_line(
 1791     int i)
 1792 {
 1793     char *sptr;
 1794     const char *name;
 1795     const char *charset;
 1796 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1797     char *tmpname;
 1798     char *tmpbuf;
 1799 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1800     char buf[BUFSIZ];
 1801     char buf2[BUFSIZ];
 1802     char *tree = NULL;
 1803     int len, namelen, tagged, treelen;
 1804     t_part *part;
 1805 
 1806 #ifdef USE_CURSES
 1807     /*
 1808      * Allocate line buffer
 1809      * make it the same size like in !USE_CURSES case to simplify some code
 1810      */
 1811 #   if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1812         sptr = my_malloc(cCOLS * MB_CUR_MAX + 2);
 1813 #   else
 1814         sptr = my_malloc(cCOLS + 2);
 1815 #   endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1816 #else
 1817     sptr = screen[INDEX2SNUM(i)].col;
 1818 #endif /* USE_CURSES */
 1819 
 1820     part = get_part(i);
 1821     namelen = MIN(cCOLS - 13 - info_len - 8, strwidth(_(txt_attachment_no_name)));
 1822     tagged = get_tagged(i);
 1823 
 1824     if (!(name = get_filename(part->params))) {
 1825         if (!(name = part->description))
 1826             name = _(txt_attachment_no_name);
 1827     }
 1828 
 1829     charset = get_param(part->params, "charset");
 1830     snprintf(buf2, sizeof(buf2), _(txt_attachment_lines), part->line_count);
 1831     /* TODO: make the layout configurable? */
 1832     if (!strcmp(content_types[part->type], "text"))
 1833         snprintf(buf, sizeof(buf), "  %s/%s, %s, %s%s%s", content_types[part->type], part->subtype, content_encodings[part->encoding], charset ? charset : "", charset ? ", " : "", buf2);
 1834     else
 1835         snprintf(buf, sizeof(buf), "  %s/%s, %s, %s", content_types[part->type], part->subtype, content_encodings[part->encoding], buf2);
 1836     if (part->depth > 0) {
 1837         treelen = cCOLS - 13 - info_len - namelen;
 1838         tree = build_tree(part->depth, treelen, i);
 1839     }
 1840     snprintf(buf2, sizeof(buf2), "%s  %s", tagged ? tin_ltoa(tagged, 3) : "   ", BlankIfNull(tree));
 1841     FreeIfNeeded(tree);
 1842     len = strwidth(buf2);
 1843     if (namelen + len + info_len + 8 <= cCOLS)
 1844         namelen = cCOLS - 8 - info_len - len;
 1845 
 1846 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1847     tmpname = spart(name, namelen, TRUE);
 1848     tmpbuf = spart(buf, info_len, TRUE);
 1849     snprintf(sptr, (size_t) cCOLS * MB_CUR_MAX, "  %s %s%*s%*s%s", tin_ltoa(i + 1, 4), buf2, namelen, BlankIfNull(tmpname), info_len, BlankIfNull(tmpbuf), cCRLF);
 1850     FreeIfNeeded(tmpname);
 1851     FreeIfNeeded(tmpbuf);
 1852 #else
 1853     snprintf(sptr, cCOLS, "  %s %s%-*.*s%*.*s%s", tin_ltoa(i + 1, 4), buf2, namelen, namelen, name, info_len, info_len, buf, cCRLF);
 1854 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1855 
 1856     WriteLine(INDEX2LNUM(i), sptr);
 1857 
 1858 #ifdef USE_CURSES
 1859     free(sptr);
 1860 #endif /* USE_CURSES */
 1861 }
 1862 
 1863 
 1864 /*
 1865  * Build attachment tree. Code adopted
 1866  * from thread.c:make_prefix().
 1867  */
 1868 static char *
 1869 build_tree(
 1870     int depth,
 1871     int maxlen,
 1872     int i)
 1873 {
 1874 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1875     char *result;
 1876     wchar_t *tree;
 1877 #else
 1878     char *tree;
 1879 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1880     int prefix_ptr, tmpdepth;
 1881     int depth_level = 0;
 1882     t_bool found = FALSE;
 1883     t_partl *lptr, *lptr2;
 1884 
 1885     lptr2 = find_part(i);
 1886     prefix_ptr = depth * 2 - 1;
 1887     if (prefix_ptr > maxlen - 1 - !(maxlen % 2)) {
 1888         int odd = ((maxlen % 2) ? 0 : 1);
 1889 
 1890         prefix_ptr -= maxlen - ++depth_level - 2 - odd;
 1891         while (prefix_ptr > maxlen - 2 - odd) {
 1892             if (depth_level < maxlen / 5)
 1893                 depth_level++;
 1894 
 1895             prefix_ptr -= maxlen - depth_level - 2 - odd;
 1896             odd = (odd ? 0 : 1);
 1897         }
 1898     }
 1899 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1900     tree = my_malloc(sizeof(wchar_t) * (size_t) prefix_ptr + 3 * sizeof(wchar_t));
 1901     tree[prefix_ptr + 2] = (wchar_t) '\0';
 1902 #else
 1903     tree = my_malloc(prefix_ptr + 3);
 1904     tree[prefix_ptr + 2] = '\0';
 1905 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1906     tree[prefix_ptr + 1] = TREE_ARROW;
 1907     tree[prefix_ptr] = TREE_HORIZ;
 1908     for (lptr = lptr2->next; lptr != NULL; lptr = lptr->next) {
 1909         if (lptr->part->depth == depth) {
 1910             found = TRUE;
 1911             break;
 1912         }
 1913         if (lptr->part->depth < depth)
 1914             break;
 1915     }
 1916     tree[--prefix_ptr] = found ? TREE_VERT_RIGHT : TREE_UP_RIGHT;
 1917     found = FALSE;
 1918     for (tmpdepth = depth - 1; prefix_ptr > 1; --tmpdepth) {
 1919         for (lptr = lptr2->next; lptr != NULL; lptr = lptr->next) {
 1920             if (lptr->part->depth == tmpdepth) {
 1921                 found = TRUE;
 1922                 break;
 1923             }
 1924             if (lptr->part->depth < tmpdepth)
 1925                 break;
 1926         }
 1927         tree[--prefix_ptr] = TREE_BLANK;
 1928         tree[--prefix_ptr] = found ? TREE_VERT : TREE_BLANK;
 1929         found = FALSE;
 1930     }
 1931     while (depth_level)
 1932         tree[--depth_level] = TREE_ARROW_WRAP;
 1933 
 1934 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1935     result = wchar_t2char(tree);
 1936     free(tree);
 1937     return result;
 1938 #else
 1939     return tree;
 1940 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1941 }
 1942 
 1943 
 1944 /*
 1945  * Find nth attachment in part_list.
 1946  * Return pointer to that part.
 1947  */
 1948 static t_partl *
 1949 find_part(
 1950     int n)
 1951 {
 1952     t_partl *lptr;
 1953 
 1954     lptr = part_list;
 1955     if (attmenu.max >= 1)
 1956         lptr = lptr->next;
 1957 
 1958     while (n-- > 0 && lptr->next)
 1959         lptr = lptr->next;
 1960 
 1961     return lptr;
 1962 }
 1963 
 1964 
 1965 t_part *
 1966 get_part(
 1967     int n)
 1968 {
 1969     t_partl *lptr;
 1970 
 1971     lptr = find_part(n);
 1972     return lptr->part;
 1973 }
 1974 
 1975 
 1976 static void
 1977 tag_pattern(
 1978     void)
 1979 {
 1980     char buf[BUFSIZ];
 1981     char pat[128];
 1982     char *prompt;
 1983     const char *name;
 1984     const char *charset;
 1985     struct regex_cache cache = REGEX_CACHE_INITIALIZER;
 1986     t_part *part;
 1987     t_partl *lptr;
 1988 
 1989 #if 0
 1990     if (num_of_tagged_parts)
 1991         untag_all_parts();
 1992 #endif /* 0 */
 1993 
 1994     prompt = fmt_string(_(txt_select_pattern), tinrc.default_select_pattern);
 1995     if (!(prompt_string_default(prompt, tinrc.default_select_pattern, _(txt_info_no_previous_expression), HIST_SELECT_PATTERN))) {
 1996         free(prompt);
 1997         return;
 1998     }
 1999     free(prompt);
 2000 
 2001     if (STRCMPEQ(tinrc.default_select_pattern, "*")) {  /* all */
 2002         if (tinrc.wildcard)
 2003             STRCPY(pat, ".*");
 2004         else
 2005             STRCPY(pat, tinrc.default_select_pattern);
 2006     } else
 2007         snprintf(pat, sizeof(pat), REGEX_FMT, tinrc.default_select_pattern);
 2008 
 2009     if (tinrc.wildcard && !(compile_regex(pat, &cache, REGEX_CASELESS)))
 2010         return;
 2011 
 2012     lptr = find_part(0);
 2013 
 2014     for (; lptr != NULL; lptr = lptr->next) {
 2015         part = lptr->part;
 2016         if (!(name = get_filename(part->params))) {
 2017             if (!(name = part->description))
 2018                 name = _(txt_attachment_no_name);
 2019         }
 2020         charset = get_param(part->params, "charset");
 2021 
 2022         snprintf(buf, sizeof(buf), "%s %s/%s %s, %s", name, content_types[part->type], part->subtype, content_encodings[part->encoding], charset ? charset : "");
 2023 
 2024         if (!match_regex(buf, pat, &cache, TRUE))
 2025             continue;
 2026 
 2027         if (!lptr->tagged)
 2028             lptr->tagged = ++num_of_tagged_parts;
 2029     }
 2030 
 2031     if (tinrc.wildcard) {
 2032         regex_cache_destroy(&cache);
 2033     }
 2034 }
 2035 
 2036 
 2037 static int
 2038 get_tagged(
 2039     int n)
 2040 {
 2041     t_partl *lptr;
 2042 
 2043     lptr = find_part(n);
 2044     return lptr->tagged;
 2045 }
 2046 
 2047 
 2048 static t_bool
 2049 tag_part(
 2050     int n)
 2051 {
 2052     t_partl *lptr;
 2053 
 2054     lptr = find_part(n);
 2055     if (lptr->tagged) {
 2056         untag_part(n);
 2057         return FALSE;
 2058     } else {
 2059         lptr->tagged = ++num_of_tagged_parts;
 2060         return TRUE;
 2061     }
 2062 }
 2063 
 2064 
 2065 static void
 2066 untag_part(
 2067     int n)
 2068 {
 2069     int i;
 2070     t_partl *curr_part, *lptr;
 2071 
 2072     lptr = find_part(0);
 2073     curr_part = find_part(n);
 2074     i = attmenu.max;
 2075 
 2076     while (i-- > 0 && lptr) {
 2077         if (lptr->tagged > curr_part->tagged)
 2078             --lptr->tagged;
 2079         lptr = lptr->next;
 2080     }
 2081 
 2082     curr_part->tagged = 0;
 2083     --num_of_tagged_parts;
 2084 }
 2085 
 2086 
 2087 static void
 2088 untag_all_parts(
 2089     void)
 2090 {
 2091     t_partl *lptr = part_list;
 2092 
 2093     while (lptr) {
 2094         if (lptr->tagged)
 2095             lptr->tagged = 0;
 2096 
 2097         lptr = lptr->next;
 2098     }
 2099     num_of_tagged_parts = 0;
 2100 }
 2101 
 2102 
 2103 /*
 2104  * Build a linked list which holds pointers to the parts we want deal with.
 2105  */
 2106 static int
 2107 build_part_list(
 2108     t_openartinfo *art)
 2109 {
 2110     int i = 0;
 2111     t_part *ptr, *uueptr;
 2112     t_partl *lptr;
 2113 
 2114     part_list = my_malloc(sizeof(t_partl));
 2115     lptr = part_list;
 2116     lptr->part = art->hdr.ext;
 2117     lptr->next = NULL;
 2118     lptr->tagged = 0;
 2119     for (ptr = art->hdr.ext; ptr != NULL; ptr = ptr->next) {
 2120         if ((uueptr = ptr->uue) != NULL) {
 2121             lptr->next = my_malloc(sizeof(t_partl));
 2122             lptr->next->part = ptr;
 2123             lptr->next->next = NULL;
 2124             lptr->next->tagged = 0;
 2125             lptr = lptr->next;
 2126             ++i;
 2127             for (; uueptr != NULL; uueptr = uueptr->next) {
 2128                 lptr->next = my_malloc(sizeof(t_partl));
 2129                 lptr->next->part = uueptr;
 2130                 lptr->next->next = NULL;
 2131                 lptr->next->tagged = 0;
 2132                 lptr = lptr->next;
 2133                 ++i;
 2134             }
 2135         }
 2136 
 2137         if (ptr->uue)
 2138             continue;
 2139 
 2140         lptr->next = my_malloc(sizeof(t_partl));
 2141         lptr->next->part = ptr;
 2142         lptr->next->next = NULL;
 2143         lptr->next->tagged = 0;
 2144         lptr = lptr->next;
 2145         ++i;
 2146     }
 2147     return i;
 2148 }
 2149 
 2150 
 2151 static void
 2152 free_part_list(
 2153     t_partl *list)
 2154 {
 2155     while (list->next != NULL) {
 2156         free_part_list(list->next);
 2157         list->next = NULL;
 2158     }
 2159     free(list);
 2160 }
 2161 
 2162 
 2163 static void
 2164 process_parts(
 2165     t_part *part,
 2166     t_openartinfo *art,
 2167     enum action what)
 2168 {
 2169     FILE *fp;
 2170     char *savepath = NULL, *tmppath;
 2171     int i, saved_parts = 0;
 2172     t_partl *lptr;
 2173 
 2174     switch (what) {
 2175         case SAVE_TAGGED:
 2176             for (i = 1; i <= num_of_tagged_parts; i++) {
 2177                 lptr = part_list;
 2178 
 2179                 while (lptr) {
 2180                     if (lptr->tagged == i) {
 2181                         if ((savepath = generate_savepath(lptr->part)) == NULL)
 2182                             return;
 2183 
 2184                         if ((fp = open_save_filename(savepath, FALSE)) == NULL) {
 2185                             free(savepath);
 2186                             return;
 2187                         }
 2188                         process_part(lptr->part, art, fp, NULL, SAVE);
 2189                         free(savepath);
 2190                         ++saved_parts;
 2191                     }
 2192                     lptr = lptr->next;
 2193                 }
 2194             }
 2195             break;
 2196 
 2197         default:
 2198             if ((tmppath = generate_savepath(part)) == NULL)
 2199                 return;
 2200 
 2201             if (what == SAVE)
 2202                 savepath = tmppath;
 2203             else {
 2204                 savepath = get_tmpfilename(tmppath);
 2205                 free(tmppath);
 2206             }
 2207             if ((fp = open_save_filename(savepath, FALSE)) == NULL) {
 2208                 free(savepath);
 2209                 return;
 2210             }
 2211             process_part(part, art, fp, savepath, what);
 2212             break;
 2213     }
 2214     switch (what) {
 2215         case SAVE_TAGGED:
 2216             wait_message(2, _(txt_attachments_saved), saved_parts, num_of_tagged_parts);
 2217             break;
 2218 
 2219         case SAVE:
 2220             wait_message(2, _(txt_attachment_saved), savepath);
 2221             free(savepath);
 2222             break;
 2223 
 2224         default:
 2225             unlink(savepath);
 2226             free(savepath);
 2227             break;
 2228     }
 2229     cursoroff();
 2230 }
 2231 
 2232 
 2233 /*
 2234  * VIEW/PIPE/SAVE the given part.
 2235  *
 2236  * PIPE_RAW uses the raw part, otherwise the part is decoded first.
 2237  */
 2238 static void
 2239 process_part(
 2240     t_part *part,
 2241     t_openartinfo *art,
 2242     FILE *outfile,
 2243     const char *savepath,
 2244     enum action what)
 2245 {
 2246     FILE *infile;
 2247     char buf[2048], buf2[2048];
 2248     int count;
 2249     int i, line_count;
 2250 #ifdef CHARSET_CONVERSION
 2251     char *conv_buf;
 2252     const char *network_charset;
 2253     size_t line_len;
 2254 #endif /* CHARSET_CONVERSION */
 2255 
 2256     /*
 2257      * uuencoded parts must be read from the cooked article,
 2258      * otherwise they might be additionally encoded with b64 or qp
 2259      */
 2260     if (part->encoding == ENCODING_UUE)
 2261         infile = art->cooked;
 2262     else
 2263         infile = art->raw;
 2264 
 2265     if (what != PIPE_RAW && part->encoding == ENCODING_BASE64)
 2266         mmdecode(NULL, 'b', 0, NULL);               /* flush */
 2267 
 2268     fseek(infile, part->offset, SEEK_SET);
 2269 
 2270     line_count = part->line_count;
 2271 
 2272     for (i = 0; i < line_count; i++) {
 2273         if ((fgets(buf, sizeof(buf), infile)) == NULL)
 2274             break;
 2275 
 2276         /* This should catch cases where people illegally append text etc */
 2277         if (buf[0] == '\0')
 2278             break;
 2279 
 2280         /*
 2281          * page.c:new_uue() sets offset to the 'begin ...' line
 2282          * -> skip over the first line in uuencoded parts
 2283          */
 2284         if (part->encoding == ENCODING_UUE && i == 0) {
 2285             ++line_count;
 2286             continue;
 2287         }
 2288 
 2289         if (what != PIPE_RAW) {
 2290             switch (part->encoding) {
 2291                 case ENCODING_QP:
 2292                 case ENCODING_BASE64:
 2293 #ifdef CHARSET_CONVERSION
 2294                     memset(buf2, '\0', sizeof(buf2));
 2295 #endif /* CHARSET_CONVERSION */
 2296                     if ((count = mmdecode(buf, part->encoding == ENCODING_QP ? 'q' : 'b', '\0', buf2)) > 0) {
 2297 #ifdef CHARSET_CONVERSION
 2298                         if (what != SAVE && what != SAVE_TAGGED && !strncmp(content_types[part->type], "text", 4)) {
 2299                             line_len = (size_t) count;
 2300                             conv_buf = my_strdup(buf2);
 2301                             network_charset = get_param(part->params, "charset");
 2302                             process_charsets(&conv_buf, &line_len, network_charset ? network_charset : "US-ASCII", tinrc.mm_local_charset, FALSE);
 2303                             strncpy(buf2, conv_buf, sizeof(buf2) - 1);
 2304                             count = (int) strlen(buf2);
 2305                             free(conv_buf);
 2306                         }
 2307 #endif /* CHARSET_CONVERSION */
 2308                         fwrite(buf2, (size_t) count, 1, outfile);
 2309                     }
 2310                     break;
 2311 
 2312                 case ENCODING_UUE:
 2313                     /* TODO: if postproc, don't decode these since the traditional uudecoder will get them */
 2314                     /*
 2315                      * x-uuencode attachments have all the header info etc which we must ignore
 2316                      */
 2317                     if (strncmp(buf, "begin ", 6) != 0 && strncmp(buf, "end\n", 4) != 0 && buf[0] != '\n')
 2318                         uudecode_line(buf, outfile);
 2319                     break;
 2320 
 2321                 default:
 2322 #ifdef CHARSET_CONVERSION
 2323                         if (what != SAVE && what != SAVE_TAGGED && !strncmp(content_types[part->type], "text", 4)) {
 2324                             conv_buf = my_strdup(buf);
 2325                             line_len = strlen(conv_buf);
 2326                             network_charset = get_param(part->params, "charset");
 2327                             process_charsets(&conv_buf, &line_len, network_charset ? network_charset : "US-ASCII", tinrc.mm_local_charset, FALSE);
 2328                             strncpy(buf, conv_buf, sizeof(buf) - 1);
 2329                             free(conv_buf);
 2330                         }
 2331 #endif /* CHARSET_CONVERSION */
 2332                     fputs(buf, outfile);
 2333             }
 2334         } else
 2335             fputs(buf, outfile);
 2336     }
 2337 
 2338     fclose(outfile);
 2339 
 2340     switch (what) {
 2341         case VIEW:
 2342             start_viewer(part, savepath);
 2343             break;
 2344 
 2345 #ifndef DONT_HAVE_PIPING
 2346         case PIPE:
 2347         case PIPE_RAW:
 2348             pipe_part(savepath);
 2349             break;
 2350 #endif /* !DONT_HAVE_PIPING */
 2351 
 2352         default:
 2353             break;
 2354     }
 2355 }
 2356 
 2357 
 2358 #ifndef DONT_HAVE_PIPING
 2359 static void
 2360 pipe_part(
 2361     const char *savepath)
 2362 {
 2363     FILE *fp, *pipe_fp;
 2364     char *prompt;
 2365 
 2366     prompt = fmt_string(_(txt_pipe_to_command), (size_t) cCOLS - (strlen(_(txt_pipe_to_command)) + 30), tinrc.default_pipe_command);
 2367     if (!(prompt_string_default(prompt, tinrc.default_pipe_command, _(txt_no_command), HIST_PIPE_COMMAND))) {
 2368         free(prompt);
 2369         return;
 2370     }
 2371     free(prompt);
 2372     if ((fp = fopen(savepath, "r")) == NULL)
 2373         /* TODO: error message? */
 2374         return;
 2375     EndWin();
 2376     Raw(FALSE);
 2377     fflush(stdout);
 2378     set_signal_catcher(FALSE);
 2379     if ((pipe_fp = popen(tinrc.default_pipe_command, "w")) == NULL) {
 2380         perror_message(_(txt_command_failed), tinrc.default_pipe_command);
 2381         set_signal_catcher(TRUE);
 2382         Raw(TRUE);
 2383         InitWin();
 2384         fclose(fp);
 2385         return;
 2386     }
 2387     copy_fp(fp, pipe_fp);
 2388     if (errno == EPIPE)
 2389         perror_message(_(txt_command_failed), tinrc.default_pipe_command);
 2390     fflush(pipe_fp);
 2391     (void) pclose(pipe_fp);
 2392     set_signal_catcher(TRUE);
 2393     fclose(fp);
 2394 #   ifdef USE_CURSES
 2395     Raw(TRUE);
 2396     InitWin();
 2397 #   endif /* USE_CURSES */
 2398     prompt_continue();
 2399 #   ifndef USE_CURSES
 2400     Raw(TRUE);
 2401     InitWin();
 2402 #   endif /* !USE_CURSES */
 2403 }
 2404 #endif /* !DONT_HAVE_PIPING */