"Fossies" - the Fresh Open Source Software Archive 
Member "tin-2.6.1/src/save.c" (22 Dec 2021, 58730 Bytes) of package /linux/misc/tin-2.6.1.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.0_vs_2.6.1.
1 /*
2 * Project : tin - a Usenet reader
3 * Module : save.c
4 * Author : I. Lea & R. Skrenta
5 * Created : 1991-04-01
6 * Updated : 2021-02-25
7 * Notes :
8 *
9 * Copyright (c) 1991-2022 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) && pcre_exec(shar_regex.re, shar_regex.extra, buf, (int) strlen(buf), 0, 0, NULL, 0) >= 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 negate = TRUE;
1381 ++type;
1382
1383 if (!*type) /* Invalid type */
1384 return NO;
1385 }
1386
1387 /* Split type and subtype */
1388 if ((subtype = strchr(type, '/')) == NULL)
1389 return NO;
1390 *(subtype++) = '\0';
1391
1392 if (!*type || !*subtype) /* Missing type or subtype */
1393 return NO;
1394
1395 /* Try and match major */
1396 if (strcmp(type, "*") == 0)
1397 found = TRUE;
1398 else if (((typeindex = content_type(type)) != -1) && typeindex == part->type)
1399 found = TRUE;
1400
1401 if (!found)
1402 return NO;
1403
1404 /* Try and match subtype */
1405 found = FALSE;
1406 if (strcmp(subtype, "*") == 0)
1407 found = TRUE;
1408 else if (strcmp(subtype, part->subtype) == 0)
1409 found = TRUE;
1410
1411 if (!found)
1412 return NO;
1413
1414 /* We got a match */
1415 if (negate)
1416 return NOTMATCH;
1417
1418 return MATCH;
1419 }
1420
1421
1422 /*
1423 * See if the mime type of this part matches the list of content types to save
1424 * or ignore. Return TRUE if there is a match
1425 * mime_types is a comma separated list of type/subtype pairs. type and/or
1426 * subtype can be a '*' to match any, and a pair can begin with a ! which
1427 * will negate the meaning. We eval all pairs, the rightmost match will
1428 * prevail
1429 */
1430 static t_bool
1431 check_save_mime_type(
1432 t_part *part,
1433 const char *mime_types)
1434 {
1435 char *ptr, *pair;
1436 int found;
1437 int retcode;
1438
1439 if (!mime_types)
1440 return FALSE;
1441
1442 ptr = my_strdup(mime_types);
1443
1444 if ((pair = strtok(ptr, ",")) == NULL) {
1445 free(ptr);
1446 return FALSE;
1447 }
1448
1449 retcode = match_content_type(part, pair);
1450
1451 while ((pair = strtok(NULL, ",")) != NULL) {
1452 if ((found = match_content_type(part, pair)) != NO)
1453 retcode = found;
1454 }
1455
1456 free(ptr);
1457 return (retcode == MATCH);
1458 }
1459
1460
1461 /*
1462 * decode and save binary MIME attachments from an open article context
1463 * optionally locate and launch a viewer application
1464 * 'postproc' determines the mode of the operation and will be set to
1465 * TRUE when we're called during a [Ss]ave operation and FALSE when
1466 * when just viewing
1467 * When it is TRUE the view option will depend on post_process_view and
1468 * the save is implicit. Feedback will also be printed.
1469 * When it is FALSE then the view/save options will be queried
1470 */
1471 void
1472 decode_save_mime(
1473 t_openartinfo *art,
1474 t_bool postproc)
1475 {
1476 t_part *ptr, *uueptr;
1477
1478 /*
1479 * Iterate over all the attachments
1480 */
1481 for (ptr = art->hdr.ext; ptr != NULL; ptr = ptr->next) {
1482 /*
1483 * Handle uuencoded sections in this message part.
1484 * Only works when the uuencoded file is entirely within the current
1485 * article.
1486 * We don't do this when postprocessing as the generic uudecode code
1487 * already handles uuencoded data, but TODO: review this
1488 */
1489 if (!postproc) {
1490 for (uueptr = ptr->uue; uueptr != NULL; uueptr = uueptr->next) {
1491 if (!(decode_save_one(uueptr, art->raw, postproc)))
1492 break;
1493 }
1494 }
1495
1496 /*
1497 * TYPE_MULTIPART is an envelope type, don't process it.
1498 * If we had an UUE part, the "surrounding" text/plain plays
1499 * the role of a multipart part. Check to see if we want to
1500 * save text and if not, skip this part.
1501 */
1502 /* check_save_mime_type() is done in decode_save_one() and the check for ptr->uue must be done unconditionally */
1503 if (ptr->type == TYPE_MULTIPART || (NULL != ptr->uue /* && !check_save_mime_type(ptr, curr_group->attribute->mime_types_to_save) */ ))
1504 continue;
1505
1506 if (!(decode_save_one(ptr, art->raw, postproc)))
1507 break;
1508 }
1509 }
1510
1511
1512 /*
1513 * Attachment menu
1514 */
1515 static void
1516 show_attachment_page(
1517 void)
1518 {
1519 char buf[BUFSIZ];
1520 const char *charset;
1521 int i, tmp_len, max_depth;
1522 t_part *part;
1523
1524 signal_context = cAttachment;
1525 currmenu = &attmenu;
1526 mark_offset = 0;
1527
1528 if (attmenu.curr < 0)
1529 attmenu.curr = 0;
1530
1531 info_len = max_depth = 0;
1532 for (i = 0; i < attmenu.max; ++i) {
1533 part = get_part(i);
1534 snprintf(buf, sizeof(buf), _(txt_attachment_lines), part->line_count);
1535 tmp_len = strwidth(buf);
1536 charset = get_param(part->params, "charset");
1537 snprintf(buf, sizeof(buf), " %s/%s, %s, %s%s", content_types[part->type], part->subtype, content_encodings[part->encoding], charset ? charset : "", charset ? ", " : "");
1538 tmp_len += strwidth(buf);
1539 if (tmp_len > info_len)
1540 info_len = tmp_len;
1541
1542 tmp_len = part->depth;
1543 if (tmp_len > max_depth)
1544 max_depth = tmp_len;
1545 }
1546 tmp_len = cCOLS - 13 - MIN((cCOLS - 13) / 2 + 10, max_depth * 2 + 1 + strwidth(_(txt_attachment_no_name)));
1547 if (info_len > tmp_len)
1548 info_len = tmp_len;
1549
1550 ClearScreen();
1551 set_first_screen_item();
1552 center_line(0, TRUE, _(txt_attachment_menu));
1553
1554 for (i = attmenu.first; i < attmenu.first + NOTESLINES && i < attmenu.max; ++i)
1555 build_attachment_line(i);
1556
1557 show_mini_help(ATTACHMENT_LEVEL);
1558
1559 if (attmenu.max <= 0) {
1560 info_message(_(txt_no_attachments));
1561 return;
1562 }
1563
1564 draw_attachment_arrow();
1565 }
1566
1567
1568 void
1569 attachment_page(
1570 t_openartinfo *art)
1571 {
1572 char key[MAXKEYLEN];
1573 t_function func;
1574 t_menu *oldmenu = NULL;
1575 t_part *part;
1576
1577 if (currmenu)
1578 oldmenu = currmenu;
1579 num_of_tagged_parts = 0;
1580 attmenu.curr = 0;
1581 attmenu.max = build_part_list(art);
1582 clear_note_area();
1583 show_attachment_page();
1584 set_xclick_off();
1585
1586 forever {
1587 switch ((func = handle_keypad(attachment_left, attachment_right, NULL, attachment_keys))) {
1588 case GLOBAL_QUIT:
1589 free_part_list(part_list);
1590 if (oldmenu)
1591 currmenu = oldmenu;
1592 return;
1593
1594 case DIGIT_1:
1595 case DIGIT_2:
1596 case DIGIT_3:
1597 case DIGIT_4:
1598 case DIGIT_5:
1599 case DIGIT_6:
1600 case DIGIT_7:
1601 case DIGIT_8:
1602 case DIGIT_9:
1603 if (attmenu.max)
1604 prompt_item_num(func_to_key(func, attachment_keys), _(txt_attachment_select));
1605 break;
1606
1607 #ifndef NO_SHELL_ESCAPE
1608 case GLOBAL_SHELL_ESCAPE:
1609 do_shell_escape();
1610 break;
1611 #endif /* !NO_SHELL_ESCAPE */
1612
1613 case GLOBAL_HELP:
1614 show_help_page(ATTACHMENT_LEVEL, _(txt_attachment_menu_com));
1615 show_attachment_page();
1616 break;
1617
1618 case GLOBAL_BUGREPORT:
1619 bug_report();
1620 show_attachment_page();
1621 break;
1622
1623 case GLOBAL_FIRST_PAGE:
1624 top_of_list();
1625 break;
1626
1627 case GLOBAL_LAST_PAGE:
1628 end_of_list();
1629 break;
1630
1631 case GLOBAL_REDRAW_SCREEN:
1632 my_retouch();
1633 show_attachment_page();
1634 break;
1635
1636 case GLOBAL_LINE_DOWN:
1637 move_down();
1638 break;
1639
1640 case GLOBAL_LINE_UP:
1641 move_up();
1642 break;
1643
1644 case GLOBAL_PAGE_DOWN:
1645 page_down();
1646 break;
1647
1648 case GLOBAL_PAGE_UP:
1649 page_up();
1650 break;
1651
1652 case GLOBAL_SCROLL_DOWN:
1653 scroll_down();
1654 break;
1655
1656 case GLOBAL_SCROLL_UP:
1657 scroll_up();
1658 break;
1659
1660 case GLOBAL_TOGGLE_HELP_DISPLAY:
1661 toggle_mini_help(ATTACHMENT_LEVEL);
1662 show_attachment_page();
1663 break;
1664
1665 case GLOBAL_TOGGLE_INFO_LAST_LINE:
1666 tinrc.info_in_last_line = bool_not(tinrc.info_in_last_line);
1667 show_attachment_page();
1668 break;
1669
1670 case ATTACHMENT_SAVE:
1671 if (attmenu.max) {
1672 part = get_part(attmenu.curr);
1673 process_parts(part, art, num_of_tagged_parts ? SAVE_TAGGED : SAVE);
1674 show_attachment_page();
1675 }
1676 break;
1677
1678 case ATTACHMENT_SELECT:
1679 if (attmenu.max) {
1680 part = get_part(attmenu.curr);
1681 process_parts(part, art, VIEW);
1682 show_attachment_page();
1683 }
1684 break;
1685
1686 case ATTACHMENT_TAG:
1687 if (attmenu.max) {
1688 t_bool tagged;
1689
1690 tagged = tag_part(attmenu.curr);
1691 show_attachment_page();
1692 if (attmenu.curr + 1 < attmenu.max)
1693 move_down();
1694 info_message(tagged ? _(txt_attachment_tagged) : _(txt_attachment_untagged));
1695 }
1696 break;
1697
1698 case ATTACHMENT_UNTAG:
1699 if (attmenu.max && num_of_tagged_parts) {
1700 untag_all_parts();
1701 show_attachment_page();
1702 }
1703 break;
1704
1705 case ATTACHMENT_TAG_PATTERN:
1706 if (attmenu.max) {
1707 tag_pattern();
1708 show_attachment_page();
1709 info_message(_(txt_attachments_tagged), num_of_tagged_parts);
1710 }
1711 break;
1712
1713 case ATTACHMENT_TOGGLE_TAGGED:
1714 if (attmenu.max) {
1715 int i;
1716
1717 for (i = attmenu.first; i < attmenu.max; ++i)
1718 tag_part(i);
1719 show_attachment_page();
1720 info_message(_(txt_attachments_tagged), num_of_tagged_parts);
1721 }
1722 break;
1723
1724 case GLOBAL_SEARCH_SUBJECT_FORWARD:
1725 case GLOBAL_SEARCH_SUBJECT_BACKWARD:
1726 case GLOBAL_SEARCH_REPEAT:
1727 if (func == GLOBAL_SEARCH_REPEAT && last_search != GLOBAL_SEARCH_SUBJECT_FORWARD && last_search != GLOBAL_SEARCH_SUBJECT_BACKWARD)
1728 info_message(_(txt_no_prev_search));
1729 else if (attmenu.max) {
1730 int new_pos, old_pos = attmenu.curr;
1731
1732 new_pos = generic_search((func == GLOBAL_SEARCH_SUBJECT_FORWARD), (func == GLOBAL_SEARCH_REPEAT), attmenu.curr, attmenu.max - 1, ATTACHMENT_LEVEL);
1733 if (new_pos != old_pos)
1734 move_to_item(new_pos);
1735 }
1736 break;
1737
1738 #ifndef DONT_HAVE_PIPING
1739 case ATTACHMENT_PIPE:
1740 case GLOBAL_PIPE:
1741 if (attmenu.max) {
1742 part = get_part(attmenu.curr);
1743 process_parts(part, art, func == GLOBAL_PIPE ? PIPE_RAW : PIPE);
1744 show_attachment_page();
1745 }
1746 break;
1747 #endif /* !DONT_HAVE_PIPING */
1748
1749 default:
1750 info_message(_(txt_bad_command), PrintFuncKey(key, GLOBAL_HELP, attachment_keys));
1751 break;
1752 }
1753 }
1754 }
1755
1756
1757 static t_function
1758 attachment_left(
1759 void)
1760 {
1761 return GLOBAL_QUIT;
1762 }
1763
1764
1765 static t_function
1766 attachment_right(
1767 void)
1768 {
1769 return ATTACHMENT_SELECT;
1770 }
1771
1772
1773 static void
1774 draw_attachment_arrow(
1775 void)
1776 {
1777 draw_arrow_mark(INDEX_TOP + attmenu.curr - attmenu.first);
1778 if (tinrc.info_in_last_line) {
1779 const char *name;
1780 t_part *part;
1781
1782 part = get_part(attmenu.curr);
1783 name = get_filename(part->params);
1784 info_message("%s %s", name ? name : _(txt_attachment_no_name), BlankIfNull(part->description));
1785 } else if (attmenu.curr == attmenu.max - 1)
1786 info_message(_(txt_end_of_attachments));
1787 }
1788
1789
1790 static void
1791 build_attachment_line(
1792 int i)
1793 {
1794 char *sptr;
1795 const char *name;
1796 const char *charset;
1797 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1798 char *tmpname;
1799 char *tmpbuf;
1800 #endif /* MULTIBYTE_ABLE && !NOLOCALE */
1801 char buf[BUFSIZ];
1802 char buf2[BUFSIZ];
1803 char *tree = NULL;
1804 int len, namelen, tagged, treelen;
1805 t_part *part;
1806
1807 #ifdef USE_CURSES
1808 /*
1809 * Allocate line buffer
1810 * make it the same size like in !USE_CURSES case to simplify some code
1811 */
1812 # if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1813 sptr = my_malloc(cCOLS * MB_CUR_MAX + 2);
1814 # else
1815 sptr = my_malloc(cCOLS + 2);
1816 # endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1817 #else
1818 sptr = screen[INDEX2SNUM(i)].col;
1819 #endif /* USE_CURSES */
1820
1821 part = get_part(i);
1822 namelen = MIN(cCOLS - 13 - info_len - 8, strwidth(_(txt_attachment_no_name)));
1823 tagged = get_tagged(i);
1824
1825 if (!(name = get_filename(part->params))) {
1826 if (!(name = part->description))
1827 name = _(txt_attachment_no_name);
1828 }
1829
1830 charset = get_param(part->params, "charset");
1831 snprintf(buf2, sizeof(buf2), _(txt_attachment_lines), part->line_count);
1832 /* TODO: make the layout configurable? */
1833 if (!strcmp(content_types[part->type], "text"))
1834 snprintf(buf, sizeof(buf), " %s/%s, %s, %s%s%s", content_types[part->type], part->subtype, content_encodings[part->encoding], charset ? charset : "", charset ? ", " : "", buf2);
1835 else
1836 snprintf(buf, sizeof(buf), " %s/%s, %s, %s", content_types[part->type], part->subtype, content_encodings[part->encoding], buf2);
1837 if (part->depth > 0) {
1838 treelen = cCOLS - 13 - info_len - namelen;
1839 tree = build_tree(part->depth, treelen, i);
1840 }
1841 snprintf(buf2, sizeof(buf2), "%s %s", tagged ? tin_ltoa(tagged, 3) : " ", BlankIfNull(tree));
1842 FreeIfNeeded(tree);
1843 len = strwidth(buf2);
1844 if (namelen + len + info_len + 8 <= cCOLS)
1845 namelen = cCOLS - 8 - info_len - len;
1846
1847 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1848 tmpname = spart(name, namelen, TRUE);
1849 tmpbuf = spart(buf, info_len, TRUE);
1850 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);
1851 FreeIfNeeded(tmpname);
1852 FreeIfNeeded(tmpbuf);
1853 #else
1854 snprintf(sptr, cCOLS, " %s %s%-*.*s%*.*s%s", tin_ltoa(i + 1, 4), buf2, namelen, namelen, name, info_len, info_len, buf, cCRLF);
1855 #endif /* MULTIBYTE_ABLE && !NOLOCALE */
1856
1857 WriteLine(INDEX2LNUM(i), sptr);
1858
1859 #ifdef USE_CURSES
1860 free(sptr);
1861 #endif /* USE_CURSES */
1862 }
1863
1864
1865 /*
1866 * Build attachment tree. Code adopted
1867 * from thread.c:make_prefix().
1868 */
1869 static char *
1870 build_tree(
1871 int depth,
1872 int maxlen,
1873 int i)
1874 {
1875 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1876 char *result;
1877 wchar_t *tree;
1878 #else
1879 char *tree;
1880 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1881 int prefix_ptr, tmpdepth;
1882 int depth_level = 0;
1883 t_bool found = FALSE;
1884 t_partl *lptr, *lptr2;
1885
1886 lptr2 = find_part(i);
1887 prefix_ptr = depth * 2 - 1;
1888 if (prefix_ptr > maxlen - 1 - !(maxlen % 2)) {
1889 int odd = ((maxlen % 2) ? 0 : 1);
1890
1891 prefix_ptr -= maxlen - ++depth_level - 2 - odd;
1892 while (prefix_ptr > maxlen - 2 - odd) {
1893 if (depth_level < maxlen / 5)
1894 depth_level++;
1895
1896 prefix_ptr -= maxlen - depth_level - 2 - odd;
1897 odd = (odd ? 0 : 1);
1898 }
1899 }
1900 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1901 tree = my_malloc(sizeof(wchar_t) * (size_t) prefix_ptr + 3 * sizeof(wchar_t));
1902 tree[prefix_ptr + 2] = (wchar_t) '\0';
1903 #else
1904 tree = my_malloc(prefix_ptr + 3);
1905 tree[prefix_ptr + 2] = '\0';
1906 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1907 tree[prefix_ptr + 1] = TREE_ARROW;
1908 tree[prefix_ptr] = TREE_HORIZ;
1909 for (lptr = lptr2->next; lptr != NULL; lptr = lptr->next) {
1910 if (lptr->part->depth == depth) {
1911 found = TRUE;
1912 break;
1913 }
1914 if (lptr->part->depth < depth)
1915 break;
1916 }
1917 tree[--prefix_ptr] = found ? TREE_VERT_RIGHT : TREE_UP_RIGHT;
1918 found = FALSE;
1919 for (tmpdepth = depth - 1; prefix_ptr > 1; --tmpdepth) {
1920 for (lptr = lptr2->next; lptr != NULL; lptr = lptr->next) {
1921 if (lptr->part->depth == tmpdepth) {
1922 found = TRUE;
1923 break;
1924 }
1925 if (lptr->part->depth < tmpdepth)
1926 break;
1927 }
1928 tree[--prefix_ptr] = TREE_BLANK;
1929 tree[--prefix_ptr] = found ? TREE_VERT : TREE_BLANK;
1930 found = FALSE;
1931 }
1932 while (depth_level)
1933 tree[--depth_level] = TREE_ARROW_WRAP;
1934
1935 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1936 result = wchar_t2char(tree);
1937 free(tree);
1938 return result;
1939 #else
1940 return tree;
1941 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1942 }
1943
1944
1945 /*
1946 * Find nth attachment in part_list.
1947 * Return pointer to that part.
1948 */
1949 static t_partl *
1950 find_part(
1951 int n)
1952 {
1953 t_partl *lptr;
1954
1955 lptr = part_list;
1956 if (attmenu.max >= 1)
1957 lptr = lptr->next;
1958
1959 while (n-- > 0 && lptr->next)
1960 lptr = lptr->next;
1961
1962 return lptr;
1963 }
1964
1965
1966 t_part *
1967 get_part(
1968 int n)
1969 {
1970 t_partl *lptr;
1971
1972 lptr = find_part(n);
1973 return lptr->part;
1974 }
1975
1976
1977 static void
1978 tag_pattern(
1979 void)
1980 {
1981 char buf[BUFSIZ];
1982 char pat[128];
1983 char *prompt;
1984 const char *name;
1985 const char *charset;
1986 struct regex_cache cache = { NULL, NULL };
1987 t_part *part;
1988 t_partl *lptr;
1989
1990 #if 0
1991 if (num_of_tagged_parts)
1992 untag_all_parts();
1993 #endif /* 0 */
1994
1995 prompt = fmt_string(_(txt_select_pattern), tinrc.default_select_pattern);
1996 if (!(prompt_string_default(prompt, tinrc.default_select_pattern, _(txt_info_no_previous_expression), HIST_SELECT_PATTERN))) {
1997 free(prompt);
1998 return;
1999 }
2000 free(prompt);
2001
2002 if (STRCMPEQ(tinrc.default_select_pattern, "*")) { /* all */
2003 if (tinrc.wildcard)
2004 STRCPY(pat, ".*");
2005 else
2006 STRCPY(pat, tinrc.default_select_pattern);
2007 } else
2008 snprintf(pat, sizeof(pat), REGEX_FMT, tinrc.default_select_pattern);
2009
2010 if (tinrc.wildcard && !(compile_regex(pat, &cache, PCRE_CASELESS)))
2011 return;
2012
2013 lptr = find_part(0);
2014
2015 for (; lptr != NULL; lptr = lptr->next) {
2016 part = lptr->part;
2017 if (!(name = get_filename(part->params))) {
2018 if (!(name = part->description))
2019 name = _(txt_attachment_no_name);
2020 }
2021 charset = get_param(part->params, "charset");
2022
2023 snprintf(buf, sizeof(buf), "%s %s/%s %s, %s", name, content_types[part->type], part->subtype, content_encodings[part->encoding], charset ? charset : "");
2024
2025 if (!match_regex(buf, pat, &cache, TRUE))
2026 continue;
2027
2028 if (!lptr->tagged)
2029 lptr->tagged = ++num_of_tagged_parts;
2030 }
2031
2032 if (tinrc.wildcard) {
2033 FreeIfNeeded(cache.re);
2034 FreeIfNeeded(cache.extra);
2035 }
2036 }
2037
2038
2039 static int
2040 get_tagged(
2041 int n)
2042 {
2043 t_partl *lptr;
2044
2045 lptr = find_part(n);
2046 return lptr->tagged;
2047 }
2048
2049
2050 static t_bool
2051 tag_part(
2052 int n)
2053 {
2054 t_partl *lptr;
2055
2056 lptr = find_part(n);
2057 if (lptr->tagged) {
2058 untag_part(n);
2059 return FALSE;
2060 } else {
2061 lptr->tagged = ++num_of_tagged_parts;
2062 return TRUE;
2063 }
2064 }
2065
2066
2067 static void
2068 untag_part(
2069 int n)
2070 {
2071 int i;
2072 t_partl *curr_part, *lptr;
2073
2074 lptr = find_part(0);
2075 curr_part = find_part(n);
2076 i = attmenu.max;
2077
2078 while (i-- > 0 && lptr) {
2079 if (lptr->tagged > curr_part->tagged)
2080 --lptr->tagged;
2081 lptr = lptr->next;
2082 }
2083
2084 curr_part->tagged = 0;
2085 --num_of_tagged_parts;
2086 }
2087
2088
2089 static void
2090 untag_all_parts(
2091 void)
2092 {
2093 t_partl *lptr = part_list;
2094
2095 while (lptr) {
2096 if (lptr->tagged)
2097 lptr->tagged = 0;
2098
2099 lptr = lptr->next;
2100 }
2101 num_of_tagged_parts = 0;
2102 }
2103
2104
2105 /*
2106 * Build a linked list which holds pointers to the parts we want deal with.
2107 */
2108 static int
2109 build_part_list(
2110 t_openartinfo *art)
2111 {
2112 int i = 0;
2113 t_part *ptr, *uueptr;
2114 t_partl *lptr;
2115
2116 part_list = my_malloc(sizeof(t_partl));
2117 lptr = part_list;
2118 lptr->part = art->hdr.ext;
2119 lptr->next = NULL;
2120 lptr->tagged = 0;
2121 for (ptr = art->hdr.ext; ptr != NULL; ptr = ptr->next) {
2122 if ((uueptr = ptr->uue) != NULL) {
2123 lptr->next = my_malloc(sizeof(t_partl));
2124 lptr->next->part = ptr;
2125 lptr->next->next = NULL;
2126 lptr->next->tagged = 0;
2127 lptr = lptr->next;
2128 ++i;
2129 for (; uueptr != NULL; uueptr = uueptr->next) {
2130 lptr->next = my_malloc(sizeof(t_partl));
2131 lptr->next->part = uueptr;
2132 lptr->next->next = NULL;
2133 lptr->next->tagged = 0;
2134 lptr = lptr->next;
2135 ++i;
2136 }
2137 }
2138
2139 if (ptr->uue)
2140 continue;
2141
2142 lptr->next = my_malloc(sizeof(t_partl));
2143 lptr->next->part = ptr;
2144 lptr->next->next = NULL;
2145 lptr->next->tagged = 0;
2146 lptr = lptr->next;
2147 ++i;
2148 }
2149 return i;
2150 }
2151
2152
2153 static void
2154 free_part_list(
2155 t_partl *list)
2156 {
2157 while (list->next != NULL) {
2158 free_part_list(list->next);
2159 list->next = NULL;
2160 }
2161 free(list);
2162 }
2163
2164
2165 static void
2166 process_parts(
2167 t_part *part,
2168 t_openartinfo *art,
2169 enum action what)
2170 {
2171 FILE *fp;
2172 char *savepath = NULL, *tmppath;
2173 int i, saved_parts = 0;
2174 t_partl *lptr;
2175
2176 switch (what) {
2177 case SAVE_TAGGED:
2178 for (i = 1; i <= num_of_tagged_parts; i++) {
2179 lptr = part_list;
2180
2181 while (lptr) {
2182 if (lptr->tagged == i) {
2183 if ((savepath = generate_savepath(lptr->part)) == NULL)
2184 return;
2185
2186 if ((fp = open_save_filename(savepath, FALSE)) == NULL) {
2187 free(savepath);
2188 return;
2189 }
2190 process_part(lptr->part, art, fp, NULL, SAVE);
2191 free(savepath);
2192 ++saved_parts;
2193 }
2194 lptr = lptr->next;
2195 }
2196 }
2197 break;
2198
2199 default:
2200 if ((tmppath = generate_savepath(part)) == NULL)
2201 return;
2202
2203 if (what == SAVE)
2204 savepath = tmppath;
2205 else {
2206 savepath = get_tmpfilename(tmppath);
2207 free(tmppath);
2208 }
2209 if ((fp = open_save_filename(savepath, FALSE)) == NULL) {
2210 free(savepath);
2211 return;
2212 }
2213 process_part(part, art, fp, savepath, what);
2214 break;
2215 }
2216 switch (what) {
2217 case SAVE_TAGGED:
2218 wait_message(2, _(txt_attachments_saved), saved_parts, num_of_tagged_parts);
2219 break;
2220
2221 case SAVE:
2222 wait_message(2, _(txt_attachment_saved), savepath);
2223 free(savepath);
2224 break;
2225
2226 default:
2227 unlink(savepath);
2228 free(savepath);
2229 break;
2230 }
2231 cursoroff();
2232 }
2233
2234
2235 /*
2236 * VIEW/PIPE/SAVE the given part.
2237 *
2238 * PIPE_RAW uses the raw part, otherwise the part is decoded first.
2239 */
2240 static void
2241 process_part(
2242 t_part *part,
2243 t_openartinfo *art,
2244 FILE *outfile,
2245 const char *savepath,
2246 enum action what)
2247 {
2248 FILE *infile;
2249 char buf[2048], buf2[2048];
2250 int count;
2251 int i, line_count;
2252 #ifdef CHARSET_CONVERSION
2253 char *conv_buf;
2254 const char *network_charset;
2255 size_t line_len;
2256 #endif /* CHARSET_CONVERSION */
2257
2258 /*
2259 * uuencoded parts must be read from the cooked article,
2260 * otherwise they might be additionally encoded with b64 or qp
2261 */
2262 if (part->encoding == ENCODING_UUE)
2263 infile = art->cooked;
2264 else
2265 infile = art->raw;
2266
2267 if (what != PIPE_RAW && part->encoding == ENCODING_BASE64)
2268 mmdecode(NULL, 'b', 0, NULL); /* flush */
2269
2270 fseek(infile, part->offset, SEEK_SET);
2271
2272 line_count = part->line_count;
2273
2274 for (i = 0; i < line_count; i++) {
2275 if ((fgets(buf, sizeof(buf), infile)) == NULL)
2276 break;
2277
2278 /* This should catch cases where people illegally append text etc */
2279 if (buf[0] == '\0')
2280 break;
2281
2282 /*
2283 * page.c:new_uue() sets offset to the 'begin ...' line
2284 * -> skip over the first line in uuencoded parts
2285 */
2286 if (part->encoding == ENCODING_UUE && i == 0) {
2287 ++line_count;
2288 continue;
2289 }
2290
2291 if (what != PIPE_RAW) {
2292 switch (part->encoding) {
2293 case ENCODING_QP:
2294 case ENCODING_BASE64:
2295 #ifdef CHARSET_CONVERSION
2296 memset(buf2, '\0', sizeof(buf2));
2297 #endif /* CHARSET_CONVERSION */
2298 if ((count = mmdecode(buf, part->encoding == ENCODING_QP ? 'q' : 'b', '\0', buf2)) > 0) {
2299 #ifdef CHARSET_CONVERSION
2300 if (what != SAVE && what != SAVE_TAGGED && !strncmp(content_types[part->type], "text", 4)) {
2301 line_len = (size_t) count;
2302 conv_buf = my_strdup(buf2);
2303 network_charset = get_param(part->params, "charset");
2304 process_charsets(&conv_buf, &line_len, network_charset ? network_charset : "US-ASCII", tinrc.mm_local_charset, FALSE);
2305 strncpy(buf2, conv_buf, sizeof(buf2) - 1);
2306 count = (int) strlen(buf2);
2307 free(conv_buf);
2308 }
2309 #endif /* CHARSET_CONVERSION */
2310 fwrite(buf2, (size_t) count, 1, outfile);
2311 }
2312 break;
2313
2314 case ENCODING_UUE:
2315 /* TODO: if postproc, don't decode these since the traditional uudecoder will get them */
2316 /*
2317 * x-uuencode attachments have all the header info etc which we must ignore
2318 */
2319 if (strncmp(buf, "begin ", 6) != 0 && strncmp(buf, "end\n", 4) != 0 && buf[0] != '\n')
2320 uudecode_line(buf, outfile);
2321 break;
2322
2323 default:
2324 #ifdef CHARSET_CONVERSION
2325 if (what != SAVE && what != SAVE_TAGGED && !strncmp(content_types[part->type], "text", 4)) {
2326 conv_buf = my_strdup(buf);
2327 line_len = strlen(conv_buf);
2328 network_charset = get_param(part->params, "charset");
2329 process_charsets(&conv_buf, &line_len, network_charset ? network_charset : "US-ASCII", tinrc.mm_local_charset, FALSE);
2330 strncpy(buf, conv_buf, sizeof(buf) - 1);
2331 free(conv_buf);
2332 }
2333 #endif /* CHARSET_CONVERSION */
2334 fputs(buf, outfile);
2335 }
2336 } else
2337 fputs(buf, outfile);
2338 }
2339
2340 fclose(outfile);
2341
2342 switch (what) {
2343 case VIEW:
2344 start_viewer(part, savepath);
2345 break;
2346
2347 #ifndef DONT_HAVE_PIPING
2348 case PIPE:
2349 case PIPE_RAW:
2350 pipe_part(savepath);
2351 break;
2352 #endif /* !DONT_HAVE_PIPING */
2353
2354 default:
2355 break;
2356 }
2357 }
2358
2359
2360 #ifndef DONT_HAVE_PIPING
2361 static void
2362 pipe_part(
2363 const char *savepath)
2364 {
2365 FILE *fp, *pipe_fp;
2366 char *prompt;
2367
2368 prompt = fmt_string(_(txt_pipe_to_command), (size_t) cCOLS - (strlen(_(txt_pipe_to_command)) + 30), tinrc.default_pipe_command);
2369 if (!(prompt_string_default(prompt, tinrc.default_pipe_command, _(txt_no_command), HIST_PIPE_COMMAND))) {
2370 free(prompt);
2371 return;
2372 }
2373 free(prompt);
2374 if ((fp = fopen(savepath, "r")) == NULL)
2375 /* TODO: error message? */
2376 return;
2377 EndWin();
2378 Raw(FALSE);
2379 fflush(stdout);
2380 set_signal_catcher(FALSE);
2381 if ((pipe_fp = popen(tinrc.default_pipe_command, "w")) == NULL) {
2382 perror_message(_(txt_command_failed), tinrc.default_pipe_command);
2383 set_signal_catcher(TRUE);
2384 Raw(TRUE);
2385 InitWin();
2386 fclose(fp);
2387 return;
2388 }
2389 copy_fp(fp, pipe_fp);
2390 if (errno == EPIPE)
2391 perror_message(_(txt_command_failed), tinrc.default_pipe_command);
2392 fflush(pipe_fp);
2393 (void) pclose(pipe_fp);
2394 set_signal_catcher(TRUE);
2395 fclose(fp);
2396 # ifdef USE_CURSES
2397 Raw(TRUE);
2398 InitWin();
2399 # endif /* USE_CURSES */
2400 prompt_continue();
2401 # ifndef USE_CURSES
2402 Raw(TRUE);
2403 InitWin();
2404 # endif /* !USE_CURSES */
2405 }
2406 #endif /* !DONT_HAVE_PIPING */