"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.5/src/save.c" (1 Dec 2020, 59487 Bytes) of package /linux/misc/tin-2.4.5.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.4_vs_2.4.5.

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