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