"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.1/src/save.c" (12 Oct 2016, 59236 Bytes) of archive /linux/misc/tin-2.4.1.tar.gz:


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.0_vs_2.4.1.

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : save.c
    4  *  Author    : I. Lea & R. Skrenta
    5  *  Created   : 1991-04-01
    6  *  Updated   : 2016-10-10
    7  *  Notes     :
    8  *
    9  * Copyright (c) 1991-2017 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];
  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 + 1);
  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     return;
  887 }
  888 
  889 #else
  890 
  891 /*
  892  * Open and read all the files in save[]
  893  * Scan for uuencode BEGIN lines, decode input as we go along
  894  * uuencoded data can span multiple files, and multiple uuencoded
  895  * files are supported per batch
  896  */
  897 static void
  898 post_process_uud(
  899     void)
  900 {
  901     FILE *fp_in;
  902     FILE *fp_out = NULL;
  903     char *filename = NULL;
  904     char file_out_dir[PATH_LEN];
  905     char path[PATH_LEN];
  906     char s[LEN], t[LEN], u[LEN];
  907     int state = INITIAL;
  908     int i;
  909     mode_t mode = 0;
  910 
  911     /*
  912      * Grab the dirname portion
  913      */
  914     my_strncpy(file_out_dir, save[0].path, save[0].file - save[0].path);
  915 
  916     t[0] = '\0';
  917     u[0] = '\0';
  918 
  919     for (i = 0; i < num_save; i++) {
  920         if ((fp_in = fopen(save[i].path, "r")) == NULL)
  921             continue;
  922 
  923         while (fgets(s, (int) sizeof(s), fp_in) != 0) {
  924             switch (state) {
  925                 case INITIAL:
  926                     if (strncmp("begin ", s, 6) == 0) {
  927                         char fmt[15];
  928                         char name[PATH_LEN];
  929                         char buf[PATH_LEN];
  930 
  931                         snprintf(fmt, sizeof(fmt), "%%o %%%dc\\n", PATH_LEN - 1);
  932                         if (sscanf(s + 6, fmt, &mode, name) == 2) {
  933                             strtok(name, "\n");
  934                             my_strncpy(buf, name, sizeof(buf) - 1);
  935                             str_trim(buf);
  936                             base_name(buf, name);
  937                         } else
  938                             name[0] = '\0';
  939 
  940                         if (!mode && !*name) { /* not a valid uu-file at all */
  941                             state = INITIAL;
  942                             continue;
  943                         }
  944 
  945                         if (!*name)
  946                             generate_filename(name, sizeof(name), "uue");
  947 
  948                         filename = name;
  949                         expand_save_filename(path, sizeof(path), filename);
  950                         filename = strrchr(path, DIRSEP) + 1;   /* ptr to filename portion */
  951                         if ((fp_out = fopen(path, "w")) == NULL) {
  952                             perror_message(_(txt_cannot_open), path);
  953                             fclose(fp_in);
  954                             return;
  955                         }
  956                         state = MIDDLE;
  957                     }
  958                     break;
  959 
  960                 case MIDDLE:
  961                     /*
  962                      * TODO: replace hardcoded length check (uue lines are not
  963                      *       required to be 60 chars long (45 encoded chars)
  964                      *       ('M' == 60 * 3 / 4 + ' ' == 77))
  965                      */
  966                     if (s[0] == 'M')
  967                         uudecode_line(s, fp_out);
  968                     else if (STRNCMPEQ("end", s, 3)) {
  969                         state = END;
  970                         if (u[0] != 'M')
  971                             uudecode_line(u, fp_out);
  972                         if (t[0] != 'M')
  973                             uudecode_line(t, fp_out);
  974                     } else  /* end */
  975                         state = OFF;    /* OFF => a break in the uuencoded data */
  976                     break;
  977 
  978                 case OFF:
  979                     if ((s[0] == 'M') && (t[0] == 'M') && (u[0] == 'M')) {
  980                         uudecode_line(u, fp_out);
  981                         uudecode_line(t, fp_out);
  982                         uudecode_line(s, fp_out);
  983                         state = MIDDLE; /* Continue output of previously suspended data */
  984                     } else if (STRNCMPEQ("end", s, 3)) {
  985                         state = END;
  986                         if (u[0] != 'M')
  987                             uudecode_line(u, fp_out);
  988                         if (t[0] != 'M')
  989                             uudecode_line(t, fp_out);
  990                     }
  991                     break;
  992 
  993                 case END:
  994                 default:
  995                     break;
  996             }   /* switch (state) */
  997 
  998             if (state == END) {
  999                 /* set the mode after getting rid of dangerous bits */
 1000                 if (!(mode &= ~(S_ISUID|S_ISGID|S_ISVTX)))
 1001                     mode = (S_IRUSR|S_IWUSR);
 1002 
 1003                 fchmod(fileno(fp_out), mode);
 1004 
 1005                 fclose(fp_out);
 1006                 fp_out = NULL;
 1007 
 1008                 my_printf(_(txt_uu_success), filename);
 1009                 my_printf(cCRLF);
 1010                 sum_file(path, filename);
 1011                 if (curr_group->attribute->post_process_view)
 1012                     view_file(path, filename);
 1013                 state = INITIAL;
 1014                 continue;
 1015             }
 1016 
 1017             strcpy(u, t);   /* Keep tabs on the last two lines, which typically do not start with M */
 1018             strcpy(t, s);
 1019 
 1020         }   /* while (fgets) ... */
 1021 
 1022         fclose(fp_in);
 1023 
 1024     } /* for i...num_save */
 1025 
 1026     /*
 1027      * Check if we ran out of data
 1028      */
 1029     if (fp_out) {
 1030         fclose(fp_out);
 1031         my_printf(_(txt_uu_error_decode), filename, _(txt_uu_error_no_end));
 1032         my_printf(cCRLF);
 1033     }
 1034     return;
 1035 }
 1036 
 1037 
 1038 /*
 1039  * Sum file - why do we bother to do this?
 1040  * nuke code or add DONT_HAVE_PIPING and !M_UNIX -tree
 1041  */
 1042 static void
 1043 sum_file(
 1044     const char *path,
 1045     const char *file)
 1046 {
 1047 #   if defined(M_UNIX) && defined(HAVE_SUM) && !defined(DONT_HAVE_PIPING)
 1048     FILE *fp_in;
 1049     char *ext;
 1050     char buf[LEN];
 1051 
 1052     sh_format(buf, sizeof(buf), "%s \"%s\"", DEFAULT_SUM, path);
 1053     if ((fp_in = popen(buf, "r")) != NULL) {
 1054         buf[0] = '\0';
 1055 
 1056         /*
 1057          * You can't do this with (fgets != NULL)
 1058          */
 1059         while (!feof(fp_in)) {
 1060             fgets(buf, (int) sizeof(buf), fp_in);
 1061             if ((ext = strchr(buf, '\n')) != NULL)
 1062                 *ext = '\0';
 1063         }
 1064         fflush(fp_in);
 1065         pclose(fp_in);
 1066 
 1067         my_printf(_(txt_checksum_of_file), file, file_size(path), _("bytes"));
 1068         my_printf(cCRLF);
 1069         my_printf("\t%s%s", buf, cCRLF);
 1070     } else {
 1071         my_printf(_(txt_command_failed), buf);
 1072         my_printf(cCRLF);
 1073     }
 1074     my_flush();
 1075 #   endif /* M_UNIX && HAVE SUM && !DONT_HAVE_PIPING */
 1076 }
 1077 #endif /* HAVE_LIBUU */
 1078 
 1079 
 1080 /*
 1081  * If defined, invoke post processor command
 1082  * Create a part structure, with defaults, insert a parameter for the name
 1083  */
 1084 static void
 1085 view_file(
 1086     const char *path,
 1087     const char *file)
 1088 {
 1089     char *ext;
 1090     t_part *part;
 1091 
 1092     part = new_part(NULL);
 1093 
 1094     if ((ext = strrchr(file, '.')) != NULL)
 1095         lookup_mimetype(ext + 1, part);             /* Get MIME type/subtype */
 1096 
 1097     /*
 1098      * Needed for the mime-type processor
 1099      */
 1100     part->params = new_params();
 1101     part->params->name = my_strdup("name");
 1102     part->params->value = my_strdup(file);
 1103 
 1104     start_viewer(part, path);
 1105     my_printf(cCRLF);
 1106 
 1107     free_parts(part);
 1108 }
 1109 
 1110 
 1111 /* Single character decode. */
 1112 #define DEC(Char) (((Char) - ' ') & 077)
 1113 /*
 1114  * Decode 'buf' - write the uudecoded output to 'fp'
 1115  */
 1116 static void
 1117 uudecode_line(
 1118     const char *buf,
 1119     FILE *fp)
 1120 {
 1121     const char *p = buf;
 1122     char ch;
 1123     int n;
 1124 
 1125     n = DEC(*p);
 1126 
 1127     for (++p; n > 0; p += 4, n -= 3) {
 1128         if (n >= 3) {
 1129             ch = ((DEC(p[0]) << 2) | (DEC(p[1]) >> 4));
 1130             fputc(ch, fp);
 1131             ch = ((DEC(p[1]) << 4) | (DEC(p[2]) >> 2));
 1132             fputc(ch, fp);
 1133             ch = ((DEC(p[2]) << 6) | DEC(p[3]));
 1134             fputc(ch, fp);
 1135         } else {
 1136             if (n >= 1) {
 1137                 ch = ((DEC(p[0]) << 2) | (DEC(p[1]) >> 4));
 1138                 fputc(ch, fp);
 1139             }
 1140             if (n >= 2) {
 1141                 ch = ((DEC(p[1]) << 4) | (DEC(p[2]) >> 2));
 1142                 fputc(ch, fp);
 1143             }
 1144         }
 1145     }
 1146     return;
 1147 }
 1148 
 1149 
 1150 /*
 1151  * Unpack /bin/sh archives
 1152  * There is no end-of-shar marker so the code reads everything after
 1153  * the start marker. This is why shar is handled separately.
 1154  * The code assumes shar archives do not span articles
 1155  */
 1156 static void
 1157 post_process_sh(
 1158     void)
 1159 {
 1160     FILE *fp_in, *fp_out = NULL;
 1161     char buf[LEN];
 1162     char file_out[PATH_LEN];
 1163     char file_out_dir[PATH_LEN];
 1164     int i;
 1165 
 1166     /*
 1167      * Grab the dirname portion
 1168      */
 1169     my_strncpy(file_out_dir, save[0].path, save[0].file - save[0].path);
 1170     snprintf(file_out, sizeof(file_out), "%ssh%ld", file_out_dir, (long) process_id);
 1171 
 1172     for (i = 0; i < num_save; i++) {
 1173         if ((fp_in = fopen(save[i].path, "r")) == NULL)
 1174             continue;
 1175 
 1176         wait_message(1, _(txt_extracting_shar), save[i].path);
 1177 
 1178         while (fgets(buf, (int) sizeof(buf), fp_in) != NULL) {
 1179             /* find #!/bin/sh style patterns */
 1180             if ((fp_out == NULL) && pcre_exec(shar_regex.re, shar_regex.extra, buf, strlen(buf), 0, 0, NULL, 0) >= 0)
 1181                 fp_out = fopen(file_out, "w");
 1182 
 1183             /* write to temp file */
 1184             if (fp_out != NULL)
 1185                 fputs(buf, fp_out);
 1186         }
 1187         fclose(fp_in);
 1188 
 1189         if (fp_out == NULL)         /* Didn't extract any shar */
 1190             continue;
 1191 
 1192         fclose(fp_out);
 1193         fp_out = NULL;
 1194 #ifndef M_UNIX
 1195         make_post_process_cmd(DEFAULT_UNSHAR, file_out_dir, file_out);
 1196 #else
 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 #endif /* !M_UNIX */
 1202         unlink(file_out);
 1203     }
 1204     return;
 1205 }
 1206 
 1207 
 1208 /*
 1209  * write tailing (MMDF)-mailbox separator
 1210  */
 1211 void
 1212 print_art_separator_line(
 1213     FILE *fp,
 1214     t_bool is_mailbox)
 1215 {
 1216 #ifdef DEBUG
 1217     if (debug & DEBUG_MISC)
 1218         error_message(2, "Mailbox=[%d], mailbox_format=[%s]", is_mailbox, txt_mailbox_formats[tinrc.mailbox_format]);
 1219 #endif /* DEBUG */
 1220 
 1221     fprintf(fp, "%s", (is_mailbox && !strcasecmp(txt_mailbox_formats[tinrc.mailbox_format], "MMDF")) ? MMDFHDRTXT : "\n");
 1222 }
 1223 
 1224 
 1225 /*
 1226  * part needs to have at least content type/subtype and a filename
 1227  * path = full path/file (used for substitution in mailcap entries)
 1228  */
 1229 static void
 1230 start_viewer(
 1231     t_part *part,
 1232     const char *path)
 1233 {
 1234     t_mailcap *foo;
 1235 
 1236     if ((foo = get_mailcap_entry(part, path)) != NULL) {
 1237         if (foo->nametemplate)  /* honor nametemplate */
 1238             rename_file(path, foo->nametemplate);
 1239 
 1240         wait_message(0, _(txt_starting_command), foo->command);
 1241         if (foo->needsterminal) {
 1242             set_xclick_off();
 1243             fflush(stdout);
 1244         } else {
 1245             if (foo->description)
 1246                 info_message("%s", foo->description);
 1247         }
 1248         invoke_cmd(foo->command);
 1249         if (foo->needsterminal) {
 1250 #ifndef USE_CURSES
 1251             EndWin();
 1252             Raw(FALSE);
 1253 #endif /* !USE_CURSES */
 1254             prompt_continue();
 1255 #ifndef USE_CURSES
 1256             Raw(TRUE);
 1257             InitWin();
 1258 #endif /* !USE_CURSES */
 1259         }
 1260         if (foo->nametemplate) /* undo nametemplate, needed as 'save'-prompt is done outside start_viewer */
 1261             rename_file(foo->nametemplate, path);
 1262         free_mailcap(foo);
 1263     } else
 1264         wait_message(1, _(txt_no_viewer_found), content_types[part->type], part->subtype);
 1265 }
 1266 
 1267 
 1268 /*
 1269  * Decode and save the binary object pointed to in 'part'
 1270  * Optionally launch a viewer for it
 1271  * Return FALSE if Abort used to skip further viewing/saving
 1272  * or other terminal error occurs
 1273  */
 1274 static t_bool
 1275 decode_save_one(
 1276     t_part *part,
 1277     FILE *rawfp,
 1278     t_bool postproc)
 1279 {
 1280     FILE *fp;
 1281     char buf[2048], buf2[2048];
 1282     char *savepath;
 1283     int count;
 1284     int i;
 1285 
 1286     /*
 1287      * Decode this message part if appropriate
 1288      */
 1289     if (!(check_save_mime_type(part, curr_group->attribute->mime_types_to_save))) {
 1290         /* TODO: skip message if saving multiple files (e.g. save 't'agged) */
 1291         wait_message(1, "Skipped %s/%s", content_types[part->type], part->subtype); /* TODO: better msg */
 1292         return TRUE;
 1293     }
 1294 
 1295     if ((savepath = generate_savepath(part)) == NULL)
 1296         return FALSE;
 1297 
 1298     /*
 1299      * Decode/save the attachment
 1300      */
 1301     if ((fp = open_save_filename(savepath, FALSE)) == NULL) {
 1302         free(savepath);
 1303         return FALSE;
 1304     }
 1305 
 1306     if (part->encoding == ENCODING_BASE64)
 1307         mmdecode(NULL, 'b', 0, NULL);               /* flush */
 1308 
 1309     fseek(rawfp, part->offset, SEEK_SET);
 1310 
 1311     for (i = 0; i < part->line_count; i++) {
 1312         if ((fgets(buf, sizeof(buf), rawfp)) == NULL)
 1313             break;
 1314 
 1315         /* This should catch cases where people illegally append text etc */
 1316         if (buf[0] == '\0')
 1317             break;
 1318 
 1319         switch (part->encoding) {
 1320             case ENCODING_QP:
 1321             case ENCODING_BASE64:
 1322                 count = mmdecode(buf, part->encoding == ENCODING_QP ? 'q' : 'b', '\0', buf2);
 1323                 fwrite(buf2, count, 1, fp);
 1324                 break;
 1325 
 1326             case ENCODING_UUE:
 1327                 /* TODO: if postproc, don't decode these since the traditional uudecoder will get them */
 1328                 /*
 1329                  * x-uuencode attachments have all the header info etc which we must ignore
 1330                  */
 1331                 if (strncmp(buf, "begin ", 6) != 0 && strncmp(buf, "end\n", 4) != 0 && buf[0] != '\n')
 1332                     uudecode_line(buf, fp);
 1333                 break;
 1334 
 1335             default:
 1336                 fputs(buf, fp);
 1337         }
 1338     }
 1339     fclose(fp);
 1340 
 1341     /*
 1342      * View the attachment
 1343      */
 1344     if ((postproc && curr_group->attribute->post_process_view) || !curr_group->attribute->ask_for_metamail) {
 1345             start_viewer(part, savepath);
 1346             my_printf(cCRLF);
 1347     } else {
 1348         snprintf(buf, sizeof(buf), _(txt_view_attachment), savepath, content_types[part->type], part->subtype);
 1349         if ((i = prompt_yn(buf, TRUE)) == 1)
 1350             start_viewer(part, savepath);
 1351         else if (i == -1) { /* Skip rest of attachments */
 1352             unlink(savepath);
 1353             free(savepath);
 1354             return FALSE;
 1355         }
 1356     }
 1357 
 1358     /*
 1359      * Save the attachment
 1360      */
 1361     if (postproc && curr_group->attribute->post_process_view) {
 1362         my_printf(_(txt_uu_success), savepath);
 1363         my_printf(cCRLF);
 1364     }
 1365     if (!postproc) {
 1366         snprintf(buf, sizeof(buf), _(txt_save_attachment), savepath, content_types[part->type], part->subtype);
 1367         if ((i = prompt_yn(buf, FALSE)) != 1) {
 1368             unlink(savepath);
 1369             if (i == -1) {  /* Skip rest of attachments */
 1370                 free(savepath);
 1371                 return FALSE;
 1372             }
 1373         }
 1374     }
 1375     free(savepath);
 1376     return TRUE;
 1377 }
 1378 
 1379 
 1380 enum match {
 1381     NO,
 1382     MATCH,
 1383     NOTMATCH
 1384 };
 1385 
 1386 /*
 1387  * Match a single type/subtype Content pair
 1388  * Returns:
 1389  * NO = Not matched
 1390  * MATCH = Matched
 1391  * NOTMATCH = Matched, but !negated
 1392  */
 1393 static int
 1394 match_content_type(
 1395     t_part *part,
 1396     char *type)
 1397 {
 1398     char *subtype;
 1399     int typeindex;
 1400     t_bool found = FALSE;
 1401     t_bool negate = FALSE;
 1402 
 1403     /* Check for negation */
 1404     if (*type == '!') {
 1405         negate = TRUE;
 1406         ++type;
 1407 
 1408         if (!*type)             /* Invalid type */
 1409             return NO;
 1410     }
 1411 
 1412     /* Split type and subtype */
 1413     if ((subtype = strchr(type, '/')) == NULL)
 1414         return NO;
 1415     *(subtype++) = '\0';
 1416 
 1417     if (!*type || !*subtype)    /* Missing type or subtype */
 1418         return NO;
 1419 
 1420     /* Try and match major */
 1421     if (strcmp(type, "*") == 0)
 1422         found = TRUE;
 1423     else if (((typeindex = content_type(type)) != -1) && typeindex == part->type)
 1424         found = TRUE;
 1425 
 1426     if (!found)
 1427         return NO;
 1428 
 1429     /* Try and match subtype */
 1430     found = FALSE;
 1431     if (strcmp(subtype, "*") == 0)
 1432         found = TRUE;
 1433     else if (strcmp(subtype, part->subtype) == 0)
 1434         found = TRUE;
 1435 
 1436     if (!found)
 1437         return NO;
 1438 
 1439     /* We got a match */
 1440     if (negate)
 1441         return NOTMATCH;
 1442 
 1443     return MATCH;
 1444 }
 1445 
 1446 
 1447 /*
 1448  * See if the mime type of this part matches the list of content types to save
 1449  * or ignore. Return TRUE if there is a match
 1450  * mime_types is a comma separated list of type/subtype pairs. type and/or
 1451  * subtype can be a '*' to match any, and a pair can begin with a ! which
 1452  * will negate the meaning. We eval all pairs, the rightmost match will
 1453  * prevail
 1454  */
 1455 static t_bool
 1456 check_save_mime_type(
 1457     t_part *part,
 1458     const char *mime_types)
 1459 {
 1460     char *ptr, *pair;
 1461     int found;
 1462     int retcode;
 1463 
 1464     if (!mime_types)
 1465         return FALSE;
 1466 
 1467     ptr = my_strdup(mime_types);
 1468 
 1469     if ((pair = strtok(ptr, ",")) == NULL) {
 1470         free(ptr);
 1471         return FALSE;
 1472     }
 1473 
 1474     retcode = match_content_type(part, pair);
 1475 
 1476     while ((pair = strtok(NULL, ",")) != NULL) {
 1477         if ((found = match_content_type(part, pair)) != NO)
 1478             retcode = found;
 1479     }
 1480 
 1481     free(ptr);
 1482     return (retcode == MATCH);
 1483 }
 1484 
 1485 
 1486 /*
 1487  * decode and save binary MIME attachments from an open article context
 1488  * optionally locate and launch a viewer application
 1489  * 'postproc' determines the mode of the operation and will be set to
 1490  * TRUE when we're called during a [Ss]ave operation and FALSE when
 1491  * when just viewing
 1492  * When it is TRUE the view option will depend on post_process_view and
 1493  * the save is implicit. Feedback will also be printed.
 1494  * When it is FALSE then the view/save options will be queried
 1495  */
 1496 void
 1497 decode_save_mime(
 1498     t_openartinfo *art,
 1499     t_bool postproc)
 1500 {
 1501     t_part *ptr, *uueptr;
 1502 
 1503     /*
 1504      * Iterate over all the attachments
 1505      */
 1506     for (ptr = art->hdr.ext; ptr != NULL; ptr = ptr->next) {
 1507         /*
 1508          * Handle uuencoded sections in this message part.
 1509          * Only works when the uuencoded file is entirely within the current
 1510          * article.
 1511          * We don't do this when postprocessing as the generic uudecode code
 1512          * already handles uuencoded data, but TODO: review this
 1513          */
 1514         if (!postproc) {
 1515             for (uueptr = ptr->uue; uueptr != NULL; uueptr = uueptr->next) {
 1516                 if (!(decode_save_one(uueptr, art->raw, postproc)))
 1517                     break;
 1518             }
 1519         }
 1520 
 1521         /*
 1522          * TYPE_MULTIPART is an envelope type, don't process it.
 1523          * If we had an UUE part, the "surrounding" text/plain plays
 1524          * the role of a multipart part. Check to see if we want to
 1525          * save text and if not, skip this part.
 1526          */
 1527          /* check_save_mime_type() is done in decode_save_one() and the check for ptr->uue must be done unconditionally */
 1528         if (ptr->type == TYPE_MULTIPART || (NULL != ptr->uue /* && !check_save_mime_type(ptr, curr_group->attribute->mime_types_to_save) */ ))
 1529             continue;
 1530 
 1531         if (!(decode_save_one(ptr, art->raw, postproc)))
 1532             break;
 1533     }
 1534 }
 1535 
 1536 
 1537 /*
 1538  * Attachment menu
 1539  */
 1540 static void
 1541 show_attachment_page(
 1542     void)
 1543 {
 1544     char buf[BUFSIZ];
 1545     const char *charset;
 1546     int i, tmp_len, max_depth;
 1547     t_part *part;
 1548 
 1549     signal_context = cAttachment;
 1550     currmenu = &attmenu;
 1551     mark_offset = 0;
 1552 
 1553     if (attmenu.curr < 0)
 1554         attmenu.curr = 0;
 1555 
 1556     info_len = max_depth = 0;
 1557     for (i = 0; i < attmenu.max; ++i) {
 1558         part = get_part(i);
 1559         snprintf(buf, sizeof(buf), _(txt_attachment_lines), part->line_count);
 1560         tmp_len = strwidth(buf);
 1561         charset = get_param(part->params, "charset");
 1562         snprintf(buf, sizeof(buf), "  %s/%s, %s, %s%s", content_types[part->type], part->subtype, content_encodings[part->encoding], charset ? charset : "", charset ? ", " : "");
 1563         tmp_len += strwidth(buf);
 1564         if (tmp_len > info_len)
 1565             info_len = tmp_len;
 1566 
 1567         tmp_len = part->depth;
 1568         if (tmp_len > max_depth)
 1569             max_depth = tmp_len;
 1570     }
 1571     tmp_len = cCOLS - 13 - MIN((cCOLS - 13) / 2 + 10, max_depth * 2 + 1 + strwidth(_(txt_attachment_no_name)));
 1572     if (info_len > tmp_len)
 1573         info_len = tmp_len;
 1574 
 1575     ClearScreen();
 1576     set_first_screen_item();
 1577     center_line(0, TRUE, _(txt_attachment_menu));
 1578 
 1579     for (i = attmenu.first; i < attmenu.first + NOTESLINES && i < attmenu.max; ++i)
 1580         build_attachment_line(i);
 1581 
 1582     show_mini_help(ATTACHMENT_LEVEL);
 1583 
 1584     if (attmenu.max <= 0) {
 1585         info_message(_(txt_no_attachments));
 1586         return;
 1587     }
 1588 
 1589     draw_attachment_arrow();
 1590 }
 1591 
 1592 
 1593 void
 1594 attachment_page(
 1595     t_openartinfo *art)
 1596 {
 1597     char key[MAXKEYLEN];
 1598     t_function func;
 1599     t_menu *oldmenu = NULL;
 1600     t_part *part;
 1601 
 1602     if (currmenu)
 1603         oldmenu = currmenu;
 1604     num_of_tagged_parts = 0;
 1605     attmenu.curr = 0;
 1606     attmenu.max = build_part_list(art);
 1607     clear_note_area();
 1608     show_attachment_page();
 1609     set_xclick_off();
 1610 
 1611     forever {
 1612         switch ((func = handle_keypad(attachment_left, attachment_right, NULL, attachment_keys))) {
 1613             case GLOBAL_QUIT:
 1614                 free_part_list(part_list);
 1615                 if (oldmenu)
 1616                     currmenu = oldmenu;
 1617                 return;
 1618 
 1619             case DIGIT_1:
 1620             case DIGIT_2:
 1621             case DIGIT_3:
 1622             case DIGIT_4:
 1623             case DIGIT_5:
 1624             case DIGIT_6:
 1625             case DIGIT_7:
 1626             case DIGIT_8:
 1627             case DIGIT_9:
 1628                 if (attmenu.max)
 1629                     prompt_item_num(func_to_key(func, attachment_keys), _(txt_attachment_select));
 1630                 break;
 1631 
 1632 #ifndef NO_SHELL_ESCAPE
 1633             case GLOBAL_SHELL_ESCAPE:
 1634                 do_shell_escape();
 1635                 break;
 1636 #endif /* !NO_SHELL_ESCAPE */
 1637 
 1638             case GLOBAL_HELP:
 1639                 show_help_page(ATTACHMENT_LEVEL, _(txt_attachment_menu_com));
 1640                 show_attachment_page();
 1641                 break;
 1642 
 1643             case GLOBAL_FIRST_PAGE:
 1644                 top_of_list();
 1645                 break;
 1646 
 1647             case GLOBAL_LAST_PAGE:
 1648                 end_of_list();
 1649                 break;
 1650 
 1651             case GLOBAL_REDRAW_SCREEN:
 1652                 my_retouch();
 1653                 show_attachment_page();
 1654                 break;
 1655 
 1656             case GLOBAL_LINE_DOWN:
 1657                 move_down();
 1658                 break;
 1659 
 1660             case GLOBAL_LINE_UP:
 1661                 move_up();
 1662                 break;
 1663 
 1664             case GLOBAL_PAGE_DOWN:
 1665                 page_down();
 1666                 break;
 1667 
 1668             case GLOBAL_PAGE_UP:
 1669                 page_up();
 1670                 break;
 1671 
 1672             case GLOBAL_SCROLL_DOWN:
 1673                 scroll_down();
 1674                 break;
 1675 
 1676             case GLOBAL_SCROLL_UP:
 1677                 scroll_up();
 1678                 break;
 1679 
 1680             case GLOBAL_TOGGLE_HELP_DISPLAY:
 1681                 toggle_mini_help(ATTACHMENT_LEVEL);
 1682                 show_attachment_page();
 1683                 break;
 1684 
 1685             case GLOBAL_TOGGLE_INFO_LAST_LINE:
 1686                 tinrc.info_in_last_line = bool_not(tinrc.info_in_last_line);
 1687                 show_attachment_page();
 1688                 break;
 1689 
 1690             case ATTACHMENT_SAVE:
 1691                 if (attmenu.max) {
 1692                     part = get_part(attmenu.curr);
 1693                     process_parts(part, art, num_of_tagged_parts ? SAVE_TAGGED : SAVE);
 1694                     show_attachment_page();
 1695                 }
 1696                 break;
 1697 
 1698             case ATTACHMENT_SELECT:
 1699                 if (attmenu.max) {
 1700                     part = get_part(attmenu.curr);
 1701                     process_parts(part, art, VIEW);
 1702                     show_attachment_page();
 1703                 }
 1704                 break;
 1705 
 1706             case ATTACHMENT_TAG:
 1707                 if (attmenu.max) {
 1708                     t_bool tagged;
 1709 
 1710                     tagged = tag_part(attmenu.curr);
 1711                     show_attachment_page();
 1712                     if (attmenu.curr + 1 < attmenu.max)
 1713                         move_down();
 1714                     info_message(tagged ? _(txt_attachment_tagged) : _(txt_attachment_untagged));
 1715                 }
 1716                 break;
 1717 
 1718             case ATTACHMENT_UNTAG:
 1719                 if (attmenu.max && num_of_tagged_parts) {
 1720                     untag_all_parts();
 1721                     show_attachment_page();
 1722                 }
 1723                 break;
 1724 
 1725             case ATTACHMENT_TAG_PATTERN:
 1726                 if (attmenu.max) {
 1727                     tag_pattern();
 1728                     show_attachment_page();
 1729                     info_message(_(txt_attachments_tagged), num_of_tagged_parts);
 1730                 }
 1731                 break;
 1732 
 1733             case ATTACHMENT_TOGGLE_TAGGED:
 1734                 if (attmenu.max) {
 1735                     int i;
 1736 
 1737                     for (i = attmenu.first; i < attmenu.max; ++i)
 1738                         tag_part(i);
 1739                     show_attachment_page();
 1740                     info_message(_(txt_attachments_tagged), num_of_tagged_parts);
 1741                 }
 1742                 break;
 1743 
 1744             case GLOBAL_SEARCH_SUBJECT_FORWARD:
 1745             case GLOBAL_SEARCH_SUBJECT_BACKWARD:
 1746             case GLOBAL_SEARCH_REPEAT:
 1747                 if (func == GLOBAL_SEARCH_REPEAT && last_search != GLOBAL_SEARCH_SUBJECT_FORWARD && last_search != GLOBAL_SEARCH_SUBJECT_BACKWARD)
 1748                     info_message(_(txt_no_prev_search));
 1749                 else if (attmenu.max) {
 1750                     int new_pos, old_pos = attmenu.curr;
 1751 
 1752                     new_pos = generic_search((func == GLOBAL_SEARCH_SUBJECT_FORWARD), (func == GLOBAL_SEARCH_REPEAT), attmenu.curr, attmenu.max - 1, ATTACHMENT_LEVEL);
 1753                     if (new_pos != old_pos)
 1754                         move_to_item(new_pos);
 1755                 }
 1756                 break;
 1757 
 1758 #ifndef DONT_HAVE_PIPING
 1759             case ATTACHMENT_PIPE:
 1760             case GLOBAL_PIPE:
 1761                 if (attmenu.max) {
 1762                     part = get_part(attmenu.curr);
 1763                     process_parts(part, art, func == GLOBAL_PIPE ? PIPE_RAW : PIPE);
 1764                     show_attachment_page();
 1765                 }
 1766                 break;
 1767 #endif /* !DONT_HAVE_PIPING */
 1768 
 1769             default:
 1770                 info_message(_(txt_bad_command), printascii(key, func_to_key(GLOBAL_HELP, attachment_keys)));
 1771                 break;
 1772         }
 1773     }
 1774 }
 1775 
 1776 
 1777 static t_function
 1778 attachment_left(
 1779     void)
 1780 {
 1781     return GLOBAL_QUIT;
 1782 }
 1783 
 1784 
 1785 static t_function
 1786 attachment_right(
 1787     void)
 1788 {
 1789     return ATTACHMENT_SELECT;
 1790 }
 1791 
 1792 
 1793 static void
 1794 draw_attachment_arrow(
 1795     void)
 1796 {
 1797     draw_arrow_mark(INDEX_TOP + attmenu.curr - attmenu.first);
 1798     if (tinrc.info_in_last_line) {
 1799         const char *name;
 1800         t_part *part;
 1801 
 1802         part = get_part(attmenu.curr);
 1803         name = get_filename(part->params);
 1804         info_message("%s  %s", name ? name : _(txt_attachment_no_name), BlankIfNull(part->description));
 1805     } else if (attmenu.curr == attmenu.max - 1)
 1806         info_message(_(txt_end_of_attachments));
 1807 }
 1808 
 1809 
 1810 static void
 1811 build_attachment_line(
 1812     int i)
 1813 {
 1814     char *sptr;
 1815     const char *name;
 1816     const char *charset;
 1817 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1818     char *tmpname;
 1819     char *tmpbuf;
 1820 #endif /* MULTIBYTE_ABLE && !NOLOCALE */
 1821     char buf[BUFSIZ];
 1822     char buf2[BUFSIZ];
 1823     char *tree = NULL;
 1824     int len, namelen, tagged, treelen;
 1825     t_part *part;
 1826 
 1827 #ifdef USE_CURSES
 1828     /*
 1829      * Allocate line buffer
 1830      * make it the same size like in !USE_CURSES case to simplify some code
 1831      */
 1832 #   if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1833         sptr = my_malloc(cCOLS * MB_CUR_MAX + 2);
 1834 #   else
 1835         sptr = my_malloc(cCOLS + 2);
 1836 #   endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1837 #else
 1838     sptr = screen[INDEX2SNUM(i)].col;
 1839 #endif /* USE_CURSES */
 1840 
 1841     part = get_part(i);
 1842     namelen = strwidth(_(txt_attachment_no_name));
 1843     tagged = get_tagged(i);
 1844 
 1845     if (!(name = get_filename(part->params))) {
 1846         if (!(name = part->description))
 1847             name = _(txt_attachment_no_name);
 1848     }
 1849 
 1850     charset = get_param(part->params, "charset");
 1851     snprintf(buf2, sizeof(buf2), _(txt_attachment_lines), part->line_count);
 1852     /* TODO: make the layout configurable? */
 1853     if (!strcmp(content_types[part->type], "text"))
 1854         snprintf(buf, sizeof(buf), "  %s/%s, %s, %s%s%s", content_types[part->type], part->subtype, content_encodings[part->encoding], charset ? charset : "", charset ? ", " : "", buf2);
 1855     else
 1856         snprintf(buf, sizeof(buf), "  %s/%s, %s, %s", content_types[part->type], part->subtype, content_encodings[part->encoding], buf2);
 1857     if (part->depth > 0) {
 1858         treelen = cCOLS - 13 - info_len - namelen;
 1859         tree = build_tree(part->depth, treelen, i);
 1860     }
 1861     snprintf(buf2, sizeof(buf2), "%s  %s", tagged ? tin_ltoa(tagged, 3) : "   ", BlankIfNull(tree));
 1862     FreeIfNeeded(tree);
 1863     len = strwidth(buf2);
 1864     if (namelen + len + info_len + 8 <= cCOLS)
 1865         namelen = cCOLS - 8 - info_len - len;
 1866 
 1867 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1868     tmpname = spart(name, namelen, TRUE);
 1869     tmpbuf = spart(buf, info_len, TRUE);
 1870     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);
 1871     FreeIfNeeded(tmpname);
 1872     FreeIfNeeded(tmpbuf);
 1873 #else
 1874     snprintf(sptr, cCOLS, "  %s %s%-*.*s%*.*s%s", tin_ltoa(i + 1, 4), buf2, namelen, namelen, name, info_len, info_len, buf, cCRLF);
 1875 #endif /* MULTIBYTE_ABLE && !NOLOCALE */
 1876 
 1877     WriteLine(INDEX2LNUM(i), sptr);
 1878 
 1879 #ifdef USE_CURSES
 1880     free(sptr);
 1881 #endif /* USE_CURSES */
 1882 }
 1883 
 1884 
 1885 /*
 1886  * Build attachment tree. Code adopted
 1887  * from thread.c:make_prefix().
 1888  */
 1889 static char *
 1890 build_tree(
 1891     int depth,
 1892     int maxlen,
 1893     int i)
 1894 {
 1895 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1896     char *result;
 1897     wchar_t *tree;
 1898 #else
 1899     char *tree;
 1900 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1901     int prefix_ptr, tmpdepth;
 1902     int depth_level = 0;
 1903     t_bool found = FALSE;
 1904     t_partl *lptr, *lptr2;
 1905 
 1906     lptr2 = find_part(i);
 1907     prefix_ptr = depth * 2 - 1;
 1908     if (prefix_ptr > maxlen - 1 - !(maxlen % 2)) {
 1909         int odd = ((maxlen % 2) ? 0 : 1);
 1910 
 1911         prefix_ptr -= maxlen - ++depth_level - 2 - odd;
 1912         while (prefix_ptr > maxlen - 2 - odd) {
 1913             if (depth_level < maxlen / 5)
 1914                 depth_level++;
 1915 
 1916             prefix_ptr -= maxlen - depth_level - 2 - odd;
 1917             odd = (odd ? 0 : 1);
 1918         }
 1919     }
 1920 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1921     tree = my_malloc(sizeof(wchar_t) * prefix_ptr + 3 * sizeof(wchar_t));
 1922     tree[prefix_ptr + 2] = (wchar_t) '\0';
 1923 #else
 1924     tree = my_malloc(prefix_ptr + 3);
 1925     tree[prefix_ptr + 2] = '\0';
 1926 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1927     tree[prefix_ptr + 1] = TREE_ARROW;
 1928     tree[prefix_ptr] = TREE_HORIZ;
 1929     for (lptr = lptr2->next; lptr != NULL; lptr = lptr->next) {
 1930         if (lptr->part->depth == depth) {
 1931             found = TRUE;
 1932             break;
 1933         }
 1934         if (lptr->part->depth < depth)
 1935             break;
 1936     }
 1937     tree[--prefix_ptr] = found ? TREE_VERT_RIGHT : TREE_UP_RIGHT;
 1938     found = FALSE;
 1939     for (tmpdepth = depth - 1; prefix_ptr > 1; --tmpdepth) {
 1940         for (lptr = lptr2->next; lptr != NULL; lptr = lptr->next) {
 1941             if (lptr->part->depth == tmpdepth) {
 1942                 found = TRUE;
 1943                 break;
 1944             }
 1945             if (lptr->part->depth < tmpdepth)
 1946                 break;
 1947         }
 1948         tree[--prefix_ptr] = TREE_BLANK;
 1949         tree[--prefix_ptr] = found ? TREE_VERT : TREE_BLANK;
 1950         found = FALSE;
 1951     }
 1952     while (depth_level)
 1953         tree[--depth_level] = TREE_ARROW_WRAP;
 1954 
 1955 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1956     result = wchar_t2char(tree);
 1957     free(tree);
 1958     return result;
 1959 #else
 1960     return tree;
 1961 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1962 }
 1963 
 1964 
 1965 /*
 1966  * Find nth attachment in part_list.
 1967  * Return pointer to that part.
 1968  */
 1969 static t_partl *
 1970 find_part(
 1971     int n)
 1972 {
 1973     t_partl *lptr;
 1974 
 1975     lptr = part_list;
 1976     if (attmenu.max >= 1)
 1977         lptr = lptr->next;
 1978 
 1979     while (n-- > 0 && lptr->next)
 1980         lptr = lptr->next;
 1981 
 1982     return lptr;
 1983 }
 1984 
 1985 
 1986 t_part *
 1987 get_part(
 1988     int n)
 1989 {
 1990     t_partl *lptr;
 1991 
 1992     lptr = find_part(n);
 1993     return lptr->part;
 1994 }
 1995 
 1996 
 1997 static void
 1998 tag_pattern(
 1999     void)
 2000 {
 2001     char buf[BUFSIZ];
 2002     char pat[128];
 2003     char *prompt;
 2004     const char *name;
 2005     const char *charset;
 2006     struct regex_cache cache = { NULL, NULL };
 2007     t_part *part;
 2008     t_partl *lptr;
 2009 
 2010 #if 0
 2011     if (num_of_tagged_parts)
 2012         untag_all_parts();
 2013 #endif /* 0 */
 2014 
 2015     prompt = fmt_string(_(txt_select_pattern), tinrc.default_select_pattern);
 2016     if (!(prompt_string_default(prompt, tinrc.default_select_pattern, _(txt_info_no_previous_expression), HIST_SELECT_PATTERN))) {
 2017         free(prompt);
 2018         return;
 2019     }
 2020     free(prompt);
 2021 
 2022     if (STRCMPEQ(tinrc.default_select_pattern, "*")) {  /* all */
 2023         if (tinrc.wildcard)
 2024             STRCPY(pat, ".*");
 2025         else
 2026             STRCPY(pat, tinrc.default_select_pattern);
 2027     } else
 2028         snprintf(pat, sizeof(pat), REGEX_FMT, tinrc.default_select_pattern);
 2029 
 2030     if (tinrc.wildcard && !(compile_regex(pat, &cache, PCRE_CASELESS)))
 2031         return;
 2032 
 2033     lptr = find_part(0);
 2034 
 2035     for (; lptr != NULL; lptr = lptr->next) {
 2036         part = lptr->part;
 2037         if (!(name = get_filename(part->params))) {
 2038             if (!(name = part->description))
 2039                 name = _(txt_attachment_no_name);
 2040         }
 2041         charset = get_param(part->params, "charset");
 2042 
 2043         snprintf(buf, sizeof(buf), "%s %s/%s %s, %s", name, content_types[part->type], part->subtype, content_encodings[part->encoding], charset ? charset : "");
 2044 
 2045         if (!match_regex(buf, pat, &cache, TRUE)) {
 2046             continue;
 2047         }
 2048         if (!lptr->tagged)
 2049             lptr->tagged = ++num_of_tagged_parts;
 2050     }
 2051 
 2052     if (tinrc.wildcard) {
 2053         FreeIfNeeded(cache.re);
 2054         FreeIfNeeded(cache.extra);
 2055     }
 2056     return;
 2057 }
 2058 
 2059 
 2060 static int
 2061 get_tagged(
 2062     int n)
 2063 {
 2064     t_partl *lptr;
 2065 
 2066     lptr = find_part(n);
 2067     return lptr->tagged;
 2068 }
 2069 
 2070 
 2071 static t_bool
 2072 tag_part(
 2073     int n)
 2074 {
 2075     t_partl *lptr;
 2076 
 2077     lptr = find_part(n);
 2078     if (lptr->tagged) {
 2079         untag_part(n);
 2080         return FALSE;
 2081     } else {
 2082         lptr->tagged = ++num_of_tagged_parts;
 2083         return TRUE;
 2084     }
 2085 }
 2086 
 2087 
 2088 static void
 2089 untag_part(
 2090     int n)
 2091 {
 2092     int i;
 2093     t_partl *curr_part, *lptr;
 2094 
 2095     lptr = find_part(0);
 2096     curr_part = find_part(n);
 2097     i = attmenu.max;
 2098 
 2099     while (i-- > 0 && lptr) {
 2100         if (lptr->tagged > curr_part->tagged)
 2101             --lptr->tagged;
 2102         lptr = lptr->next;
 2103     }
 2104 
 2105     curr_part->tagged = 0;
 2106     --num_of_tagged_parts;
 2107 }
 2108 
 2109 
 2110 static void
 2111 untag_all_parts(
 2112     void)
 2113 {
 2114     t_partl *lptr = part_list;
 2115 
 2116     while (lptr) {
 2117         if (lptr->tagged) {
 2118             lptr->tagged = 0;
 2119         }
 2120         lptr = lptr->next;
 2121     }
 2122     num_of_tagged_parts = 0;
 2123 }
 2124 
 2125 
 2126 /*
 2127  * Build a linked list which holds pointers to the parts we want deal with.
 2128  */
 2129 static int
 2130 build_part_list(
 2131     t_openartinfo *art)
 2132 {
 2133     int i = 0;
 2134     t_part *ptr, *uueptr;
 2135     t_partl *lptr;
 2136 
 2137     part_list = my_malloc(sizeof(t_partl));
 2138     lptr = part_list;
 2139     lptr->part = art->hdr.ext;
 2140     lptr->next = NULL;
 2141     lptr->tagged = 0;
 2142     for (ptr = art->hdr.ext; ptr != NULL; ptr = ptr->next) {
 2143         if ((uueptr = ptr->uue) != NULL) {
 2144             lptr->next = my_malloc(sizeof(t_partl));
 2145             lptr->next->part = ptr;
 2146             lptr->next->next = NULL;
 2147             lptr->next->tagged = 0;
 2148             lptr = lptr->next;
 2149             ++i;
 2150             for (; uueptr != NULL; uueptr = uueptr->next) {
 2151                 lptr->next = my_malloc(sizeof(t_partl));
 2152                 lptr->next->part = uueptr;
 2153                 lptr->next->next = NULL;
 2154                 lptr->next->tagged = 0;
 2155                 lptr = lptr->next;
 2156                 ++i;
 2157             }
 2158         }
 2159 
 2160         if (ptr->uue)
 2161             continue;
 2162 
 2163         lptr->next = my_malloc(sizeof(t_partl));
 2164         lptr->next->part = ptr;
 2165         lptr->next->next = NULL;
 2166         lptr->next->tagged = 0;
 2167         lptr = lptr->next;
 2168         ++i;
 2169     }
 2170     return i;
 2171 }
 2172 
 2173 
 2174 static void
 2175 free_part_list(
 2176     t_partl *list)
 2177 {
 2178     while (list->next != NULL) {
 2179         free_part_list(list->next);
 2180         list->next = NULL;
 2181     }
 2182     free(list);
 2183 }
 2184 
 2185 
 2186 static void
 2187 process_parts(
 2188     t_part *part,
 2189     t_openartinfo *art,
 2190     enum action what)
 2191 {
 2192     FILE *fp;
 2193     char *savepath = NULL, *tmppath;
 2194     int i, saved_parts = 0;
 2195     t_partl *lptr;
 2196 
 2197     switch (what) {
 2198         case SAVE_TAGGED:
 2199             for (i = 1; i <= num_of_tagged_parts; i++) {
 2200                 lptr = part_list;
 2201 
 2202                 while (lptr) {
 2203                     if (lptr->tagged == i) {
 2204                         if ((savepath = generate_savepath(lptr->part)) == NULL)
 2205                             return;
 2206 
 2207                         if ((fp = open_save_filename(savepath, FALSE)) == NULL) {
 2208                             free(savepath);
 2209                             return;
 2210                         }
 2211                         process_part(lptr->part, art, fp, NULL, SAVE);
 2212                         free(savepath);
 2213                         ++saved_parts;
 2214                     }
 2215                     lptr = lptr->next;
 2216                 }
 2217             }
 2218             break;
 2219 
 2220         default:
 2221             if ((tmppath = generate_savepath(part)) == NULL)
 2222                 return;
 2223 
 2224             if (what == SAVE)
 2225                 savepath = tmppath;
 2226             else {
 2227                 savepath = get_tmpfilename(tmppath);
 2228                 free(tmppath);
 2229             }
 2230             if ((fp = open_save_filename(savepath, FALSE)) == NULL) {
 2231                 free(savepath);
 2232                 return;
 2233             }
 2234             process_part(part, art, fp, savepath, what);
 2235             break;
 2236     }
 2237     switch (what) {
 2238         case SAVE_TAGGED:
 2239             wait_message(2, _(txt_attachments_saved), saved_parts, num_of_tagged_parts);
 2240             break;
 2241 
 2242         case SAVE:
 2243             wait_message(2, _(txt_attachment_saved), savepath);
 2244             free(savepath);
 2245             break;
 2246 
 2247         default:
 2248             unlink(savepath);
 2249             free(savepath);
 2250             break;
 2251     }
 2252     cursoroff();
 2253 }
 2254 
 2255 
 2256 /*
 2257  * VIEW/PIPE/SAVE the given part.
 2258  *
 2259  * PIPE_RAW uses the raw part, otherwise the part is decoded first.
 2260  */
 2261 static void
 2262 process_part(
 2263     t_part *part,
 2264     t_openartinfo *art,
 2265     FILE *outfile,
 2266     const char *savepath,
 2267     enum action what)
 2268 {
 2269     FILE *infile = NULL;
 2270     char buf[2048], buf2[2048];
 2271     int count;
 2272     int i, line_count;
 2273 #ifdef CHARSET_CONVERSION
 2274     char *conv_buf;
 2275     const char *network_charset;
 2276     size_t line_len;
 2277 #endif /* CHARSET_CONVERSION */
 2278 
 2279     /*
 2280      * uuencoded parts must be read from the cooked article,
 2281      * otherwise they might be additionally encoded with b64 or qp
 2282      */
 2283     if (part->encoding == ENCODING_UUE)
 2284         infile = art->cooked;
 2285     else
 2286         infile = art->raw;
 2287 
 2288     if (what != PIPE_RAW && part->encoding == ENCODING_BASE64)
 2289         mmdecode(NULL, 'b', 0, NULL);               /* flush */
 2290 
 2291     fseek(infile, part->offset, SEEK_SET);
 2292 
 2293     line_count = part->line_count;
 2294 
 2295     for (i = 0; i < line_count; i++) {
 2296         if ((fgets(buf, sizeof(buf), infile)) == NULL)
 2297             break;
 2298 
 2299         /* This should catch cases where people illegally append text etc */
 2300         if (buf[0] == '\0')
 2301             break;
 2302 
 2303         /*
 2304          * page.c:new_uue() sets offset to the 'begin ...' line
 2305          * -> skip over the first line in uuencoded parts
 2306          */
 2307         if (part->encoding == ENCODING_UUE && i == 0) {
 2308             ++line_count;
 2309             continue;
 2310         }
 2311 
 2312         if (what != PIPE_RAW) {
 2313             switch (part->encoding) {
 2314                 case ENCODING_QP:
 2315                 case ENCODING_BASE64:
 2316 #ifdef CHARSET_CONVERSION
 2317                     memset(buf2, '\0', sizeof(buf2));
 2318 #endif /* CHARSET_CONVERSION */
 2319                     if ((count = mmdecode(buf, part->encoding == ENCODING_QP ? 'q' : 'b', '\0', buf2)) > 0) {
 2320 #ifdef CHARSET_CONVERSION
 2321                         if (what != SAVE && what != SAVE_TAGGED && !strncmp(content_types[part->type], "text", 4)) {
 2322                             line_len = count;
 2323                             conv_buf = my_strdup(buf2);
 2324                             network_charset = get_param(part->params, "charset");
 2325                             process_charsets(&conv_buf, &line_len, network_charset ? network_charset : "US-ASCII", tinrc.mm_local_charset, FALSE);
 2326                             strncpy(buf2, conv_buf, sizeof(buf2) - 1);
 2327                             count = strlen(buf2);
 2328                             free(conv_buf);
 2329                         }
 2330 #endif /* CHARSET_CONVERSION */
 2331                         fwrite(buf2, count, 1, outfile);
 2332                     }
 2333                     break;
 2334 
 2335                 case ENCODING_UUE:
 2336                     /* TODO: if postproc, don't decode these since the traditional uudecoder will get them */
 2337                     /*
 2338                      * x-uuencode attachments have all the header info etc which we must ignore
 2339                      */
 2340                     if (strncmp(buf, "begin ", 6) != 0 && strncmp(buf, "end\n", 4) != 0 && buf[0] != '\n')
 2341                         uudecode_line(buf, outfile);
 2342                     break;
 2343 
 2344                 default:
 2345 #ifdef CHARSET_CONVERSION
 2346                         if (what != SAVE && what != SAVE_TAGGED && !strncmp(content_types[part->type], "text", 4)) {
 2347                             conv_buf = my_strdup(buf);
 2348                             line_len = strlen(conv_buf);
 2349                             network_charset = get_param(part->params, "charset");
 2350                             process_charsets(&conv_buf, &line_len, network_charset ? network_charset : "US-ASCII", tinrc.mm_local_charset, FALSE);
 2351                             strncpy(buf, conv_buf, sizeof(buf) - 1);
 2352                             free(conv_buf);
 2353                         }
 2354 #endif /* CHARSET_CONVERSION */
 2355                     fputs(buf, outfile);
 2356             }
 2357         } else
 2358             fputs(buf, outfile);
 2359     }
 2360 
 2361     fclose(outfile);
 2362 
 2363     switch (what) {
 2364         case VIEW:
 2365             start_viewer(part, savepath);
 2366             break;
 2367 
 2368 #ifndef DONT_HAVE_PIPING
 2369         case PIPE:
 2370         case PIPE_RAW:
 2371             pipe_part(savepath);
 2372             break;
 2373 #endif /* !DONT_HAVE_PIPING */
 2374 
 2375         default:
 2376             break;
 2377     }
 2378 }
 2379 
 2380 
 2381 #ifndef DONT_HAVE_PIPING
 2382 static void
 2383 pipe_part(
 2384     const char *savepath)
 2385 {
 2386     FILE *fp, *pipe_fp;
 2387     char *prompt;
 2388 
 2389     prompt = fmt_string(_(txt_pipe_to_command), cCOLS - (strlen(_(txt_pipe_to_command)) + 30), tinrc.default_pipe_command);
 2390     if (!(prompt_string_default(prompt, tinrc.default_pipe_command, _(txt_no_command), HIST_PIPE_COMMAND))) {
 2391         free(prompt);
 2392         return;
 2393     }
 2394     free(prompt);
 2395     if ((fp = fopen(savepath, "r")) == NULL)
 2396         /* TODO: error message? */
 2397         return;
 2398     EndWin();
 2399     Raw(FALSE);
 2400     fflush(stdout);
 2401     set_signal_catcher(FALSE);
 2402     if ((pipe_fp = popen(tinrc.default_pipe_command, "w")) == NULL) {
 2403         perror_message(_(txt_command_failed), tinrc.default_pipe_command);
 2404         set_signal_catcher(TRUE);
 2405         Raw(TRUE);
 2406         InitWin();
 2407         fclose(fp);
 2408         return;
 2409     }
 2410     copy_fp(fp, pipe_fp);
 2411     if (errno == EPIPE)
 2412         perror_message(_(txt_command_failed), tinrc.default_pipe_command);
 2413     fflush(pipe_fp);
 2414     (void) pclose(pipe_fp);
 2415     set_signal_catcher(TRUE);
 2416     fclose(fp);
 2417 #   ifdef USE_CURSES
 2418     Raw(TRUE);
 2419     InitWin();
 2420 #   endif /* USE_CURSES */
 2421     prompt_continue();
 2422 #   ifndef USE_CURSES
 2423     Raw(TRUE);
 2424     InitWin();
 2425 #   endif /* !USE_CURSES */
 2426 }
 2427 #endif /* !DONT_HAVE_PIPING */