"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.4/src/save.c" (20 Nov 2019, 59373 Bytes) of package /linux/misc/tin-2.4.4.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.4.3_vs_2.4.4.

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : save.c
    4  *  Author    : I. Lea & R. Skrenta
    5  *  Created   : 1991-04-01
    6  *  Updated   : 2019-06-05
    7  *  Notes     :
    8  *
    9  * Copyright (c) 1991-2020 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 = 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: 1.0\n");
  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                 printascii(keyappend, func_to_key(SAVE_APPEND_FILE, save_append_overwrite_keys)),
  417                 printascii(keyoverwrite, func_to_key(SAVE_OVERWRITE_FILE, save_append_overwrite_keys)),
  418                 printascii(keyquit, func_to_key(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  * 'artptr' points to the article in arts[]
  453  * 'mailbox' is set if we are saving to a =mailbox
  454  * 'inpath' is the template save path/file to save to
  455  * 'max' is the number of articles we are saving
  456  * 'post_process' is set if we want post-processing
  457  * Expand the path appropriately, taking account of multiple file
  458  * extensions and the auto-save with Archive-Name: headers
  459  *
  460  * Extract binary attachments if !LIBUU
  461  * Start viewer if requested
  462  * If successful, add entry to the save[] array
  463  * Returns:
  464  *     TRUE or FALSE depending on whether article was saved okay.
  465  *
  466  * TODO: could we use append_mail() here
  467  */
  468 t_bool
  469 save_and_process_art(
  470     t_openartinfo *artinfo,
  471     struct t_article *artptr,
  472     t_bool is_mailbox,
  473     const char *inpath,
  474     int max,
  475     t_bool post_process)
  476 {
  477     FILE *fp;
  478     char from[HEADER_LEN];
  479     char path[PATH_LEN];
  480     time_t epoch;
  481     t_bool mmdf = (is_mailbox && !strcasecmp(txt_mailbox_formats[tinrc.mailbox_format], "MMDF"));
  482 
  483     if (fseek(artinfo->raw, 0L, SEEK_SET) == -1) {
  484         perror_message(txt_error_fseek, artinfo->hdr.subj);
  485         return FALSE;
  486     }
  487 
  488     /* The first task is to fixup the filename to be saved too. This is context dependent */
  489     strncpy(path, inpath, sizeof(path) - 1);
  490 /* 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)); */
  491 
  492     /*
  493      * If using the auto-save feature on an article with Archive-Name,
  494      * the path will be: <original-path>/<archive-name>/<part|patch><part#>
  495      */
  496     if (!is_mailbox && curr_group->attribute->auto_save && artptr->archive) {
  497         const char *partprefix;
  498         char *ptr;
  499         char archpath[PATH_LEN];
  500         char filename[NAME_LEN + 1];
  501 
  502         /*
  503          * We need either a part or a patch number, part takes precedence
  504          */
  505         if (artptr->archive->ispart)
  506             partprefix = PATH_PART;
  507         else
  508             partprefix = PATH_PATCH;
  509 
  510         /*
  511          * Strip off any existing filename
  512          */
  513         if ((ptr = strrchr(path, DIRSEP)) != NULL)
  514             *(ptr + 1) = '\0';
  515 
  516         /* Add on the archive name as a directory */
  517         /* TODO: maybe a s!/!.! on archive-name would be better */
  518         joinpath(archpath, sizeof(archpath), path, artptr->archive->name);
  519 
  520         /* Generate the filename part and append it */
  521         snprintf(filename, sizeof(filename), "%s%s", partprefix, artptr->archive->partnum);
  522         joinpath(path, sizeof(path), archpath, filename);
  523 /*fprintf(stderr, "save_and_process_art archive-name mangled path=(%s)\n", path);*/
  524         if (!create_path(path))
  525             return FALSE;
  526     } else {
  527         /*
  528          * Mailbox saves are by definition to a single file as are single file
  529          * saves. Multiple file saves append a .NNN sequence number to the path
  530          * This is backward-contemptibility with older versions of tin
  531          */
  532         if (!is_mailbox && max > 1) {
  533             const char suffixsep = '.';
  534 
  535             sprintf(&path[strlen(path)], "%c%03d", suffixsep, num_save + 1);
  536         }
  537     }
  538 
  539 /* fprintf(stderr, "save_and_process_art expanded path now=(%s)\n", path); */
  540 
  541     if ((fp = open_save_filename(path, is_mailbox)) == NULL)
  542         return FALSE;
  543 
  544     if (mmdf)
  545         fprintf(fp, "%s", MMDFHDRTXT);
  546     else {
  547         if (artinfo->hdr.from)
  548             strip_name(artinfo->hdr.from, from);
  549         (void) time(&epoch);
  550         fprintf(fp, "From %s %s", from, ctime(&epoch));
  551         /*
  552          * TODO: add Content-Length: header when using MBOXO
  553          *       so tin actually write MBOXCL instead of MBOXO?
  554          */
  555     }
  556 
  557     if (copy_fp(artinfo->raw, fp)) /* Write tailing newline or MMDF-mailbox separator */
  558         print_art_separator_line(fp, is_mailbox);
  559     else {
  560         fclose(fp);
  561         unlink(path);
  562         return FALSE;
  563     }
  564 
  565     fclose(fp);
  566 
  567     /*
  568      * Saved ok, so fill out a save[] record
  569      */
  570     if (num_save == max_save - 1)
  571         expand_save();
  572     save[num_save].path = my_strdup(path);
  573     save[num_save].file = strrchr(save[num_save].path, DIRSEP) + 1; /* ptr to filename portion */
  574     save[num_save].mailbox = is_mailbox;
  575 /* fprintf(stderr, "SAPA (%s) (%s) mbox=%s\n", save[num_save].path, save[num_save].file, bool_unparse(save[num_save].mailbox)); */
  576     num_save++;         /* NB: num_save is bumped here only */
  577 
  578     /*
  579      * Extract/view parts from multipart articles if required
  580      * libuu does this as part of its own processing
  581      */
  582 #ifndef HAVE_LIBUU
  583     if (post_process) {
  584 #   ifdef USE_CURSES
  585         scrollok(stdscr, TRUE);
  586 #   endif /* USE_CURSES */
  587         decode_save_mime(artinfo, TRUE);
  588 #   ifdef USE_CURSES
  589         scrollok(stdscr, FALSE);
  590 #   endif /* USE_CURSES */
  591     }
  592 #endif /* !HAVE_LIBUU */
  593 
  594     return TRUE;
  595 }
  596 
  597 
  598 /*
  599  * Create the supplied path. Create intermediate directories as needed
  600  * Don't create the last component (which would be the filename) unless the
  601  * path is / terminated.
  602  * Return FALSE if it somehow fails.
  603  */
  604 t_bool
  605 create_path(
  606     const char *path)
  607 {
  608     char *buf, *p;
  609     struct stat st;
  610 
  611     if (!strlen(path))
  612         return FALSE;
  613 
  614     buf = my_strdup(path);
  615     p = buf + 1;
  616 
  617     if (!strlen(p)) {
  618         free(buf);
  619         return FALSE;
  620     }
  621 
  622     while ((p = strchr(p, DIRSEP)) != NULL) {
  623         *p = '\0';
  624         if (stat(buf, &st) == -1) {
  625             if (my_mkdir(buf, (mode_t) (S_IRWXU|S_IRUGO|S_IXUGO)) == -1) {
  626                 if (errno != EEXIST) {
  627                     perror_message(_(txt_cannot_create), buf);
  628                     free(buf);
  629                     return FALSE;
  630                 }
  631             }
  632         }
  633         *p++ = DIRSEP;
  634     }
  635     free(buf);
  636     return TRUE;
  637 }
  638 
  639 
  640 /*
  641  * Generate semi-meaningful filename based on sequence number and
  642  * Content-(sub)type
  643  */
  644 static void
  645 generate_filename(
  646     char *buf,
  647     int buflen,
  648     const char *suffix)
  649 {
  650     static int seqno = 0;
  651 
  652     snprintf(buf, buflen, "%s-%03d.%s", SAVEFILE_PREFIX, seqno++, suffix);
  653 }
  654 
  655 
  656 /*
  657  * Generate /save/to/path name.
  658  *
  659  * Return pointer to allocated memory which the caller must free or
  660  * NULL if something went wrong.
  661  */
  662 static char *
  663 generate_savepath(
  664     t_part *part)
  665 {
  666     char buf[2048];
  667     char *savepath;
  668     const char *name;
  669     t_bool mbox;
  670 
  671     savepath = my_malloc(PATH_LEN);
  672     /*
  673      * Get the filename to save to in 'savepath'
  674      */
  675     if ((name = get_filename(part->params)) == NULL) {
  676         char extension[NAME_LEN + 1];
  677 
  678         lookup_extension(extension, sizeof(extension), content_types[part->type], part->subtype);
  679         generate_filename(buf, sizeof(buf), extension);
  680         mbox = expand_save_filename(savepath, PATH_LEN, buf);
  681     } else
  682         mbox = expand_save_filename(savepath, PATH_LEN, name);
  683 
  684     /*
  685      * Not a good idea to dump attachments over a mailbox
  686      */
  687     if (mbox) {
  688         wait_message(2, _(txt_is_mailbox), content_types[part->type], part->subtype);
  689         free(savepath);
  690         return NULL;
  691     }
  692 
  693     if (!(create_path(savepath))) {
  694         error_message(2, _(txt_cannot_open_for_saving), savepath);
  695         free(savepath);
  696         return NULL;
  697     }
  698 
  699     return savepath;
  700 }
  701 
  702 
  703 /*
  704  * Generate a path/filename to save to, using 'path' as input.
  705  * The pathname is stored in 'outpath', which should be PATH_LEN in size
  706  * Expand metacharacters and use defaults as needed.
  707  * Return TRUE if the path is a mailbox, or FALSE otherwise.
  708  */
  709 static t_bool
  710 expand_save_filename(
  711     char *outpath,
  712     size_t outpath_len,
  713     const char *path)
  714 {
  715     char base_filename[PATH_LEN];
  716     char buf[PATH_LEN];
  717     char buf_path[PATH_LEN];
  718     int ret;
  719 
  720     /*
  721      * Make sure that externally supplied filename is a filename only and fits
  722      * into buffer
  723      */
  724     STRCPY(buf_path, path);
  725     base_name(buf_path, base_filename);
  726 
  727     /* Build default path to save to */
  728     if (!(ret = strfpath((cmdline.args & CMDLINE_SAVEDIR) ? cmdline.savedir : curr_group->attribute->savedir, buf, sizeof(buf), curr_group, FALSE)))
  729         joinpath(buf, sizeof(buf), homedir, DEFAULT_SAVEDIR);
  730 
  731     /* Join path and filename */
  732     joinpath(outpath, outpath_len, buf, base_filename);
  733 
  734     return (ret == 1);  /* should now always evaluate to FALSE */
  735 }
  736 
  737 
  738 /*
  739  * Post process the articles in save[] according to proc_type_ch
  740  * auto_delete is set if we should remove the saved files after processing
  741  * This stage can produce a fair bit of output so we allow it to
  742  * scroll up the screen rather than waste time displaying it in the
  743  * message bar
  744  */
  745 t_bool
  746 post_process_files(
  747     t_function proc_type_func,
  748     t_bool auto_delete)
  749 {
  750     if (num_save < 1)
  751         return FALSE;
  752 
  753     EndWin();
  754     Raw(FALSE);
  755     my_printf("%s%s", _(txt_post_processing), cCRLF);
  756 
  757     switch (proc_type_func) {
  758         case POSTPROCESS_SHAR:
  759             post_process_sh();
  760             break;
  761 
  762         /* This is the default, eg, with AUTOSAVE */
  763         case POSTPROCESS_YES:
  764         default:
  765             post_process_uud();
  766             break;
  767     }
  768 
  769     my_printf("%s%s%s", _(txt_post_processing_finished), cCRLF, cCRLF);
  770     my_flush();
  771 #ifdef USE_CURSES
  772     Raw(TRUE);
  773     InitWin();
  774 #endif /* USE_CURSES */
  775     prompt_continue();
  776 #ifndef USE_CURSES
  777     Raw(TRUE);
  778     InitWin();
  779 #endif /* !USE_CURSES */
  780 
  781     /*
  782      * Remove the post-processed files if required
  783      */
  784     if (auto_delete) {
  785         int i;
  786 
  787         wait_message((tinrc.beginner_level) ? 2 : 1, "%s", _(txt_deleting));
  788         cursoroff();
  789 
  790         for (i = 0; i < num_save; i++)
  791             unlink(save[i].path);
  792     }
  793 
  794     return TRUE;
  795 }
  796 
  797 
  798 /*
  799  * Two implementations .....
  800  * The LIBUU case performs multi-file decoding for uue, base64
  801  * binhex, qp. This is handled entirely during the post processing phase
  802  *
  803  * The !LIBUU case only handles multi-file uudecoding, the other MIME
  804  * types were handled using the internal MIME parser when the articles
  805  * were originally saved
  806  */
  807 #ifdef HAVE_LIBUU
  808 static void
  809 post_process_uud(
  810     void)
  811 {
  812     FILE *fp_in;
  813     char file_out_dir[PATH_LEN];
  814     const char *eptr;
  815     int i;
  816     int count;
  817     int errors = 0;
  818     uulist *item;
  819 
  820     /*
  821      * Grab the dirname portion
  822      */
  823     my_strncpy(file_out_dir, save[0].path, save[0].file - save[0].path);
  824 
  825     UUInitialize();
  826 
  827     UUSetOption(UUOPT_SAVEPATH, 0, file_out_dir);
  828     for (i = 0; i < num_save; i++) {
  829         if ((fp_in = fopen(save[i].path, "r")) != NULL) {
  830             UULoadFile(save[i].path, NULL, 0);  /* Scans file for encoded data */
  831             fclose(fp_in);
  832         }
  833     }
  834 
  835 #   if 0
  836     /*
  837      * uudeview's "intelligent" multi-part detection
  838      * From the uudeview docs: This function is a bunch of heuristics, and I
  839      * don't really trust them... should only be called as a last resort on
  840      * explicit user request
  841      */
  842     UUSmerge(0);
  843     UUSmerge(1);
  844     UUSmerge(99);
  845 #   endif /* 0 */
  846 
  847     i = count = 0;
  848     item = UUGetFileListItem(i);
  849     my_printf(cCRLF);
  850 
  851     while (item != NULL) {
  852         if (UUDecodeFile(item, NULL) == UURET_OK) {
  853             char path[PATH_LEN];
  854 
  855 /* TODO: test for multiple things per article decoded okay? */
  856             count++;
  857             my_printf(_(txt_uu_success), item->filename);
  858             my_printf(cCRLF);
  859 
  860             /* item->mimetype seems not to be available for uudecoded files etc */
  861             if (curr_group->attribute->post_process_view) {
  862                 joinpath(path, sizeof(path), file_out_dir, item->filename);
  863                 view_file(path, strrchr(path, DIRSEP) + 1);
  864             }
  865         } else {
  866             errors++;
  867             if (item->state & UUFILE_MISPART)
  868                 eptr = _(txt_libuu_error_missing);
  869             else if (item->state & UUFILE_NOBEGIN)
  870                 eptr = _(txt_libuu_error_no_begin);
  871             else if (item->state & UUFILE_NOEND)
  872                 eptr = _(txt_uu_error_no_end);
  873             else if (item->state & UUFILE_NODATA)
  874                 eptr = _(txt_libuu_error_no_data);
  875             else
  876                 eptr = _(txt_libuu_error_unknown);
  877 
  878             my_printf(_(txt_uu_error_decode), (item->filename) ? item->filename : item->subfname, eptr);
  879             my_printf(cCRLF);
  880         }
  881         i++;
  882         item = UUGetFileListItem(i);
  883         my_flush();
  884     }
  885 
  886     my_printf(_(txt_libuu_saved), count, num_save, errors, PLURAL(errors, txt_error));
  887     my_printf(cCRLF);
  888     UUCleanUp();
  889 }
  890 
  891 #else
  892 
  893 /*
  894  * Open and read all the files in save[]
  895  * Scan for uuencode BEGIN lines, decode input as we go along
  896  * uuencoded data can span multiple files, and multiple uuencoded
  897  * files are supported per batch
  898  */
  899 static void
  900 post_process_uud(
  901     void)
  902 {
  903     FILE *fp_in;
  904     FILE *fp_out = NULL;
  905     char *filename = NULL;
  906     char file_out_dir[PATH_LEN];
  907     char path[PATH_LEN];
  908     char s[LEN], t[LEN], u[LEN];
  909     int state = INITIAL;
  910     int i;
  911     mode_t mode = 0;
  912 
  913     /*
  914      * Grab the dirname portion
  915      */
  916     my_strncpy(file_out_dir, save[0].path, save[0].file - save[0].path);
  917 
  918     t[0] = '\0';
  919     u[0] = '\0';
  920 
  921     for (i = 0; i < num_save; i++) {
  922         if ((fp_in = fopen(save[i].path, "r")) == NULL)
  923             continue;
  924 
  925         while (fgets(s, (int) sizeof(s), fp_in) != NULL) {
  926             switch (state) {
  927                 case INITIAL:
  928                     if (strncmp("begin ", s, 6) == 0) {
  929                         char fmt[15];
  930                         char name[PATH_LEN];
  931                         char buf[PATH_LEN];
  932 
  933                         snprintf(fmt, sizeof(fmt), "%%o %%%dc\\n", PATH_LEN - 1);
  934                         if (sscanf(s + 6, fmt, &mode, name) == 2) {
  935                             strtok(name, "\n");
  936                             my_strncpy(buf, name, sizeof(buf) - 1);
  937                             str_trim(buf);
  938                             base_name(buf, name);
  939                         } else
  940                             name[0] = '\0';
  941 
  942                         if (!mode && !*name) { /* not a valid uu-file at all */
  943                             state = INITIAL;
  944                             continue;
  945                         }
  946 
  947                         if (!*name)
  948                             generate_filename(name, sizeof(name), "uue");
  949 
  950                         filename = name;
  951                         expand_save_filename(path, sizeof(path), filename);
  952                         filename = strrchr(path, DIRSEP) + 1;   /* ptr to filename portion */
  953                         if ((fp_out = fopen(path, "w")) == NULL) {
  954                             perror_message(_(txt_cannot_open), path);
  955                             fclose(fp_in);
  956                             return;
  957                         }
  958                         state = MIDDLE;
  959                     }
  960                     break;
  961 
  962                 case MIDDLE:
  963                     /*
  964                      * TODO: replace hard coded length check (uue lines are not
  965                      *       required to be 60 chars long (45 encoded chars)
  966                      *       ('M' == 60 * 3 / 4 + ' ' == 77))
  967                      */
  968                     if (s[0] == 'M')
  969                         uudecode_line(s, fp_out);
  970                     else if (STRNCMPEQ("end", s, 3)) {
  971                         state = END;
  972                         if (u[0] != 'M')
  973                             uudecode_line(u, fp_out);
  974                         if (t[0] != 'M')
  975                             uudecode_line(t, fp_out);
  976                     } else  /* end */
  977                         state = OFF;    /* OFF => a break in the uuencoded data */
  978                     break;
  979 
  980                 case OFF:
  981                     if ((s[0] == 'M') && (t[0] == 'M') && (u[0] == 'M')) {
  982                         uudecode_line(u, fp_out);
  983                         uudecode_line(t, fp_out);
  984                         uudecode_line(s, fp_out);
  985                         state = MIDDLE; /* Continue output of previously suspended data */
  986                     } else if (STRNCMPEQ("end", s, 3)) {
  987                         state = END;
  988                         if (u[0] != 'M')
  989                             uudecode_line(u, fp_out);
  990                         if (t[0] != 'M')
  991                             uudecode_line(t, fp_out);
  992                     }
  993                     break;
  994 
  995                 case END:
  996                 default:
  997                     break;
  998             }   /* switch (state) */
  999 
 1000             if (state == END) {
 1001                 /* set the mode after getting rid of dangerous bits */
 1002                 if (!(mode &= ~(S_ISUID|S_ISGID|S_ISVTX)))
 1003                     mode = (S_IRUSR|S_IWUSR);
 1004 
 1005                 fchmod(fileno(fp_out), mode);
 1006 
 1007                 fclose(fp_out);
 1008                 fp_out = NULL;
 1009 
 1010                 my_printf(_(txt_uu_success), filename);
 1011                 my_printf(cCRLF);
 1012                 sum_file(path, filename);
 1013                 if (curr_group->attribute->post_process_view)
 1014                     view_file(path, filename);
 1015                 state = INITIAL;
 1016                 continue;
 1017             }
 1018 
 1019             strcpy(u, t);   /* Keep tabs on the last two lines, which typically do not start with M */
 1020             strcpy(t, s);
 1021 
 1022         }   /* while (fgets) ... */
 1023 
 1024         fclose(fp_in);
 1025 
 1026     } /* for i...num_save */
 1027 
 1028     /*
 1029      * Check if we ran out of data
 1030      */
 1031     if (fp_out) {
 1032         fclose(fp_out);
 1033         my_printf(_(txt_uu_error_decode), filename, _(txt_uu_error_no_end));
 1034         my_printf(cCRLF);
 1035     }
 1036     return;
 1037 }
 1038 
 1039 
 1040 /*
 1041  * Sum file - why do we bother to do this?
 1042  * nuke code or add DONT_HAVE_PIPING -tree
 1043  */
 1044 static void
 1045 sum_file(
 1046     const char *path,
 1047     const char *file)
 1048 {
 1049 #   if defined(HAVE_SUM) && !defined(DONT_HAVE_PIPING)
 1050     FILE *fp_in;
 1051     char *ext;
 1052     char buf[LEN];
 1053 
 1054     sh_format(buf, sizeof(buf), "%s \"%s\"", DEFAULT_SUM, path);
 1055     if ((fp_in = popen(buf, "r")) != NULL) {
 1056         buf[0] = '\0';
 1057 
 1058         /*
 1059          * You can't do this with (fgets != NULL)
 1060          */
 1061         while (!feof(fp_in)) {
 1062             fgets(buf, (int) sizeof(buf), fp_in);
 1063             if ((ext = strchr(buf, '\n')) != NULL)
 1064                 *ext = '\0';
 1065         }
 1066         fflush(fp_in);
 1067         pclose(fp_in);
 1068 
 1069         my_printf(_(txt_checksum_of_file), file, file_size(path), _("bytes"));
 1070         my_printf(cCRLF);
 1071         my_printf("\t%s%s", buf, cCRLF);
 1072     } else {
 1073         my_printf(_(txt_command_failed), buf);
 1074         my_printf(cCRLF);
 1075     }
 1076     my_flush();
 1077 #   endif /* HAVE SUM && !DONT_HAVE_PIPING */
 1078 }
 1079 #endif /* HAVE_LIBUU */
 1080 
 1081 
 1082 /*
 1083  * If defined, invoke post processor command
 1084  * Create a part structure, with defaults, insert a parameter for the name
 1085  */
 1086 static void
 1087 view_file(
 1088     const char *path,
 1089     const char *file)
 1090 {
 1091     char *ext;
 1092     t_part *part;
 1093 
 1094     part = new_part(NULL);
 1095 
 1096     if ((ext = strrchr(file, '.')) != NULL)
 1097         lookup_mimetype(ext + 1, part);             /* Get MIME type/subtype */
 1098 
 1099     /*
 1100      * Needed for the mime-type processor
 1101      */
 1102     part->params = new_params();
 1103     part->params->name = my_strdup("name");
 1104     part->params->value = my_strdup(file);
 1105 
 1106     start_viewer(part, path);
 1107     my_printf(cCRLF);
 1108 
 1109     free_parts(part);
 1110 }
 1111 
 1112 
 1113 /* Single character decode. */
 1114 #define DEC(Char) (((Char) - ' ') & 077)
 1115 /*
 1116  * Decode 'buf' - write the uudecoded output to 'fp'
 1117  */
 1118 static void
 1119 uudecode_line(
 1120     const char *buf,
 1121     FILE *fp)
 1122 {
 1123     const char *p = buf;
 1124     char ch;
 1125     int n;
 1126 
 1127     n = DEC(*p);
 1128 
 1129     for (++p; n > 0; p += 4, n -= 3) {
 1130         if (n >= 3) {
 1131             ch = ((DEC(p[0]) << 2) | (DEC(p[1]) >> 4));
 1132             fputc(ch, fp);
 1133             ch = ((DEC(p[1]) << 4) | (DEC(p[2]) >> 2));
 1134             fputc(ch, fp);
 1135             ch = ((DEC(p[2]) << 6) | DEC(p[3]));
 1136             fputc(ch, fp);
 1137         } else {
 1138             if (n >= 1) {
 1139                 ch = ((DEC(p[0]) << 2) | (DEC(p[1]) >> 4));
 1140                 fputc(ch, fp);
 1141             }
 1142             if (n >= 2) {
 1143                 ch = ((DEC(p[1]) << 4) | (DEC(p[2]) >> 2));
 1144                 fputc(ch, fp);
 1145             }
 1146         }
 1147     }
 1148 }
 1149 
 1150 
 1151 /*
 1152  * Unpack /bin/sh archives
 1153  * There is no end-of-shar marker so the code reads everything after
 1154  * the start marker. This is why shar is handled separately.
 1155  * The code assumes shar archives do not span articles
 1156  */
 1157 static void
 1158 post_process_sh(
 1159     void)
 1160 {
 1161     FILE *fp_in, *fp_out = NULL;
 1162     char buf[LEN];
 1163     char file_out[PATH_LEN];
 1164     char file_out_dir[PATH_LEN];
 1165     int i;
 1166 
 1167     /*
 1168      * Grab the dirname portion
 1169      */
 1170     my_strncpy(file_out_dir, save[0].path, save[0].file - save[0].path);
 1171     snprintf(file_out, sizeof(file_out), "%ssh%ld", file_out_dir, (long) process_id);
 1172 
 1173     for (i = 0; i < num_save; i++) {
 1174         if ((fp_in = fopen(save[i].path, "r")) == NULL)
 1175             continue;
 1176 
 1177         wait_message(0, _(txt_extracting_shar), save[i].path);
 1178 
 1179         while (fgets(buf, (int) sizeof(buf), fp_in) != NULL) {
 1180             /* find #!/bin/sh style patterns */
 1181             if ((fp_out == NULL) && pcre_exec(shar_regex.re, shar_regex.extra, buf, strlen(buf), 0, 0, NULL, 0) >= 0)
 1182                 fp_out = fopen(file_out, "w");
 1183 
 1184             /* write to temp file */
 1185             if (fp_out != NULL)
 1186                 fputs(buf, fp_out);
 1187         }
 1188         fclose(fp_in);
 1189 
 1190         if (fp_out == NULL) {           /* Didn't extract any shar */
 1191             my_fputs(cCRLF, stdout);
 1192             continue;
 1193         }
 1194 
 1195         fclose(fp_out);
 1196         fp_out = NULL;
 1197         sh_format(buf, sizeof(buf), "cd %s; sh %s", file_out_dir, file_out);
 1198         my_fputs(cCRLF, stdout);
 1199         my_flush();
 1200         invoke_cmd(buf);            /* Handles its own errors */
 1201         unlink(file_out);
 1202     }
 1203 }
 1204 
 1205 
 1206 /*
 1207  * write tailing (MMDF)-mailbox separator
 1208  */
 1209 void
 1210 print_art_separator_line(
 1211     FILE *fp,
 1212     t_bool is_mailbox)
 1213 {
 1214 #ifdef DEBUG
 1215     if (debug & DEBUG_MISC)
 1216         error_message(2, "Mailbox=[%d], mailbox_format=[%s]", is_mailbox, txt_mailbox_formats[tinrc.mailbox_format]);
 1217 #endif /* DEBUG */
 1218 
 1219     fprintf(fp, "%s", (is_mailbox && !strcasecmp(txt_mailbox_formats[tinrc.mailbox_format], "MMDF")) ? MMDFHDRTXT : "\n");
 1220 }
 1221 
 1222 
 1223 /*
 1224  * part needs to have at least content type/subtype and a filename
 1225  * path = full path/file (used for substitution in mailcap entries)
 1226  */
 1227 static void
 1228 start_viewer(
 1229     t_part *part,
 1230     const char *path)
 1231 {
 1232     t_mailcap *foo;
 1233 
 1234     if ((foo = get_mailcap_entry(part, path)) != NULL) {
 1235         if (foo->nametemplate)  /* honor nametemplate */
 1236             rename_file(path, foo->nametemplate);
 1237 
 1238         wait_message(0, _(txt_starting_command), foo->command);
 1239         if (foo->needsterminal) {
 1240             set_xclick_off();
 1241             fflush(stdout);
 1242         } else {
 1243             if (foo->description)
 1244                 info_message("%s", foo->description);
 1245         }
 1246         invoke_cmd(foo->command);
 1247         if (foo->needsterminal) {
 1248 #ifndef USE_CURSES
 1249             EndWin();
 1250             Raw(FALSE);
 1251 #endif /* !USE_CURSES */
 1252             prompt_continue();
 1253 #ifndef USE_CURSES
 1254             Raw(TRUE);
 1255             InitWin();
 1256 #endif /* !USE_CURSES */
 1257         }
 1258         if (foo->nametemplate) /* undo nametemplate, needed as 'save'-prompt is done outside start_viewer */
 1259             rename_file(foo->nametemplate, path);
 1260         free_mailcap(foo);
 1261     } else
 1262         wait_message(1, _(txt_no_viewer_found), content_types[part->type], part->subtype);
 1263 }
 1264 
 1265 
 1266 /*
 1267  * Decode and save the binary object pointed to in 'part'
 1268  * Optionally launch a viewer for it
 1269  * Return FALSE if Abort used to skip further viewing/saving
 1270  * or other terminal error occurs
 1271  */
 1272 static t_bool
 1273 decode_save_one(
 1274     t_part *part,
 1275     FILE *rawfp,
 1276     t_bool postproc)
 1277 {
 1278     FILE *fp;
 1279     char buf[2048], buf2[2048];
 1280     char *savepath;
 1281     int count;
 1282     int i;
 1283 
 1284     /*
 1285      * Decode this message part if appropriate
 1286      */
 1287     if (!(check_save_mime_type(part, curr_group->attribute->mime_types_to_save))) {
 1288         /* TODO: skip message if saving multiple files (e.g. save 't'agged) */
 1289         wait_message(1, "Skipped %s/%s", content_types[part->type], part->subtype); /* TODO: better msg */
 1290         return TRUE;
 1291     }
 1292 
 1293     if ((savepath = generate_savepath(part)) == NULL)
 1294         return FALSE;
 1295 
 1296     /*
 1297      * Decode/save the attachment
 1298      */
 1299     if ((fp = open_save_filename(savepath, FALSE)) == NULL) {
 1300         free(savepath);
 1301         return FALSE;
 1302     }
 1303 
 1304     if (part->encoding == ENCODING_BASE64)
 1305         mmdecode(NULL, 'b', 0, NULL);               /* flush */
 1306 
 1307     fseek(rawfp, part->offset, SEEK_SET);
 1308 
 1309     for (i = 0; i < part->line_count; i++) {
 1310         if ((fgets(buf, sizeof(buf), rawfp)) == NULL)
 1311             break;
 1312 
 1313         /* This should catch cases where people illegally append text etc */
 1314         if (buf[0] == '\0')
 1315             break;
 1316 
 1317         switch (part->encoding) {
 1318             case ENCODING_QP:
 1319             case ENCODING_BASE64:
 1320                 count = mmdecode(buf, part->encoding == ENCODING_QP ? 'q' : 'b', '\0', buf2);
 1321                 fwrite(buf2, count, 1, fp);
 1322                 break;
 1323 
 1324             case ENCODING_UUE:
 1325                 /* TODO: if postproc, don't decode these since the traditional uudecoder will get them */
 1326                 /*
 1327                  * x-uuencode attachments have all the header info etc which we must ignore
 1328                  */
 1329                 if (strncmp(buf, "begin ", 6) != 0 && strncmp(buf, "end\n", 4) != 0 && buf[0] != '\n')
 1330                     uudecode_line(buf, fp);
 1331                 break;
 1332 
 1333             default:
 1334                 fputs(buf, fp);
 1335         }
 1336     }
 1337     fclose(fp);
 1338 
 1339     /*
 1340      * View the attachment
 1341      */
 1342     if ((postproc && curr_group->attribute->post_process_view) || !curr_group->attribute->ask_for_metamail) {
 1343             start_viewer(part, savepath);
 1344             my_printf(cCRLF);
 1345     } else {
 1346         snprintf(buf, sizeof(buf), _(txt_view_attachment), savepath, content_types[part->type], part->subtype);
 1347         if ((i = prompt_yn(buf, TRUE)) == 1)
 1348             start_viewer(part, savepath);
 1349         else if (i == -1) { /* Skip rest of attachments */
 1350             unlink(savepath);
 1351             free(savepath);
 1352             return FALSE;
 1353         }
 1354     }
 1355 
 1356     /*
 1357      * Save the attachment
 1358      */
 1359     if (postproc && curr_group->attribute->post_process_view) {
 1360         my_printf(_(txt_uu_success), savepath);
 1361         my_printf(cCRLF);
 1362     }
 1363     if (!postproc) {
 1364         snprintf(buf, sizeof(buf), _(txt_save_attachment), savepath, content_types[part->type], part->subtype);
 1365         if ((i = prompt_yn(buf, FALSE)) != 1) {
 1366             unlink(savepath);
 1367             if (i == -1) {  /* Skip rest of attachments */
 1368                 free(savepath);
 1369                 return FALSE;
 1370             }
 1371         }
 1372     }
 1373     free(savepath);
 1374     return TRUE;
 1375 }
 1376 
 1377 
 1378 enum match {
 1379     NO,
 1380     MATCH,
 1381     NOTMATCH
 1382 };
 1383 
 1384 /*
 1385  * Match a single type/subtype Content pair
 1386  * Returns:
 1387  * NO = Not matched
 1388  * MATCH = Matched
 1389  * NOTMATCH = Matched, but !negated
 1390  */
 1391 static int
 1392 match_content_type(
 1393     t_part *part,
 1394     char *type)
 1395 {
 1396     char *subtype;
 1397     int typeindex;
 1398     t_bool found = FALSE;
 1399     t_bool negate = FALSE;
 1400 
 1401     /* Check for negation */
 1402     if (*type == '!') {
 1403         negate = TRUE;
 1404         ++type;
 1405 
 1406         if (!*type)             /* Invalid type */
 1407             return NO;
 1408     }
 1409 
 1410     /* Split type and subtype */
 1411     if ((subtype = strchr(type, '/')) == NULL)
 1412         return NO;
 1413     *(subtype++) = '\0';
 1414 
 1415     if (!*type || !*subtype)    /* Missing type or subtype */
 1416         return NO;
 1417 
 1418     /* Try and match major */
 1419     if (strcmp(type, "*") == 0)
 1420         found = TRUE;
 1421     else if (((typeindex = content_type(type)) != -1) && typeindex == part->type)
 1422         found = TRUE;
 1423 
 1424     if (!found)
 1425         return NO;
 1426 
 1427     /* Try and match subtype */
 1428     found = FALSE;
 1429     if (strcmp(subtype, "*") == 0)
 1430         found = TRUE;
 1431     else if (strcmp(subtype, part->subtype) == 0)
 1432         found = TRUE;
 1433 
 1434     if (!found)
 1435         return NO;
 1436 
 1437     /* We got a match */
 1438     if (negate)
 1439         return NOTMATCH;
 1440 
 1441     return MATCH;
 1442 }
 1443 
 1444 
 1445 /*
 1446  * See if the mime type of this part matches the list of content types to save
 1447  * or ignore. Return TRUE if there is a match
 1448  * mime_types is a comma separated list of type/subtype pairs. type and/or
 1449  * subtype can be a '*' to match any, and a pair can begin with a ! which
 1450  * will negate the meaning. We eval all pairs, the rightmost match will
 1451  * prevail
 1452  */
 1453 static t_bool
 1454 check_save_mime_type(
 1455     t_part *part,
 1456     const char *mime_types)
 1457 {
 1458     char *ptr, *pair;
 1459     int found;
 1460     int retcode;
 1461 
 1462     if (!mime_types)
 1463         return FALSE;
 1464 
 1465     ptr = my_strdup(mime_types);
 1466 
 1467     if ((pair = strtok(ptr, ",")) == NULL) {
 1468         free(ptr);
 1469         return FALSE;
 1470     }
 1471 
 1472     retcode = match_content_type(part, pair);
 1473 
 1474     while ((pair = strtok(NULL, ",")) != NULL) {
 1475         if ((found = match_content_type(part, pair)) != NO)
 1476             retcode = found;
 1477     }
 1478 
 1479     free(ptr);
 1480     return (retcode == MATCH);
 1481 }
 1482 
 1483 
 1484 /*
 1485  * decode and save binary MIME attachments from an open article context
 1486  * optionally locate and launch a viewer application
 1487  * 'postproc' determines the mode of the operation and will be set to
 1488  * TRUE when we're called during a [Ss]ave operation and FALSE when
 1489  * when just viewing
 1490  * When it is TRUE the view option will depend on post_process_view and
 1491  * the save is implicit. Feedback will also be printed.
 1492  * When it is FALSE then the view/save options will be queried
 1493  */
 1494 void
 1495 decode_save_mime(
 1496     t_openartinfo *art,
 1497     t_bool postproc)
 1498 {
 1499     t_part *ptr, *uueptr;
 1500 
 1501     /*
 1502      * Iterate over all the attachments
 1503      */
 1504     for (ptr = art->hdr.ext; ptr != NULL; ptr = ptr->next) {
 1505         /*
 1506          * Handle uuencoded sections in this message part.
 1507          * Only works when the uuencoded file is entirely within the current
 1508          * article.
 1509          * We don't do this when postprocessing as the generic uudecode code
 1510          * already handles uuencoded data, but TODO: review this
 1511          */
 1512         if (!postproc) {
 1513             for (uueptr = ptr->uue; uueptr != NULL; uueptr = uueptr->next) {
 1514                 if (!(decode_save_one(uueptr, art->raw, postproc)))
 1515                     break;
 1516             }
 1517         }
 1518 
 1519         /*
 1520          * TYPE_MULTIPART is an envelope type, don't process it.
 1521          * If we had an UUE part, the "surrounding" text/plain plays
 1522          * the role of a multipart part. Check to see if we want to
 1523          * save text and if not, skip this part.
 1524          */
 1525          /* check_save_mime_type() is done in decode_save_one() and the check for ptr->uue must be done unconditionally */
 1526         if (ptr->type == TYPE_MULTIPART || (NULL != ptr->uue /* && !check_save_mime_type(ptr, curr_group->attribute->mime_types_to_save) */ ))
 1527             continue;
 1528 
 1529         if (!(decode_save_one(ptr, art->raw, postproc)))
 1530             break;
 1531     }
 1532 }
 1533 
 1534 
 1535 /*
 1536  * Attachment menu
 1537  */
 1538 static void
 1539 show_attachment_page(
 1540     void)
 1541 {
 1542     char buf[BUFSIZ];
 1543     const char *charset;
 1544     int i, tmp_len, max_depth;
 1545     t_part *part;
 1546 
 1547     signal_context = cAttachment;
 1548     currmenu = &attmenu;
 1549     mark_offset = 0;
 1550 
 1551     if (attmenu.curr < 0)
 1552         attmenu.curr = 0;
 1553 
 1554     info_len = max_depth = 0;
 1555     for (i = 0; i < attmenu.max; ++i) {
 1556         part = get_part(i);
 1557         snprintf(buf, sizeof(buf), _(txt_attachment_lines), part->line_count);
 1558         tmp_len = strwidth(buf);
 1559         charset = get_param(part->params, "charset");
 1560         snprintf(buf, sizeof(buf), "  %s/%s, %s, %s%s", content_types[part->type], part->subtype, content_encodings[part->encoding], charset ? charset : "", charset ? ", " : "");
 1561         tmp_len += strwidth(buf);
 1562         if (tmp_len > info_len)
 1563             info_len = tmp_len;
 1564 
 1565         tmp_len = part->depth;
 1566         if (tmp_len > max_depth)
 1567             max_depth = tmp_len;
 1568     }
 1569     tmp_len = cCOLS - 13 - MIN((cCOLS - 13) / 2 + 10, max_depth * 2 + 1 + strwidth(_(txt_attachment_no_name)));
 1570     if (info_len > tmp_len)
 1571         info_len = tmp_len;
 1572 
 1573     ClearScreen();
 1574     set_first_screen_item();
 1575     center_line(0, TRUE, _(txt_attachment_menu));
 1576 
 1577     for (i = attmenu.first; i < attmenu.first + NOTESLINES && i < attmenu.max; ++i)
 1578         build_attachment_line(i);
 1579 
 1580     show_mini_help(ATTACHMENT_LEVEL);
 1581 
 1582     if (attmenu.max <= 0) {
 1583         info_message(_(txt_no_attachments));
 1584         return;
 1585     }
 1586 
 1587     draw_attachment_arrow();
 1588 }
 1589 
 1590 
 1591 void
 1592 attachment_page(
 1593     t_openartinfo *art)
 1594 {
 1595     char key[MAXKEYLEN];
 1596     t_function func;
 1597     t_menu *oldmenu = NULL;
 1598     t_part *part;
 1599 
 1600     if (currmenu)
 1601         oldmenu = currmenu;
 1602     num_of_tagged_parts = 0;
 1603     attmenu.curr = 0;
 1604     attmenu.max = build_part_list(art);
 1605     clear_note_area();
 1606     show_attachment_page();
 1607     set_xclick_off();
 1608 
 1609     forever {
 1610         switch ((func = handle_keypad(attachment_left, attachment_right, NULL, attachment_keys))) {
 1611             case GLOBAL_QUIT:
 1612                 free_part_list(part_list);
 1613                 if (oldmenu)
 1614                     currmenu = oldmenu;
 1615                 return;
 1616 
 1617             case DIGIT_1:
 1618             case DIGIT_2:
 1619             case DIGIT_3:
 1620             case DIGIT_4:
 1621             case DIGIT_5:
 1622             case DIGIT_6:
 1623             case DIGIT_7:
 1624             case DIGIT_8:
 1625             case DIGIT_9:
 1626                 if (attmenu.max)
 1627                     prompt_item_num(func_to_key(func, attachment_keys), _(txt_attachment_select));
 1628                 break;
 1629 
 1630 #ifndef NO_SHELL_ESCAPE
 1631             case GLOBAL_SHELL_ESCAPE:
 1632                 do_shell_escape();
 1633                 break;
 1634 #endif /* !NO_SHELL_ESCAPE */
 1635 
 1636             case GLOBAL_HELP:
 1637                 show_help_page(ATTACHMENT_LEVEL, _(txt_attachment_menu_com));
 1638                 show_attachment_page();
 1639                 break;
 1640 
 1641             case GLOBAL_BUGREPORT:
 1642                 bug_report();
 1643                 show_attachment_page();
 1644                 break;
 1645 
 1646             case GLOBAL_FIRST_PAGE:
 1647                 top_of_list();
 1648                 break;
 1649 
 1650             case GLOBAL_LAST_PAGE:
 1651                 end_of_list();
 1652                 break;
 1653 
 1654             case GLOBAL_REDRAW_SCREEN:
 1655                 my_retouch();
 1656                 show_attachment_page();
 1657                 break;
 1658 
 1659             case GLOBAL_LINE_DOWN:
 1660                 move_down();
 1661                 break;
 1662 
 1663             case GLOBAL_LINE_UP:
 1664                 move_up();
 1665                 break;
 1666 
 1667             case GLOBAL_PAGE_DOWN:
 1668                 page_down();
 1669                 break;
 1670 
 1671             case GLOBAL_PAGE_UP:
 1672                 page_up();
 1673                 break;
 1674 
 1675             case GLOBAL_SCROLL_DOWN:
 1676                 scroll_down();
 1677                 break;
 1678 
 1679             case GLOBAL_SCROLL_UP:
 1680                 scroll_up();
 1681                 break;
 1682 
 1683             case GLOBAL_TOGGLE_HELP_DISPLAY:
 1684                 toggle_mini_help(ATTACHMENT_LEVEL);
 1685                 show_attachment_page();
 1686                 break;
 1687 
 1688             case GLOBAL_TOGGLE_INFO_LAST_LINE:
 1689                 tinrc.info_in_last_line = bool_not(tinrc.info_in_last_line);
 1690                 show_attachment_page();
 1691                 break;
 1692 
 1693             case ATTACHMENT_SAVE:
 1694                 if (attmenu.max) {
 1695                     part = get_part(attmenu.curr);
 1696                     process_parts(part, art, num_of_tagged_parts ? SAVE_TAGGED : SAVE);
 1697                     show_attachment_page();
 1698                 }
 1699                 break;
 1700 
 1701             case ATTACHMENT_SELECT:
 1702                 if (attmenu.max) {
 1703                     part = get_part(attmenu.curr);
 1704                     process_parts(part, art, VIEW);
 1705                     show_attachment_page();
 1706                 }
 1707                 break;
 1708 
 1709             case ATTACHMENT_TAG:
 1710                 if (attmenu.max) {
 1711                     t_bool tagged;
 1712 
 1713                     tagged = tag_part(attmenu.curr);
 1714                     show_attachment_page();
 1715                     if (attmenu.curr + 1 < attmenu.max)
 1716                         move_down();
 1717                     info_message(tagged ? _(txt_attachment_tagged) : _(txt_attachment_untagged));
 1718                 }
 1719                 break;
 1720 
 1721             case ATTACHMENT_UNTAG:
 1722                 if (attmenu.max && num_of_tagged_parts) {
 1723                     untag_all_parts();
 1724                     show_attachment_page();
 1725                 }
 1726                 break;
 1727 
 1728             case ATTACHMENT_TAG_PATTERN:
 1729                 if (attmenu.max) {
 1730                     tag_pattern();
 1731                     show_attachment_page();
 1732                     info_message(_(txt_attachments_tagged), num_of_tagged_parts);
 1733                 }
 1734                 break;
 1735 
 1736             case ATTACHMENT_TOGGLE_TAGGED:
 1737                 if (attmenu.max) {
 1738                     int i;
 1739 
 1740                     for (i = attmenu.first; i < attmenu.max; ++i)
 1741                         tag_part(i);
 1742                     show_attachment_page();
 1743                     info_message(_(txt_attachments_tagged), num_of_tagged_parts);
 1744                 }
 1745                 break;
 1746 
 1747             case GLOBAL_SEARCH_SUBJECT_FORWARD:
 1748             case GLOBAL_SEARCH_SUBJECT_BACKWARD:
 1749             case GLOBAL_SEARCH_REPEAT:
 1750                 if (func == GLOBAL_SEARCH_REPEAT && last_search != GLOBAL_SEARCH_SUBJECT_FORWARD && last_search != GLOBAL_SEARCH_SUBJECT_BACKWARD)
 1751                     info_message(_(txt_no_prev_search));
 1752                 else if (attmenu.max) {
 1753                     int new_pos, old_pos = attmenu.curr;
 1754 
 1755                     new_pos = generic_search((func == GLOBAL_SEARCH_SUBJECT_FORWARD), (func == GLOBAL_SEARCH_REPEAT), attmenu.curr, attmenu.max - 1, ATTACHMENT_LEVEL);
 1756                     if (new_pos != old_pos)
 1757                         move_to_item(new_pos);
 1758                 }
 1759                 break;
 1760 
 1761 #ifndef DONT_HAVE_PIPING
 1762             case ATTACHMENT_PIPE:
 1763             case GLOBAL_PIPE:
 1764                 if (attmenu.max) {
 1765                     part = get_part(attmenu.curr);
 1766                     process_parts(part, art, func == GLOBAL_PIPE ? PIPE_RAW : PIPE);
 1767                     show_attachment_page();
 1768                 }
 1769                 break;
 1770 #endif /* !DONT_HAVE_PIPING */
 1771 
 1772             default:
 1773                 info_message(_(txt_bad_command), printascii(key, func_to_key(GLOBAL_HELP, attachment_keys)));
 1774                 break;
 1775         }
 1776     }
 1777 }
 1778 
 1779 
 1780 static t_function
 1781 attachment_left(
 1782     void)
 1783 {
 1784     return GLOBAL_QUIT;
 1785 }
 1786 
 1787 
 1788 static t_function
 1789 attachment_right(
 1790     void)
 1791 {
 1792     return ATTACHMENT_SELECT;
 1793 }
 1794 
 1795 
 1796 static void
 1797 draw_attachment_arrow(
 1798     void)
 1799 {
 1800     draw_arrow_mark(INDEX_TOP + attmenu.curr - attmenu.first);
 1801     if (tinrc.info_in_last_line) {
 1802         const char *name;
 1803         t_part *part;
 1804 
 1805         part = get_part(attmenu.curr);
 1806         name = get_filename(part->params);
 1807         info_message("%s  %s", name ? name : _(txt_attachment_no_name), BlankIfNull(part->description));
 1808     } else if (attmenu.curr == attmenu.max - 1)
 1809         info_message(_(txt_end_of_attachments));
 1810 }
 1811 
 1812 
 1813 static void
 1814 build_attachment_line(
 1815     int i)
 1816 {
 1817     char *sptr;
 1818     const char *name;
 1819     const char *charset;
 1820 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1821     char *tmpname;
 1822     char *tmpbuf;
 1823 #endif /* MULTIBYTE_ABLE && !NOLOCALE */
 1824     char buf[BUFSIZ];
 1825     char buf2[BUFSIZ];
 1826     char *tree = NULL;
 1827     int len, namelen, tagged, treelen;
 1828     t_part *part;
 1829 
 1830 #ifdef USE_CURSES
 1831     /*
 1832      * Allocate line buffer
 1833      * make it the same size like in !USE_CURSES case to simplify some code
 1834      */
 1835 #   if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1836         sptr = my_malloc(cCOLS * MB_CUR_MAX + 2);
 1837 #   else
 1838         sptr = my_malloc(cCOLS + 2);
 1839 #   endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1840 #else
 1841     sptr = screen[INDEX2SNUM(i)].col;
 1842 #endif /* USE_CURSES */
 1843 
 1844     part = get_part(i);
 1845     namelen = MIN(cCOLS - 13 - info_len - 8, strwidth(_(txt_attachment_no_name)));
 1846     tagged = get_tagged(i);
 1847 
 1848     if (!(name = get_filename(part->params))) {
 1849         if (!(name = part->description))
 1850             name = _(txt_attachment_no_name);
 1851     }
 1852 
 1853     charset = get_param(part->params, "charset");
 1854     snprintf(buf2, sizeof(buf2), _(txt_attachment_lines), part->line_count);
 1855     /* TODO: make the layout configurable? */
 1856     if (!strcmp(content_types[part->type], "text"))
 1857         snprintf(buf, sizeof(buf), "  %s/%s, %s, %s%s%s", content_types[part->type], part->subtype, content_encodings[part->encoding], charset ? charset : "", charset ? ", " : "", buf2);
 1858     else
 1859         snprintf(buf, sizeof(buf), "  %s/%s, %s, %s", content_types[part->type], part->subtype, content_encodings[part->encoding], buf2);
 1860     if (part->depth > 0) {
 1861         treelen = cCOLS - 13 - info_len - namelen;
 1862         tree = build_tree(part->depth, treelen, i);
 1863     }
 1864     snprintf(buf2, sizeof(buf2), "%s  %s", tagged ? tin_ltoa(tagged, 3) : "   ", BlankIfNull(tree));
 1865     FreeIfNeeded(tree);
 1866     len = strwidth(buf2);
 1867     if (namelen + len + info_len + 8 <= cCOLS)
 1868         namelen = cCOLS - 8 - info_len - len;
 1869 
 1870 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1871     tmpname = spart(name, namelen, TRUE);
 1872     tmpbuf = spart(buf, info_len, TRUE);
 1873     snprintf(sptr, cCOLS * MB_CUR_MAX, "  %s %s%*s%*s%s", tin_ltoa(i + 1, 4), buf2, namelen, BlankIfNull(tmpname), info_len, BlankIfNull(tmpbuf), cCRLF);
 1874     FreeIfNeeded(tmpname);
 1875     FreeIfNeeded(tmpbuf);
 1876 #else
 1877     snprintf(sptr, cCOLS, "  %s %s%-*.*s%*.*s%s", tin_ltoa(i + 1, 4), buf2, namelen, namelen, name, info_len, info_len, buf, cCRLF);
 1878 #endif /* MULTIBYTE_ABLE && !NOLOCALE */
 1879 
 1880     WriteLine(INDEX2LNUM(i), sptr);
 1881 
 1882 #ifdef USE_CURSES
 1883     free(sptr);
 1884 #endif /* USE_CURSES */
 1885 }
 1886 
 1887 
 1888 /*
 1889  * Build attachment tree. Code adopted
 1890  * from thread.c:make_prefix().
 1891  */
 1892 static char *
 1893 build_tree(
 1894     int depth,
 1895     int maxlen,
 1896     int i)
 1897 {
 1898 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1899     char *result;
 1900     wchar_t *tree;
 1901 #else
 1902     char *tree;
 1903 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1904     int prefix_ptr, tmpdepth;
 1905     int depth_level = 0;
 1906     t_bool found = FALSE;
 1907     t_partl *lptr, *lptr2;
 1908 
 1909     lptr2 = find_part(i);
 1910     prefix_ptr = depth * 2 - 1;
 1911     if (prefix_ptr > maxlen - 1 - !(maxlen % 2)) {
 1912         int odd = ((maxlen % 2) ? 0 : 1);
 1913 
 1914         prefix_ptr -= maxlen - ++depth_level - 2 - odd;
 1915         while (prefix_ptr > maxlen - 2 - odd) {
 1916             if (depth_level < maxlen / 5)
 1917                 depth_level++;
 1918 
 1919             prefix_ptr -= maxlen - depth_level - 2 - odd;
 1920             odd = (odd ? 0 : 1);
 1921         }
 1922     }
 1923 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1924     tree = my_malloc(sizeof(wchar_t) * prefix_ptr + 3 * sizeof(wchar_t));
 1925     tree[prefix_ptr + 2] = (wchar_t) '\0';
 1926 #else
 1927     tree = my_malloc(prefix_ptr + 3);
 1928     tree[prefix_ptr + 2] = '\0';
 1929 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1930     tree[prefix_ptr + 1] = TREE_ARROW;
 1931     tree[prefix_ptr] = TREE_HORIZ;
 1932     for (lptr = lptr2->next; lptr != NULL; lptr = lptr->next) {
 1933         if (lptr->part->depth == depth) {
 1934             found = TRUE;
 1935             break;
 1936         }
 1937         if (lptr->part->depth < depth)
 1938             break;
 1939     }
 1940     tree[--prefix_ptr] = found ? TREE_VERT_RIGHT : TREE_UP_RIGHT;
 1941     found = FALSE;
 1942     for (tmpdepth = depth - 1; prefix_ptr > 1; --tmpdepth) {
 1943         for (lptr = lptr2->next; lptr != NULL; lptr = lptr->next) {
 1944             if (lptr->part->depth == tmpdepth) {
 1945                 found = TRUE;
 1946                 break;
 1947             }
 1948             if (lptr->part->depth < tmpdepth)
 1949                 break;
 1950         }
 1951         tree[--prefix_ptr] = TREE_BLANK;
 1952         tree[--prefix_ptr] = found ? TREE_VERT : TREE_BLANK;
 1953         found = FALSE;
 1954     }
 1955     while (depth_level)
 1956         tree[--depth_level] = TREE_ARROW_WRAP;
 1957 
 1958 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1959     result = wchar_t2char(tree);
 1960     free(tree);
 1961     return result;
 1962 #else
 1963     return tree;
 1964 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1965 }
 1966 
 1967 
 1968 /*
 1969  * Find nth attachment in part_list.
 1970  * Return pointer to that part.
 1971  */
 1972 static t_partl *
 1973 find_part(
 1974     int n)
 1975 {
 1976     t_partl *lptr;
 1977 
 1978     lptr = part_list;
 1979     if (attmenu.max >= 1)
 1980         lptr = lptr->next;
 1981 
 1982     while (n-- > 0 && lptr->next)
 1983         lptr = lptr->next;
 1984 
 1985     return lptr;
 1986 }
 1987 
 1988 
 1989 t_part *
 1990 get_part(
 1991     int n)
 1992 {
 1993     t_partl *lptr;
 1994 
 1995     lptr = find_part(n);
 1996     return lptr->part;
 1997 }
 1998 
 1999 
 2000 static void
 2001 tag_pattern(
 2002     void)
 2003 {
 2004     char buf[BUFSIZ];
 2005     char pat[128];
 2006     char *prompt;
 2007     const char *name;
 2008     const char *charset;
 2009     struct regex_cache cache = { NULL, NULL };
 2010     t_part *part;
 2011     t_partl *lptr;
 2012 
 2013 #if 0
 2014     if (num_of_tagged_parts)
 2015         untag_all_parts();
 2016 #endif /* 0 */
 2017 
 2018     prompt = fmt_string(_(txt_select_pattern), tinrc.default_select_pattern);
 2019     if (!(prompt_string_default(prompt, tinrc.default_select_pattern, _(txt_info_no_previous_expression), HIST_SELECT_PATTERN))) {
 2020         free(prompt);
 2021         return;
 2022     }
 2023     free(prompt);
 2024 
 2025     if (STRCMPEQ(tinrc.default_select_pattern, "*")) {  /* all */
 2026         if (tinrc.wildcard)
 2027             STRCPY(pat, ".*");
 2028         else
 2029             STRCPY(pat, tinrc.default_select_pattern);
 2030     } else
 2031         snprintf(pat, sizeof(pat), REGEX_FMT, tinrc.default_select_pattern);
 2032 
 2033     if (tinrc.wildcard && !(compile_regex(pat, &cache, PCRE_CASELESS)))
 2034         return;
 2035 
 2036     lptr = find_part(0);
 2037 
 2038     for (; lptr != NULL; lptr = lptr->next) {
 2039         part = lptr->part;
 2040         if (!(name = get_filename(part->params))) {
 2041             if (!(name = part->description))
 2042                 name = _(txt_attachment_no_name);
 2043         }
 2044         charset = get_param(part->params, "charset");
 2045 
 2046         snprintf(buf, sizeof(buf), "%s %s/%s %s, %s", name, content_types[part->type], part->subtype, content_encodings[part->encoding], charset ? charset : "");
 2047 
 2048         if (!match_regex(buf, pat, &cache, TRUE)) {
 2049             continue;
 2050         }
 2051         if (!lptr->tagged)
 2052             lptr->tagged = ++num_of_tagged_parts;
 2053     }
 2054 
 2055     if (tinrc.wildcard) {
 2056         FreeIfNeeded(cache.re);
 2057         FreeIfNeeded(cache.extra);
 2058     }
 2059 }
 2060 
 2061 
 2062 static int
 2063 get_tagged(
 2064     int n)
 2065 {
 2066     t_partl *lptr;
 2067 
 2068     lptr = find_part(n);
 2069     return lptr->tagged;
 2070 }
 2071 
 2072 
 2073 static t_bool
 2074 tag_part(
 2075     int n)
 2076 {
 2077     t_partl *lptr;
 2078 
 2079     lptr = find_part(n);
 2080     if (lptr->tagged) {
 2081         untag_part(n);
 2082         return FALSE;
 2083     } else {
 2084         lptr->tagged = ++num_of_tagged_parts;
 2085         return TRUE;
 2086     }
 2087 }
 2088 
 2089 
 2090 static void
 2091 untag_part(
 2092     int n)
 2093 {
 2094     int i;
 2095     t_partl *curr_part, *lptr;
 2096 
 2097     lptr = find_part(0);
 2098     curr_part = find_part(n);
 2099     i = attmenu.max;
 2100 
 2101     while (i-- > 0 && lptr) {
 2102         if (lptr->tagged > curr_part->tagged)
 2103             --lptr->tagged;
 2104         lptr = lptr->next;
 2105     }
 2106 
 2107     curr_part->tagged = 0;
 2108     --num_of_tagged_parts;
 2109 }
 2110 
 2111 
 2112 static void
 2113 untag_all_parts(
 2114     void)
 2115 {
 2116     t_partl *lptr = part_list;
 2117 
 2118     while (lptr) {
 2119         if (lptr->tagged) {
 2120             lptr->tagged = 0;
 2121         }
 2122         lptr = lptr->next;
 2123     }
 2124     num_of_tagged_parts = 0;
 2125 }
 2126 
 2127 
 2128 /*
 2129  * Build a linked list which holds pointers to the parts we want deal with.
 2130  */
 2131 static int
 2132 build_part_list(
 2133     t_openartinfo *art)
 2134 {
 2135     int i = 0;
 2136     t_part *ptr, *uueptr;
 2137     t_partl *lptr;
 2138 
 2139     part_list = my_malloc(sizeof(t_partl));
 2140     lptr = part_list;
 2141     lptr->part = art->hdr.ext;
 2142     lptr->next = NULL;
 2143     lptr->tagged = 0;
 2144     for (ptr = art->hdr.ext; ptr != NULL; ptr = ptr->next) {
 2145         if ((uueptr = ptr->uue) != NULL) {
 2146             lptr->next = my_malloc(sizeof(t_partl));
 2147             lptr->next->part = ptr;
 2148             lptr->next->next = NULL;
 2149             lptr->next->tagged = 0;
 2150             lptr = lptr->next;
 2151             ++i;
 2152             for (; uueptr != NULL; uueptr = uueptr->next) {
 2153                 lptr->next = my_malloc(sizeof(t_partl));
 2154                 lptr->next->part = uueptr;
 2155                 lptr->next->next = NULL;
 2156                 lptr->next->tagged = 0;
 2157                 lptr = lptr->next;
 2158                 ++i;
 2159             }
 2160         }
 2161 
 2162         if (ptr->uue)
 2163             continue;
 2164 
 2165         lptr->next = my_malloc(sizeof(t_partl));
 2166         lptr->next->part = ptr;
 2167         lptr->next->next = NULL;
 2168         lptr->next->tagged = 0;
 2169         lptr = lptr->next;
 2170         ++i;
 2171     }
 2172     return i;
 2173 }
 2174 
 2175 
 2176 static void
 2177 free_part_list(
 2178     t_partl *list)
 2179 {
 2180     while (list->next != NULL) {
 2181         free_part_list(list->next);
 2182         list->next = NULL;
 2183     }
 2184     free(list);
 2185 }
 2186 
 2187 
 2188 static void
 2189 process_parts(
 2190     t_part *part,
 2191     t_openartinfo *art,
 2192     enum action what)
 2193 {
 2194     FILE *fp;
 2195     char *savepath = NULL, *tmppath;
 2196     int i, saved_parts = 0;
 2197     t_partl *lptr;
 2198 
 2199     switch (what) {
 2200         case SAVE_TAGGED:
 2201             for (i = 1; i <= num_of_tagged_parts; i++) {
 2202                 lptr = part_list;
 2203 
 2204                 while (lptr) {
 2205                     if (lptr->tagged == i) {
 2206                         if ((savepath = generate_savepath(lptr->part)) == NULL)
 2207                             return;
 2208 
 2209                         if ((fp = open_save_filename(savepath, FALSE)) == NULL) {
 2210                             free(savepath);
 2211                             return;
 2212                         }
 2213                         process_part(lptr->part, art, fp, NULL, SAVE);
 2214                         free(savepath);
 2215                         ++saved_parts;
 2216                     }
 2217                     lptr = lptr->next;
 2218                 }
 2219             }
 2220             break;
 2221 
 2222         default:
 2223             if ((tmppath = generate_savepath(part)) == NULL)
 2224                 return;
 2225 
 2226             if (what == SAVE)
 2227                 savepath = tmppath;
 2228             else {
 2229                 savepath = get_tmpfilename(tmppath);
 2230                 free(tmppath);
 2231             }
 2232             if ((fp = open_save_filename(savepath, FALSE)) == NULL) {
 2233                 free(savepath);
 2234                 return;
 2235             }
 2236             process_part(part, art, fp, savepath, what);
 2237             break;
 2238     }
 2239     switch (what) {
 2240         case SAVE_TAGGED:
 2241             wait_message(2, _(txt_attachments_saved), saved_parts, num_of_tagged_parts);
 2242             break;
 2243 
 2244         case SAVE:
 2245             wait_message(2, _(txt_attachment_saved), savepath);
 2246             free(savepath);
 2247             break;
 2248 
 2249         default:
 2250             unlink(savepath);
 2251             free(savepath);
 2252             break;
 2253     }
 2254     cursoroff();
 2255 }
 2256 
 2257 
 2258 /*
 2259  * VIEW/PIPE/SAVE the given part.
 2260  *
 2261  * PIPE_RAW uses the raw part, otherwise the part is decoded first.
 2262  */
 2263 static void
 2264 process_part(
 2265     t_part *part,
 2266     t_openartinfo *art,
 2267     FILE *outfile,
 2268     const char *savepath,
 2269     enum action what)
 2270 {
 2271     FILE *infile;
 2272     char buf[2048], buf2[2048];
 2273     int count;
 2274     int i, line_count;
 2275 #ifdef CHARSET_CONVERSION
 2276     char *conv_buf;
 2277     const char *network_charset;
 2278     size_t line_len;
 2279 #endif /* CHARSET_CONVERSION */
 2280 
 2281     /*
 2282      * uuencoded parts must be read from the cooked article,
 2283      * otherwise they might be additionally encoded with b64 or qp
 2284      */
 2285     if (part->encoding == ENCODING_UUE)
 2286         infile = art->cooked;
 2287     else
 2288         infile = art->raw;
 2289 
 2290     if (what != PIPE_RAW && part->encoding == ENCODING_BASE64)
 2291         mmdecode(NULL, 'b', 0, NULL);               /* flush */
 2292 
 2293     fseek(infile, part->offset, SEEK_SET);
 2294 
 2295     line_count = part->line_count;
 2296 
 2297     for (i = 0; i < line_count; i++) {
 2298         if ((fgets(buf, sizeof(buf), infile)) == NULL)
 2299             break;
 2300 
 2301         /* This should catch cases where people illegally append text etc */
 2302         if (buf[0] == '\0')
 2303             break;
 2304 
 2305         /*
 2306          * page.c:new_uue() sets offset to the 'begin ...' line
 2307          * -> skip over the first line in uuencoded parts
 2308          */
 2309         if (part->encoding == ENCODING_UUE && i == 0) {
 2310             ++line_count;
 2311             continue;
 2312         }
 2313 
 2314         if (what != PIPE_RAW) {
 2315             switch (part->encoding) {
 2316                 case ENCODING_QP:
 2317                 case ENCODING_BASE64:
 2318 #ifdef CHARSET_CONVERSION
 2319                     memset(buf2, '\0', sizeof(buf2));
 2320 #endif /* CHARSET_CONVERSION */
 2321                     if ((count = mmdecode(buf, part->encoding == ENCODING_QP ? 'q' : 'b', '\0', buf2)) > 0) {
 2322 #ifdef CHARSET_CONVERSION
 2323                         if (what != SAVE && what != SAVE_TAGGED && !strncmp(content_types[part->type], "text", 4)) {
 2324                             line_len = count;
 2325                             conv_buf = my_strdup(buf2);
 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(buf2, conv_buf, sizeof(buf2) - 1);
 2329                             count = strlen(buf2);
 2330                             free(conv_buf);
 2331                         }
 2332 #endif /* CHARSET_CONVERSION */
 2333                         fwrite(buf2, count, 1, outfile);
 2334                     }
 2335                     break;
 2336 
 2337                 case ENCODING_UUE:
 2338                     /* TODO: if postproc, don't decode these since the traditional uudecoder will get them */
 2339                     /*
 2340                      * x-uuencode attachments have all the header info etc which we must ignore
 2341                      */
 2342                     if (strncmp(buf, "begin ", 6) != 0 && strncmp(buf, "end\n", 4) != 0 && buf[0] != '\n')
 2343                         uudecode_line(buf, outfile);
 2344                     break;
 2345 
 2346                 default:
 2347 #ifdef CHARSET_CONVERSION
 2348                         if (what != SAVE && what != SAVE_TAGGED && !strncmp(content_types[part->type], "text", 4)) {
 2349                             conv_buf = my_strdup(buf);
 2350                             line_len = strlen(conv_buf);
 2351                             network_charset = get_param(part->params, "charset");
 2352                             process_charsets(&conv_buf, &line_len, network_charset ? network_charset : "US-ASCII", tinrc.mm_local_charset, FALSE);
 2353                             strncpy(buf, conv_buf, sizeof(buf) - 1);
 2354                             free(conv_buf);
 2355                         }
 2356 #endif /* CHARSET_CONVERSION */
 2357                     fputs(buf, outfile);
 2358             }
 2359         } else
 2360             fputs(buf, outfile);
 2361     }
 2362 
 2363     fclose(outfile);
 2364 
 2365     switch (what) {
 2366         case VIEW:
 2367             start_viewer(part, savepath);
 2368             break;
 2369 
 2370 #ifndef DONT_HAVE_PIPING
 2371         case PIPE:
 2372         case PIPE_RAW:
 2373             pipe_part(savepath);
 2374             break;
 2375 #endif /* !DONT_HAVE_PIPING */
 2376 
 2377         default:
 2378             break;
 2379     }
 2380 }
 2381 
 2382 
 2383 #ifndef DONT_HAVE_PIPING
 2384 static void
 2385 pipe_part(
 2386     const char *savepath)
 2387 {
 2388     FILE *fp, *pipe_fp;
 2389     char *prompt;
 2390 
 2391     prompt = fmt_string(_(txt_pipe_to_command), cCOLS - (strlen(_(txt_pipe_to_command)) + 30), tinrc.default_pipe_command);
 2392     if (!(prompt_string_default(prompt, tinrc.default_pipe_command, _(txt_no_command), HIST_PIPE_COMMAND))) {
 2393         free(prompt);
 2394         return;
 2395     }
 2396     free(prompt);
 2397     if ((fp = fopen(savepath, "r")) == NULL)
 2398         /* TODO: error message? */
 2399         return;
 2400     EndWin();
 2401     Raw(FALSE);
 2402     fflush(stdout);
 2403     set_signal_catcher(FALSE);
 2404     if ((pipe_fp = popen(tinrc.default_pipe_command, "w")) == NULL) {
 2405         perror_message(_(txt_command_failed), tinrc.default_pipe_command);
 2406         set_signal_catcher(TRUE);
 2407         Raw(TRUE);
 2408         InitWin();
 2409         fclose(fp);
 2410         return;
 2411     }
 2412     copy_fp(fp, pipe_fp);
 2413     if (errno == EPIPE)
 2414         perror_message(_(txt_command_failed), tinrc.default_pipe_command);
 2415     fflush(pipe_fp);
 2416     (void) pclose(pipe_fp);
 2417     set_signal_catcher(TRUE);
 2418     fclose(fp);
 2419 #   ifdef USE_CURSES
 2420     Raw(TRUE);
 2421     InitWin();
 2422 #   endif /* USE_CURSES */
 2423     prompt_continue();
 2424 #   ifndef USE_CURSES
 2425     Raw(TRUE);
 2426     InitWin();
 2427 #   endif /* !USE_CURSES */
 2428 }
 2429 #endif /* !DONT_HAVE_PIPING */