"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.6.1/src/save.c" (22 Dec 2021, 58730 Bytes) of package /linux/misc/tin-2.6.1.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.0_vs_2.6.1.

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : save.c
    4  *  Author    : I. Lea & R. Skrenta
    5  *  Created   : 1991-04-01
    6  *  Updated   : 2021-02-25
    7  *  Notes     :
    8  *
    9  * Copyright (c) 1991-2022 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) && pcre_exec(shar_regex.re, shar_regex.extra, buf, (int) strlen(buf), 0, 0, NULL, 0) >= 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         negate = TRUE;
 1381         ++type;
 1382 
 1383         if (!*type)             /* Invalid type */
 1384             return NO;
 1385     }
 1386 
 1387     /* Split type and subtype */
 1388     if ((subtype = strchr(type, '/')) == NULL)
 1389         return NO;
 1390     *(subtype++) = '\0';
 1391 
 1392     if (!*type || !*subtype)    /* Missing type or subtype */
 1393         return NO;
 1394 
 1395     /* Try and match major */
 1396     if (strcmp(type, "*") == 0)
 1397         found = TRUE;
 1398     else if (((typeindex = content_type(type)) != -1) && typeindex == part->type)
 1399         found = TRUE;
 1400 
 1401     if (!found)
 1402         return NO;
 1403 
 1404     /* Try and match subtype */
 1405     found = FALSE;
 1406     if (strcmp(subtype, "*") == 0)
 1407         found = TRUE;
 1408     else if (strcmp(subtype, part->subtype) == 0)
 1409         found = TRUE;
 1410 
 1411     if (!found)
 1412         return NO;
 1413 
 1414     /* We got a match */
 1415     if (negate)
 1416         return NOTMATCH;
 1417 
 1418     return MATCH;
 1419 }
 1420 
 1421 
 1422 /*
 1423  * See if the mime type of this part matches the list of content types to save
 1424  * or ignore. Return TRUE if there is a match
 1425  * mime_types is a comma separated list of type/subtype pairs. type and/or
 1426  * subtype can be a '*' to match any, and a pair can begin with a ! which
 1427  * will negate the meaning. We eval all pairs, the rightmost match will
 1428  * prevail
 1429  */
 1430 static t_bool
 1431 check_save_mime_type(
 1432     t_part *part,
 1433     const char *mime_types)
 1434 {
 1435     char *ptr, *pair;
 1436     int found;
 1437     int retcode;
 1438 
 1439     if (!mime_types)
 1440         return FALSE;
 1441 
 1442     ptr = my_strdup(mime_types);
 1443 
 1444     if ((pair = strtok(ptr, ",")) == NULL) {
 1445         free(ptr);
 1446         return FALSE;
 1447     }
 1448 
 1449     retcode = match_content_type(part, pair);
 1450 
 1451     while ((pair = strtok(NULL, ",")) != NULL) {
 1452         if ((found = match_content_type(part, pair)) != NO)
 1453             retcode = found;
 1454     }
 1455 
 1456     free(ptr);
 1457     return (retcode == MATCH);
 1458 }
 1459 
 1460 
 1461 /*
 1462  * decode and save binary MIME attachments from an open article context
 1463  * optionally locate and launch a viewer application
 1464  * 'postproc' determines the mode of the operation and will be set to
 1465  * TRUE when we're called during a [Ss]ave operation and FALSE when
 1466  * when just viewing
 1467  * When it is TRUE the view option will depend on post_process_view and
 1468  * the save is implicit. Feedback will also be printed.
 1469  * When it is FALSE then the view/save options will be queried
 1470  */
 1471 void
 1472 decode_save_mime(
 1473     t_openartinfo *art,
 1474     t_bool postproc)
 1475 {
 1476     t_part *ptr, *uueptr;
 1477 
 1478     /*
 1479      * Iterate over all the attachments
 1480      */
 1481     for (ptr = art->hdr.ext; ptr != NULL; ptr = ptr->next) {
 1482         /*
 1483          * Handle uuencoded sections in this message part.
 1484          * Only works when the uuencoded file is entirely within the current
 1485          * article.
 1486          * We don't do this when postprocessing as the generic uudecode code
 1487          * already handles uuencoded data, but TODO: review this
 1488          */
 1489         if (!postproc) {
 1490             for (uueptr = ptr->uue; uueptr != NULL; uueptr = uueptr->next) {
 1491                 if (!(decode_save_one(uueptr, art->raw, postproc)))
 1492                     break;
 1493             }
 1494         }
 1495 
 1496         /*
 1497          * TYPE_MULTIPART is an envelope type, don't process it.
 1498          * If we had an UUE part, the "surrounding" text/plain plays
 1499          * the role of a multipart part. Check to see if we want to
 1500          * save text and if not, skip this part.
 1501          */
 1502         /* check_save_mime_type() is done in decode_save_one() and the check for ptr->uue must be done unconditionally */
 1503         if (ptr->type == TYPE_MULTIPART || (NULL != ptr->uue /* && !check_save_mime_type(ptr, curr_group->attribute->mime_types_to_save) */ ))
 1504             continue;
 1505 
 1506         if (!(decode_save_one(ptr, art->raw, postproc)))
 1507             break;
 1508     }
 1509 }
 1510 
 1511 
 1512 /*
 1513  * Attachment menu
 1514  */
 1515 static void
 1516 show_attachment_page(
 1517     void)
 1518 {
 1519     char buf[BUFSIZ];
 1520     const char *charset;
 1521     int i, tmp_len, max_depth;
 1522     t_part *part;
 1523 
 1524     signal_context = cAttachment;
 1525     currmenu = &attmenu;
 1526     mark_offset = 0;
 1527 
 1528     if (attmenu.curr < 0)
 1529         attmenu.curr = 0;
 1530 
 1531     info_len = max_depth = 0;
 1532     for (i = 0; i < attmenu.max; ++i) {
 1533         part = get_part(i);
 1534         snprintf(buf, sizeof(buf), _(txt_attachment_lines), part->line_count);
 1535         tmp_len = strwidth(buf);
 1536         charset = get_param(part->params, "charset");
 1537         snprintf(buf, sizeof(buf), "  %s/%s, %s, %s%s", content_types[part->type], part->subtype, content_encodings[part->encoding], charset ? charset : "", charset ? ", " : "");
 1538         tmp_len += strwidth(buf);
 1539         if (tmp_len > info_len)
 1540             info_len = tmp_len;
 1541 
 1542         tmp_len = part->depth;
 1543         if (tmp_len > max_depth)
 1544             max_depth = tmp_len;
 1545     }
 1546     tmp_len = cCOLS - 13 - MIN((cCOLS - 13) / 2 + 10, max_depth * 2 + 1 + strwidth(_(txt_attachment_no_name)));
 1547     if (info_len > tmp_len)
 1548         info_len = tmp_len;
 1549 
 1550     ClearScreen();
 1551     set_first_screen_item();
 1552     center_line(0, TRUE, _(txt_attachment_menu));
 1553 
 1554     for (i = attmenu.first; i < attmenu.first + NOTESLINES && i < attmenu.max; ++i)
 1555         build_attachment_line(i);
 1556 
 1557     show_mini_help(ATTACHMENT_LEVEL);
 1558 
 1559     if (attmenu.max <= 0) {
 1560         info_message(_(txt_no_attachments));
 1561         return;
 1562     }
 1563 
 1564     draw_attachment_arrow();
 1565 }
 1566 
 1567 
 1568 void
 1569 attachment_page(
 1570     t_openartinfo *art)
 1571 {
 1572     char key[MAXKEYLEN];
 1573     t_function func;
 1574     t_menu *oldmenu = NULL;
 1575     t_part *part;
 1576 
 1577     if (currmenu)
 1578         oldmenu = currmenu;
 1579     num_of_tagged_parts = 0;
 1580     attmenu.curr = 0;
 1581     attmenu.max = build_part_list(art);
 1582     clear_note_area();
 1583     show_attachment_page();
 1584     set_xclick_off();
 1585 
 1586     forever {
 1587         switch ((func = handle_keypad(attachment_left, attachment_right, NULL, attachment_keys))) {
 1588             case GLOBAL_QUIT:
 1589                 free_part_list(part_list);
 1590                 if (oldmenu)
 1591                     currmenu = oldmenu;
 1592                 return;
 1593 
 1594             case DIGIT_1:
 1595             case DIGIT_2:
 1596             case DIGIT_3:
 1597             case DIGIT_4:
 1598             case DIGIT_5:
 1599             case DIGIT_6:
 1600             case DIGIT_7:
 1601             case DIGIT_8:
 1602             case DIGIT_9:
 1603                 if (attmenu.max)
 1604                     prompt_item_num(func_to_key(func, attachment_keys), _(txt_attachment_select));
 1605                 break;
 1606 
 1607 #ifndef NO_SHELL_ESCAPE
 1608             case GLOBAL_SHELL_ESCAPE:
 1609                 do_shell_escape();
 1610                 break;
 1611 #endif /* !NO_SHELL_ESCAPE */
 1612 
 1613             case GLOBAL_HELP:
 1614                 show_help_page(ATTACHMENT_LEVEL, _(txt_attachment_menu_com));
 1615                 show_attachment_page();
 1616                 break;
 1617 
 1618             case GLOBAL_BUGREPORT:
 1619                 bug_report();
 1620                 show_attachment_page();
 1621                 break;
 1622 
 1623             case GLOBAL_FIRST_PAGE:
 1624                 top_of_list();
 1625                 break;
 1626 
 1627             case GLOBAL_LAST_PAGE:
 1628                 end_of_list();
 1629                 break;
 1630 
 1631             case GLOBAL_REDRAW_SCREEN:
 1632                 my_retouch();
 1633                 show_attachment_page();
 1634                 break;
 1635 
 1636             case GLOBAL_LINE_DOWN:
 1637                 move_down();
 1638                 break;
 1639 
 1640             case GLOBAL_LINE_UP:
 1641                 move_up();
 1642                 break;
 1643 
 1644             case GLOBAL_PAGE_DOWN:
 1645                 page_down();
 1646                 break;
 1647 
 1648             case GLOBAL_PAGE_UP:
 1649                 page_up();
 1650                 break;
 1651 
 1652             case GLOBAL_SCROLL_DOWN:
 1653                 scroll_down();
 1654                 break;
 1655 
 1656             case GLOBAL_SCROLL_UP:
 1657                 scroll_up();
 1658                 break;
 1659 
 1660             case GLOBAL_TOGGLE_HELP_DISPLAY:
 1661                 toggle_mini_help(ATTACHMENT_LEVEL);
 1662                 show_attachment_page();
 1663                 break;
 1664 
 1665             case GLOBAL_TOGGLE_INFO_LAST_LINE:
 1666                 tinrc.info_in_last_line = bool_not(tinrc.info_in_last_line);
 1667                 show_attachment_page();
 1668                 break;
 1669 
 1670             case ATTACHMENT_SAVE:
 1671                 if (attmenu.max) {
 1672                     part = get_part(attmenu.curr);
 1673                     process_parts(part, art, num_of_tagged_parts ? SAVE_TAGGED : SAVE);
 1674                     show_attachment_page();
 1675                 }
 1676                 break;
 1677 
 1678             case ATTACHMENT_SELECT:
 1679                 if (attmenu.max) {
 1680                     part = get_part(attmenu.curr);
 1681                     process_parts(part, art, VIEW);
 1682                     show_attachment_page();
 1683                 }
 1684                 break;
 1685 
 1686             case ATTACHMENT_TAG:
 1687                 if (attmenu.max) {
 1688                     t_bool tagged;
 1689 
 1690                     tagged = tag_part(attmenu.curr);
 1691                     show_attachment_page();
 1692                     if (attmenu.curr + 1 < attmenu.max)
 1693                         move_down();
 1694                     info_message(tagged ? _(txt_attachment_tagged) : _(txt_attachment_untagged));
 1695                 }
 1696                 break;
 1697 
 1698             case ATTACHMENT_UNTAG:
 1699                 if (attmenu.max && num_of_tagged_parts) {
 1700                     untag_all_parts();
 1701                     show_attachment_page();
 1702                 }
 1703                 break;
 1704 
 1705             case ATTACHMENT_TAG_PATTERN:
 1706                 if (attmenu.max) {
 1707                     tag_pattern();
 1708                     show_attachment_page();
 1709                     info_message(_(txt_attachments_tagged), num_of_tagged_parts);
 1710                 }
 1711                 break;
 1712 
 1713             case ATTACHMENT_TOGGLE_TAGGED:
 1714                 if (attmenu.max) {
 1715                     int i;
 1716 
 1717                     for (i = attmenu.first; i < attmenu.max; ++i)
 1718                         tag_part(i);
 1719                     show_attachment_page();
 1720                     info_message(_(txt_attachments_tagged), num_of_tagged_parts);
 1721                 }
 1722                 break;
 1723 
 1724             case GLOBAL_SEARCH_SUBJECT_FORWARD:
 1725             case GLOBAL_SEARCH_SUBJECT_BACKWARD:
 1726             case GLOBAL_SEARCH_REPEAT:
 1727                 if (func == GLOBAL_SEARCH_REPEAT && last_search != GLOBAL_SEARCH_SUBJECT_FORWARD && last_search != GLOBAL_SEARCH_SUBJECT_BACKWARD)
 1728                     info_message(_(txt_no_prev_search));
 1729                 else if (attmenu.max) {
 1730                     int new_pos, old_pos = attmenu.curr;
 1731 
 1732                     new_pos = generic_search((func == GLOBAL_SEARCH_SUBJECT_FORWARD), (func == GLOBAL_SEARCH_REPEAT), attmenu.curr, attmenu.max - 1, ATTACHMENT_LEVEL);
 1733                     if (new_pos != old_pos)
 1734                         move_to_item(new_pos);
 1735                 }
 1736                 break;
 1737 
 1738 #ifndef DONT_HAVE_PIPING
 1739             case ATTACHMENT_PIPE:
 1740             case GLOBAL_PIPE:
 1741                 if (attmenu.max) {
 1742                     part = get_part(attmenu.curr);
 1743                     process_parts(part, art, func == GLOBAL_PIPE ? PIPE_RAW : PIPE);
 1744                     show_attachment_page();
 1745                 }
 1746                 break;
 1747 #endif /* !DONT_HAVE_PIPING */
 1748 
 1749             default:
 1750                 info_message(_(txt_bad_command), PrintFuncKey(key, GLOBAL_HELP, attachment_keys));
 1751                 break;
 1752         }
 1753     }
 1754 }
 1755 
 1756 
 1757 static t_function
 1758 attachment_left(
 1759     void)
 1760 {
 1761     return GLOBAL_QUIT;
 1762 }
 1763 
 1764 
 1765 static t_function
 1766 attachment_right(
 1767     void)
 1768 {
 1769     return ATTACHMENT_SELECT;
 1770 }
 1771 
 1772 
 1773 static void
 1774 draw_attachment_arrow(
 1775     void)
 1776 {
 1777     draw_arrow_mark(INDEX_TOP + attmenu.curr - attmenu.first);
 1778     if (tinrc.info_in_last_line) {
 1779         const char *name;
 1780         t_part *part;
 1781 
 1782         part = get_part(attmenu.curr);
 1783         name = get_filename(part->params);
 1784         info_message("%s  %s", name ? name : _(txt_attachment_no_name), BlankIfNull(part->description));
 1785     } else if (attmenu.curr == attmenu.max - 1)
 1786         info_message(_(txt_end_of_attachments));
 1787 }
 1788 
 1789 
 1790 static void
 1791 build_attachment_line(
 1792     int i)
 1793 {
 1794     char *sptr;
 1795     const char *name;
 1796     const char *charset;
 1797 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1798     char *tmpname;
 1799     char *tmpbuf;
 1800 #endif /* MULTIBYTE_ABLE && !NOLOCALE */
 1801     char buf[BUFSIZ];
 1802     char buf2[BUFSIZ];
 1803     char *tree = NULL;
 1804     int len, namelen, tagged, treelen;
 1805     t_part *part;
 1806 
 1807 #ifdef USE_CURSES
 1808     /*
 1809      * Allocate line buffer
 1810      * make it the same size like in !USE_CURSES case to simplify some code
 1811      */
 1812 #   if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1813         sptr = my_malloc(cCOLS * MB_CUR_MAX + 2);
 1814 #   else
 1815         sptr = my_malloc(cCOLS + 2);
 1816 #   endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1817 #else
 1818     sptr = screen[INDEX2SNUM(i)].col;
 1819 #endif /* USE_CURSES */
 1820 
 1821     part = get_part(i);
 1822     namelen = MIN(cCOLS - 13 - info_len - 8, strwidth(_(txt_attachment_no_name)));
 1823     tagged = get_tagged(i);
 1824 
 1825     if (!(name = get_filename(part->params))) {
 1826         if (!(name = part->description))
 1827             name = _(txt_attachment_no_name);
 1828     }
 1829 
 1830     charset = get_param(part->params, "charset");
 1831     snprintf(buf2, sizeof(buf2), _(txt_attachment_lines), part->line_count);
 1832     /* TODO: make the layout configurable? */
 1833     if (!strcmp(content_types[part->type], "text"))
 1834         snprintf(buf, sizeof(buf), "  %s/%s, %s, %s%s%s", content_types[part->type], part->subtype, content_encodings[part->encoding], charset ? charset : "", charset ? ", " : "", buf2);
 1835     else
 1836         snprintf(buf, sizeof(buf), "  %s/%s, %s, %s", content_types[part->type], part->subtype, content_encodings[part->encoding], buf2);
 1837     if (part->depth > 0) {
 1838         treelen = cCOLS - 13 - info_len - namelen;
 1839         tree = build_tree(part->depth, treelen, i);
 1840     }
 1841     snprintf(buf2, sizeof(buf2), "%s  %s", tagged ? tin_ltoa(tagged, 3) : "   ", BlankIfNull(tree));
 1842     FreeIfNeeded(tree);
 1843     len = strwidth(buf2);
 1844     if (namelen + len + info_len + 8 <= cCOLS)
 1845         namelen = cCOLS - 8 - info_len - len;
 1846 
 1847 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1848     tmpname = spart(name, namelen, TRUE);
 1849     tmpbuf = spart(buf, info_len, TRUE);
 1850     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);
 1851     FreeIfNeeded(tmpname);
 1852     FreeIfNeeded(tmpbuf);
 1853 #else
 1854     snprintf(sptr, cCOLS, "  %s %s%-*.*s%*.*s%s", tin_ltoa(i + 1, 4), buf2, namelen, namelen, name, info_len, info_len, buf, cCRLF);
 1855 #endif /* MULTIBYTE_ABLE && !NOLOCALE */
 1856 
 1857     WriteLine(INDEX2LNUM(i), sptr);
 1858 
 1859 #ifdef USE_CURSES
 1860     free(sptr);
 1861 #endif /* USE_CURSES */
 1862 }
 1863 
 1864 
 1865 /*
 1866  * Build attachment tree. Code adopted
 1867  * from thread.c:make_prefix().
 1868  */
 1869 static char *
 1870 build_tree(
 1871     int depth,
 1872     int maxlen,
 1873     int i)
 1874 {
 1875 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1876     char *result;
 1877     wchar_t *tree;
 1878 #else
 1879     char *tree;
 1880 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1881     int prefix_ptr, tmpdepth;
 1882     int depth_level = 0;
 1883     t_bool found = FALSE;
 1884     t_partl *lptr, *lptr2;
 1885 
 1886     lptr2 = find_part(i);
 1887     prefix_ptr = depth * 2 - 1;
 1888     if (prefix_ptr > maxlen - 1 - !(maxlen % 2)) {
 1889         int odd = ((maxlen % 2) ? 0 : 1);
 1890 
 1891         prefix_ptr -= maxlen - ++depth_level - 2 - odd;
 1892         while (prefix_ptr > maxlen - 2 - odd) {
 1893             if (depth_level < maxlen / 5)
 1894                 depth_level++;
 1895 
 1896             prefix_ptr -= maxlen - depth_level - 2 - odd;
 1897             odd = (odd ? 0 : 1);
 1898         }
 1899     }
 1900 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1901     tree = my_malloc(sizeof(wchar_t) * (size_t) prefix_ptr + 3 * sizeof(wchar_t));
 1902     tree[prefix_ptr + 2] = (wchar_t) '\0';
 1903 #else
 1904     tree = my_malloc(prefix_ptr + 3);
 1905     tree[prefix_ptr + 2] = '\0';
 1906 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1907     tree[prefix_ptr + 1] = TREE_ARROW;
 1908     tree[prefix_ptr] = TREE_HORIZ;
 1909     for (lptr = lptr2->next; lptr != NULL; lptr = lptr->next) {
 1910         if (lptr->part->depth == depth) {
 1911             found = TRUE;
 1912             break;
 1913         }
 1914         if (lptr->part->depth < depth)
 1915             break;
 1916     }
 1917     tree[--prefix_ptr] = found ? TREE_VERT_RIGHT : TREE_UP_RIGHT;
 1918     found = FALSE;
 1919     for (tmpdepth = depth - 1; prefix_ptr > 1; --tmpdepth) {
 1920         for (lptr = lptr2->next; lptr != NULL; lptr = lptr->next) {
 1921             if (lptr->part->depth == tmpdepth) {
 1922                 found = TRUE;
 1923                 break;
 1924             }
 1925             if (lptr->part->depth < tmpdepth)
 1926                 break;
 1927         }
 1928         tree[--prefix_ptr] = TREE_BLANK;
 1929         tree[--prefix_ptr] = found ? TREE_VERT : TREE_BLANK;
 1930         found = FALSE;
 1931     }
 1932     while (depth_level)
 1933         tree[--depth_level] = TREE_ARROW_WRAP;
 1934 
 1935 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1936     result = wchar_t2char(tree);
 1937     free(tree);
 1938     return result;
 1939 #else
 1940     return tree;
 1941 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1942 }
 1943 
 1944 
 1945 /*
 1946  * Find nth attachment in part_list.
 1947  * Return pointer to that part.
 1948  */
 1949 static t_partl *
 1950 find_part(
 1951     int n)
 1952 {
 1953     t_partl *lptr;
 1954 
 1955     lptr = part_list;
 1956     if (attmenu.max >= 1)
 1957         lptr = lptr->next;
 1958 
 1959     while (n-- > 0 && lptr->next)
 1960         lptr = lptr->next;
 1961 
 1962     return lptr;
 1963 }
 1964 
 1965 
 1966 t_part *
 1967 get_part(
 1968     int n)
 1969 {
 1970     t_partl *lptr;
 1971 
 1972     lptr = find_part(n);
 1973     return lptr->part;
 1974 }
 1975 
 1976 
 1977 static void
 1978 tag_pattern(
 1979     void)
 1980 {
 1981     char buf[BUFSIZ];
 1982     char pat[128];
 1983     char *prompt;
 1984     const char *name;
 1985     const char *charset;
 1986     struct regex_cache cache = { NULL, NULL };
 1987     t_part *part;
 1988     t_partl *lptr;
 1989 
 1990 #if 0
 1991     if (num_of_tagged_parts)
 1992         untag_all_parts();
 1993 #endif /* 0 */
 1994 
 1995     prompt = fmt_string(_(txt_select_pattern), tinrc.default_select_pattern);
 1996     if (!(prompt_string_default(prompt, tinrc.default_select_pattern, _(txt_info_no_previous_expression), HIST_SELECT_PATTERN))) {
 1997         free(prompt);
 1998         return;
 1999     }
 2000     free(prompt);
 2001 
 2002     if (STRCMPEQ(tinrc.default_select_pattern, "*")) {  /* all */
 2003         if (tinrc.wildcard)
 2004             STRCPY(pat, ".*");
 2005         else
 2006             STRCPY(pat, tinrc.default_select_pattern);
 2007     } else
 2008         snprintf(pat, sizeof(pat), REGEX_FMT, tinrc.default_select_pattern);
 2009 
 2010     if (tinrc.wildcard && !(compile_regex(pat, &cache, PCRE_CASELESS)))
 2011         return;
 2012 
 2013     lptr = find_part(0);
 2014 
 2015     for (; lptr != NULL; lptr = lptr->next) {
 2016         part = lptr->part;
 2017         if (!(name = get_filename(part->params))) {
 2018             if (!(name = part->description))
 2019                 name = _(txt_attachment_no_name);
 2020         }
 2021         charset = get_param(part->params, "charset");
 2022 
 2023         snprintf(buf, sizeof(buf), "%s %s/%s %s, %s", name, content_types[part->type], part->subtype, content_encodings[part->encoding], charset ? charset : "");
 2024 
 2025         if (!match_regex(buf, pat, &cache, TRUE))
 2026             continue;
 2027 
 2028         if (!lptr->tagged)
 2029             lptr->tagged = ++num_of_tagged_parts;
 2030     }
 2031 
 2032     if (tinrc.wildcard) {
 2033         FreeIfNeeded(cache.re);
 2034         FreeIfNeeded(cache.extra);
 2035     }
 2036 }
 2037 
 2038 
 2039 static int
 2040 get_tagged(
 2041     int n)
 2042 {
 2043     t_partl *lptr;
 2044 
 2045     lptr = find_part(n);
 2046     return lptr->tagged;
 2047 }
 2048 
 2049 
 2050 static t_bool
 2051 tag_part(
 2052     int n)
 2053 {
 2054     t_partl *lptr;
 2055 
 2056     lptr = find_part(n);
 2057     if (lptr->tagged) {
 2058         untag_part(n);
 2059         return FALSE;
 2060     } else {
 2061         lptr->tagged = ++num_of_tagged_parts;
 2062         return TRUE;
 2063     }
 2064 }
 2065 
 2066 
 2067 static void
 2068 untag_part(
 2069     int n)
 2070 {
 2071     int i;
 2072     t_partl *curr_part, *lptr;
 2073 
 2074     lptr = find_part(0);
 2075     curr_part = find_part(n);
 2076     i = attmenu.max;
 2077 
 2078     while (i-- > 0 && lptr) {
 2079         if (lptr->tagged > curr_part->tagged)
 2080             --lptr->tagged;
 2081         lptr = lptr->next;
 2082     }
 2083 
 2084     curr_part->tagged = 0;
 2085     --num_of_tagged_parts;
 2086 }
 2087 
 2088 
 2089 static void
 2090 untag_all_parts(
 2091     void)
 2092 {
 2093     t_partl *lptr = part_list;
 2094 
 2095     while (lptr) {
 2096         if (lptr->tagged)
 2097             lptr->tagged = 0;
 2098 
 2099         lptr = lptr->next;
 2100     }
 2101     num_of_tagged_parts = 0;
 2102 }
 2103 
 2104 
 2105 /*
 2106  * Build a linked list which holds pointers to the parts we want deal with.
 2107  */
 2108 static int
 2109 build_part_list(
 2110     t_openartinfo *art)
 2111 {
 2112     int i = 0;
 2113     t_part *ptr, *uueptr;
 2114     t_partl *lptr;
 2115 
 2116     part_list = my_malloc(sizeof(t_partl));
 2117     lptr = part_list;
 2118     lptr->part = art->hdr.ext;
 2119     lptr->next = NULL;
 2120     lptr->tagged = 0;
 2121     for (ptr = art->hdr.ext; ptr != NULL; ptr = ptr->next) {
 2122         if ((uueptr = ptr->uue) != NULL) {
 2123             lptr->next = my_malloc(sizeof(t_partl));
 2124             lptr->next->part = ptr;
 2125             lptr->next->next = NULL;
 2126             lptr->next->tagged = 0;
 2127             lptr = lptr->next;
 2128             ++i;
 2129             for (; uueptr != NULL; uueptr = uueptr->next) {
 2130                 lptr->next = my_malloc(sizeof(t_partl));
 2131                 lptr->next->part = uueptr;
 2132                 lptr->next->next = NULL;
 2133                 lptr->next->tagged = 0;
 2134                 lptr = lptr->next;
 2135                 ++i;
 2136             }
 2137         }
 2138 
 2139         if (ptr->uue)
 2140             continue;
 2141 
 2142         lptr->next = my_malloc(sizeof(t_partl));
 2143         lptr->next->part = ptr;
 2144         lptr->next->next = NULL;
 2145         lptr->next->tagged = 0;
 2146         lptr = lptr->next;
 2147         ++i;
 2148     }
 2149     return i;
 2150 }
 2151 
 2152 
 2153 static void
 2154 free_part_list(
 2155     t_partl *list)
 2156 {
 2157     while (list->next != NULL) {
 2158         free_part_list(list->next);
 2159         list->next = NULL;
 2160     }
 2161     free(list);
 2162 }
 2163 
 2164 
 2165 static void
 2166 process_parts(
 2167     t_part *part,
 2168     t_openartinfo *art,
 2169     enum action what)
 2170 {
 2171     FILE *fp;
 2172     char *savepath = NULL, *tmppath;
 2173     int i, saved_parts = 0;
 2174     t_partl *lptr;
 2175 
 2176     switch (what) {
 2177         case SAVE_TAGGED:
 2178             for (i = 1; i <= num_of_tagged_parts; i++) {
 2179                 lptr = part_list;
 2180 
 2181                 while (lptr) {
 2182                     if (lptr->tagged == i) {
 2183                         if ((savepath = generate_savepath(lptr->part)) == NULL)
 2184                             return;
 2185 
 2186                         if ((fp = open_save_filename(savepath, FALSE)) == NULL) {
 2187                             free(savepath);
 2188                             return;
 2189                         }
 2190                         process_part(lptr->part, art, fp, NULL, SAVE);
 2191                         free(savepath);
 2192                         ++saved_parts;
 2193                     }
 2194                     lptr = lptr->next;
 2195                 }
 2196             }
 2197             break;
 2198 
 2199         default:
 2200             if ((tmppath = generate_savepath(part)) == NULL)
 2201                 return;
 2202 
 2203             if (what == SAVE)
 2204                 savepath = tmppath;
 2205             else {
 2206                 savepath = get_tmpfilename(tmppath);
 2207                 free(tmppath);
 2208             }
 2209             if ((fp = open_save_filename(savepath, FALSE)) == NULL) {
 2210                 free(savepath);
 2211                 return;
 2212             }
 2213             process_part(part, art, fp, savepath, what);
 2214             break;
 2215     }
 2216     switch (what) {
 2217         case SAVE_TAGGED:
 2218             wait_message(2, _(txt_attachments_saved), saved_parts, num_of_tagged_parts);
 2219             break;
 2220 
 2221         case SAVE:
 2222             wait_message(2, _(txt_attachment_saved), savepath);
 2223             free(savepath);
 2224             break;
 2225 
 2226         default:
 2227             unlink(savepath);
 2228             free(savepath);
 2229             break;
 2230     }
 2231     cursoroff();
 2232 }
 2233 
 2234 
 2235 /*
 2236  * VIEW/PIPE/SAVE the given part.
 2237  *
 2238  * PIPE_RAW uses the raw part, otherwise the part is decoded first.
 2239  */
 2240 static void
 2241 process_part(
 2242     t_part *part,
 2243     t_openartinfo *art,
 2244     FILE *outfile,
 2245     const char *savepath,
 2246     enum action what)
 2247 {
 2248     FILE *infile;
 2249     char buf[2048], buf2[2048];
 2250     int count;
 2251     int i, line_count;
 2252 #ifdef CHARSET_CONVERSION
 2253     char *conv_buf;
 2254     const char *network_charset;
 2255     size_t line_len;
 2256 #endif /* CHARSET_CONVERSION */
 2257 
 2258     /*
 2259      * uuencoded parts must be read from the cooked article,
 2260      * otherwise they might be additionally encoded with b64 or qp
 2261      */
 2262     if (part->encoding == ENCODING_UUE)
 2263         infile = art->cooked;
 2264     else
 2265         infile = art->raw;
 2266 
 2267     if (what != PIPE_RAW && part->encoding == ENCODING_BASE64)
 2268         mmdecode(NULL, 'b', 0, NULL);               /* flush */
 2269 
 2270     fseek(infile, part->offset, SEEK_SET);
 2271 
 2272     line_count = part->line_count;
 2273 
 2274     for (i = 0; i < line_count; i++) {
 2275         if ((fgets(buf, sizeof(buf), infile)) == NULL)
 2276             break;
 2277 
 2278         /* This should catch cases where people illegally append text etc */
 2279         if (buf[0] == '\0')
 2280             break;
 2281 
 2282         /*
 2283          * page.c:new_uue() sets offset to the 'begin ...' line
 2284          * -> skip over the first line in uuencoded parts
 2285          */
 2286         if (part->encoding == ENCODING_UUE && i == 0) {
 2287             ++line_count;
 2288             continue;
 2289         }
 2290 
 2291         if (what != PIPE_RAW) {
 2292             switch (part->encoding) {
 2293                 case ENCODING_QP:
 2294                 case ENCODING_BASE64:
 2295 #ifdef CHARSET_CONVERSION
 2296                     memset(buf2, '\0', sizeof(buf2));
 2297 #endif /* CHARSET_CONVERSION */
 2298                     if ((count = mmdecode(buf, part->encoding == ENCODING_QP ? 'q' : 'b', '\0', buf2)) > 0) {
 2299 #ifdef CHARSET_CONVERSION
 2300                         if (what != SAVE && what != SAVE_TAGGED && !strncmp(content_types[part->type], "text", 4)) {
 2301                             line_len = (size_t) count;
 2302                             conv_buf = my_strdup(buf2);
 2303                             network_charset = get_param(part->params, "charset");
 2304                             process_charsets(&conv_buf, &line_len, network_charset ? network_charset : "US-ASCII", tinrc.mm_local_charset, FALSE);
 2305                             strncpy(buf2, conv_buf, sizeof(buf2) - 1);
 2306                             count = (int) strlen(buf2);
 2307                             free(conv_buf);
 2308                         }
 2309 #endif /* CHARSET_CONVERSION */
 2310                         fwrite(buf2, (size_t) count, 1, outfile);
 2311                     }
 2312                     break;
 2313 
 2314                 case ENCODING_UUE:
 2315                     /* TODO: if postproc, don't decode these since the traditional uudecoder will get them */
 2316                     /*
 2317                      * x-uuencode attachments have all the header info etc which we must ignore
 2318                      */
 2319                     if (strncmp(buf, "begin ", 6) != 0 && strncmp(buf, "end\n", 4) != 0 && buf[0] != '\n')
 2320                         uudecode_line(buf, outfile);
 2321                     break;
 2322 
 2323                 default:
 2324 #ifdef CHARSET_CONVERSION
 2325                         if (what != SAVE && what != SAVE_TAGGED && !strncmp(content_types[part->type], "text", 4)) {
 2326                             conv_buf = my_strdup(buf);
 2327                             line_len = strlen(conv_buf);
 2328                             network_charset = get_param(part->params, "charset");
 2329                             process_charsets(&conv_buf, &line_len, network_charset ? network_charset : "US-ASCII", tinrc.mm_local_charset, FALSE);
 2330                             strncpy(buf, conv_buf, sizeof(buf) - 1);
 2331                             free(conv_buf);
 2332                         }
 2333 #endif /* CHARSET_CONVERSION */
 2334                     fputs(buf, outfile);
 2335             }
 2336         } else
 2337             fputs(buf, outfile);
 2338     }
 2339 
 2340     fclose(outfile);
 2341 
 2342     switch (what) {
 2343         case VIEW:
 2344             start_viewer(part, savepath);
 2345             break;
 2346 
 2347 #ifndef DONT_HAVE_PIPING
 2348         case PIPE:
 2349         case PIPE_RAW:
 2350             pipe_part(savepath);
 2351             break;
 2352 #endif /* !DONT_HAVE_PIPING */
 2353 
 2354         default:
 2355             break;
 2356     }
 2357 }
 2358 
 2359 
 2360 #ifndef DONT_HAVE_PIPING
 2361 static void
 2362 pipe_part(
 2363     const char *savepath)
 2364 {
 2365     FILE *fp, *pipe_fp;
 2366     char *prompt;
 2367 
 2368     prompt = fmt_string(_(txt_pipe_to_command), (size_t) cCOLS - (strlen(_(txt_pipe_to_command)) + 30), tinrc.default_pipe_command);
 2369     if (!(prompt_string_default(prompt, tinrc.default_pipe_command, _(txt_no_command), HIST_PIPE_COMMAND))) {
 2370         free(prompt);
 2371         return;
 2372     }
 2373     free(prompt);
 2374     if ((fp = fopen(savepath, "r")) == NULL)
 2375         /* TODO: error message? */
 2376         return;
 2377     EndWin();
 2378     Raw(FALSE);
 2379     fflush(stdout);
 2380     set_signal_catcher(FALSE);
 2381     if ((pipe_fp = popen(tinrc.default_pipe_command, "w")) == NULL) {
 2382         perror_message(_(txt_command_failed), tinrc.default_pipe_command);
 2383         set_signal_catcher(TRUE);
 2384         Raw(TRUE);
 2385         InitWin();
 2386         fclose(fp);
 2387         return;
 2388     }
 2389     copy_fp(fp, pipe_fp);
 2390     if (errno == EPIPE)
 2391         perror_message(_(txt_command_failed), tinrc.default_pipe_command);
 2392     fflush(pipe_fp);
 2393     (void) pclose(pipe_fp);
 2394     set_signal_catcher(TRUE);
 2395     fclose(fp);
 2396 #   ifdef USE_CURSES
 2397     Raw(TRUE);
 2398     InitWin();
 2399 #   endif /* USE_CURSES */
 2400     prompt_continue();
 2401 #   ifndef USE_CURSES
 2402     Raw(TRUE);
 2403     InitWin();
 2404 #   endif /* !USE_CURSES */
 2405 }
 2406 #endif /* !DONT_HAVE_PIPING */