"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.2/src/save.c" (8 Dec 2017, 59233 Bytes) of package /linux/misc/tin-2.4.2.tar.xz:


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

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