Don't display multipart/parallel in parallel. Always serialize displaying.
[mmh] / uip / mhshow.c
1 /*
2 ** mhshow.c -- display the contents of MIME messages
3 **
4 ** This code is Copyright (c) 2002, by the authors of nmh.  See the
5 ** COPYRIGHT file in the root directory of the nmh distribution for
6 ** complete copyright information.
7 */
8
9 #include <h/mh.h>
10 #include <fcntl.h>
11 #include <h/signals.h>
12 #include <h/md5.h>
13 #include <errno.h>
14 #include <signal.h>
15 #include <h/tws.h>
16 #include <h/mime.h>
17 #include <h/mhparse.h>
18 #include <h/mhcachesbr.h>
19 #include <h/utils.h>
20
21 static struct swit switches[] = {
22 #define CHECKSW  0
23         { "check", 0 },
24 #define NCHECKSW  1
25         { "nocheck", 0 },
26 #define VERBSW  2
27         { "verbose", 0 },
28 #define NVERBSW  3
29         { "noverbose", 0 },
30 #define FILESW  4  /* interface from show */
31         { "file file", 0 },
32 #define FORMSW  5
33         { "form formfile", 0 },
34 #define PARTSW  6
35         { "part number", 0 },
36 #define TYPESW  7
37         { "type content", 0 },
38 #define RCACHESW  8
39         { "rcache policy", 0 },
40 #define WCACHESW  9
41         { "wcache policy", 0 },
42 #define VERSIONSW  10
43         { "version", 0 },
44 #define HELPSW  11
45         { "help", 0 },
46 #define DEBUGSW  12
47         { "debug", -5 },
48         { NULL, 0 }
49 };
50
51
52 /* mhparse.c */
53 extern char *tmp;  /* directory to place temp files */
54
55 /* mhcachesbr.c */
56 extern int rcachesw;
57 extern int wcachesw;
58 extern char *cache_public;
59 extern char *cache_private;
60
61 /* mhshowsbr.c */
62 extern int nolist;
63 extern char *formsw;
64
65 /* mhmisc.c */
66 extern int npart;
67 extern int ntype;
68 extern char *parts[NPARTS + 1];
69 extern char *types[NTYPES + 1];
70 extern int userrs;
71
72 int debugsw = 0;
73 int verbosw = 0;
74
75 #define quitser pipeser
76
77 /* mhparse.c */
78 CT parse_mime(char *);
79
80 /* mhmisc.c */
81 int part_ok(CT, int);
82 int type_ok(CT, int);
83 void set_endian(void);
84 void flush_errors(void);
85
86 /* mhshowsbr.c */
87 void show_all_messages(CT *);
88
89 /* mhfree.c */
90 void free_content(CT);
91 extern CT *cts;
92 void freects_done(int) NORETURN;
93
94 /*
95 ** static prototypes
96 */
97 static void pipeser(int);
98
99
100 int
101 main(int argc, char **argv)
102 {
103         int msgnum, *icachesw;
104         char *cp, *file = NULL, *folder = NULL;
105         char *maildir, buf[100], **argp;
106         char **arguments;
107         struct msgs_array msgs = { 0, 0, NULL };
108         struct msgs *mp = NULL;
109         CT ct, *ctp;
110         FILE *fp;
111
112         done=freects_done;
113
114 #ifdef LOCALE
115         setlocale(LC_ALL, "");
116 #endif
117         invo_name = mhbasename(argv[0]);
118
119         /* read user profile/context */
120         context_read();
121
122         arguments = getarguments(invo_name, argc, argv, 1);
123         argp = arguments;
124
125         /*
126         ** Parse arguments
127         */
128         while ((cp = *argp++)) {
129                 if (*cp == '-') {
130                         switch (smatch(++cp, switches)) {
131                         case AMBIGSW:
132                                 ambigsw(cp, switches);
133                                 done(1);
134                         case UNKWNSW:
135                                 adios(NULL, "-%s unknown", cp);
136
137                         case HELPSW:
138                                 snprintf(buf, sizeof(buf), "%s [+folder] [msgs] [switches]", invo_name);
139                                 print_help(buf, switches, 1);
140                                 done(1);
141                         case VERSIONSW:
142                                 print_version(invo_name);
143                                 done(1);
144
145                         case RCACHESW:
146                                 icachesw = &rcachesw;
147                                 goto do_cache;
148                         case WCACHESW:
149                                 icachesw = &wcachesw;
150 do_cache:
151                                 if (!(cp = *argp++) || *cp == '-')
152                                         adios(NULL, "missing argument to %s",
153                                                         argp[-2]);
154                                 switch (*icachesw = smatch(cp, caches)) {
155                                 case AMBIGSW:
156                                         ambigsw(cp, caches);
157                                         done(1);
158                                 case UNKWNSW:
159                                         adios(NULL, "%s unknown", cp);
160                                 default:
161                                         break;
162                                 }
163                                 continue;
164
165                         case CHECKSW:
166                                 checksw++;
167                                 continue;
168                         case NCHECKSW:
169                                 checksw = 0;
170                                 continue;
171
172                         case PARTSW:
173                                 if (!(cp = *argp++) || *cp == '-')
174                                         adios(NULL, "missing argument to %s",
175                                                         argp[-2]);
176                                 if (npart >= NPARTS)
177                                         adios(NULL, "too many parts (starting with %s), %d max", cp, NPARTS);
178                                 parts[npart++] = cp;
179                                 continue;
180
181                         case TYPESW:
182                                 if (!(cp = *argp++) || *cp == '-')
183                                         adios(NULL, "missing argument to %s",
184                                                         argp[-2]);
185                                 if (ntype >= NTYPES)
186                                         adios(NULL, "too many types (starting with %s), %d max", cp, NTYPES);
187                                 types[ntype++] = cp;
188                                 continue;
189
190                         case FILESW:
191                                 if (!(cp = *argp++) || (*cp == '-' && cp[1]))
192                                         adios(NULL, "missing argument to %s",
193                                                         argp[-2]);
194                                 file = *cp == '-' ? cp : getcpy(expanddir(cp));
195                                 continue;
196
197                         case FORMSW:
198                                 if (!(cp = *argp++) || *cp == '-')
199                                         adios(NULL, "missing argument to %s",
200                                                         argp[-2]);
201                                 if (formsw)
202                                         free(formsw);
203                                 formsw = getcpy(etcpath(cp));
204                                 continue;
205
206                         case VERBSW:
207                                 verbosw = 1;
208                                 continue;
209                         case NVERBSW:
210                                 verbosw = 0;
211                                 continue;
212                         case DEBUGSW:
213                                 debugsw = 1;
214                                 continue;
215                         }
216                 }
217                 if (*cp == '+' || *cp == '@') {
218                         if (folder)
219                                 adios(NULL, "only one folder at a time!");
220                         else
221                                 folder = getcpy(expandfol(cp));
222                 } else
223                         app_msgarg(&msgs, cp);
224         }
225
226         /* null terminate the list of acceptable parts/types */
227         parts[npart] = NULL;
228         types[ntype] = NULL;
229
230         set_endian();
231
232         if ((cp = getenv("MM_NOASK")) && strcmp(cp, "1")==0) {
233                 nolist  = 1;
234         }
235
236         /*
237         ** Check if we've specified an additional profile
238         */
239         if ((cp = getenv("MHSHOW"))) {
240                 if ((fp = fopen(cp, "r"))) {
241                         readconfig((struct node **) 0, fp, cp, 0);
242                         fclose(fp);
243                 } else {
244                         admonish("", "unable to read $MHSHOW profile (%s)",
245                                         cp);
246                 }
247         }
248
249         /*
250         ** Read the standard profile setup
251         */
252         if ((fp = fopen(cp = etcpath("mhn.defaults"), "r"))) {
253                 readconfig((struct node **) 0, fp, cp, 0);
254                 fclose(fp);
255         }
256
257         /* Check for public cache location */
258         if ((cache_public = context_find(nmhcache)) && *cache_public != '/')
259                 cache_public = NULL;
260
261         /* Check for private cache location */
262         if (!(cache_private = context_find(nmhprivcache)))
263                 cache_private = ".cache";
264         cache_private = getcpy(toabsdir(cache_private));
265
266         /*
267         ** Check for storage directory.  If specified,
268         ** then store temporary files there.  Else we
269         ** store them in standard nmh directory.
270         */
271         if ((cp = context_find(nmhstorage)) && *cp)
272                 tmp = concat(cp, "/", invo_name, NULL);
273         else
274                 tmp = getcpy(toabsdir(invo_name));
275
276         if (file && msgs.size)
277                 adios(NULL, "cannot specify msg and file at same time!");
278
279         /*
280         ** check if message is coming from file
281         */
282         if (file) {
283                 if (!(cts = (CT *) calloc((size_t) 2, sizeof(*cts))))
284                         adios(NULL, "out of memory");
285                 ctp = cts;
286
287                 if ((ct = parse_mime(file)))
288                         *ctp++ = ct;
289         } else {
290                 /*
291                 ** message(s) are coming from a folder
292                 */
293                 if (!msgs.size)
294                         app_msgarg(&msgs, seq_cur);
295                 if (!folder)
296                         folder = getcurfol();
297                 maildir = toabsdir(folder);
298
299                 if (chdir(maildir) == NOTOK)
300                         adios(maildir, "unable to change directory to");
301
302                 /* read folder and create message structure */
303                 if (!(mp = folder_read(folder)))
304                         adios(NULL, "unable to read folder %s", folder);
305
306                 /* check for empty folder */
307                 if (mp->nummsg == 0)
308                         adios(NULL, "no messages in %s", folder);
309
310                 /* parse all the message ranges/sequences and set SELECTED */
311                 for (msgnum = 0; msgnum < msgs.size; msgnum++)
312                         if (!m_convert(mp, msgs.msgs[msgnum]))
313                                 done(1);
314
315                 /*
316                 ** Set the SELECT_UNSEEN bit for all the SELECTED messages,
317                 ** since we will use that as a tag to know which messages
318                 ** to remove from the "unseen" sequence.
319                 */
320                 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++)
321                         if (is_selected(mp, msgnum))
322                                 set_unseen(mp, msgnum);
323
324                 seq_setprev(mp);  /* set the Previous-Sequence */
325                 seq_setunseen(mp, 0);  /* unset unseen seqs for shown msgs */
326
327                 if (!(cts = (CT *) calloc((size_t) (mp->numsel + 1),
328                                 sizeof(*cts))))
329                         adios(NULL, "out of memory");
330                 ctp = cts;
331
332                 /*
333                 ** Parse all the SELECTED messages.
334                 */
335                 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
336                         if (is_selected(mp, msgnum)) {
337                                 char *msgnam;
338
339                                 msgnam = m_name(msgnum);
340                                 if ((ct = parse_mime(msgnam)))
341                                         *ctp++ = ct;
342                         }
343                 }
344         }
345
346         if (!*cts)
347                 done(1);
348
349         userrs = 1;
350         SIGNAL(SIGQUIT, quitser);
351         SIGNAL(SIGPIPE, pipeser);
352
353         /*
354         ** Get the associated umask for the relevant contents.
355         */
356         for (ctp = cts; *ctp; ctp++) {
357                 struct stat st;
358
359                 ct = *ctp;
360                 if (type_ok(ct, 1) && !ct->c_umask) {
361                         if (stat(ct->c_file, &st) != NOTOK)
362                                 ct->c_umask = ~(st.st_mode & 0777);
363                         else
364                                 ct->c_umask = ~m_gmprot();
365                 }
366         }
367
368         /*
369         ** Show the message content
370         */
371         show_all_messages(cts);
372
373         /* Now free all the structures for the content */
374         for (ctp = cts; *ctp; ctp++)
375                 free_content(*ctp);
376
377         free((char *) cts);
378         cts = NULL;
379
380         /* If reading from a folder, do some updating */
381         if (mp) {
382                 context_replace(curfolder, folder); /* update current folder */
383                 seq_setcur(mp, mp->hghsel);        /* update current message */
384                 seq_save(mp);                      /* synchronize sequences */
385                 context_save();                    /* save the context file */
386         }
387
388         done(0);
389         return 1;
390 }
391
392
393 static void
394 pipeser(int i)
395 {
396         if (i == SIGQUIT) {
397                 unlink("core");
398                 fflush(stdout);
399                 fprintf(stderr, "\n");
400                 fflush(stderr);
401         }
402
403         done(1);
404         /* NOTREACHED */
405 }