tin  2.4.5
About: TIN is a threaded NNTP and spool based UseNet newsreader.
  Fossies Dox: tin-2.4.5.tar.xz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

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