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