Rearranged whitespace (and comments) in all the code!
[mmh] / uip / show.c
1 /*
2  * show.c -- show/list 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 <h/mime.h>
11 #include <h/utils.h>
12
13 static struct swit switches[] = {
14 #define CHECKMIMESW  0
15         { "checkmime", 0 },
16 #define NOCHECKMIMESW  1
17         { "nocheckmime", 0 },
18 #define HEADSW  2
19         { "header", 0 },
20 #define NHEADSW  3
21         { "noheader", 0 },
22 #define FORMSW  4
23         { "form formfile", 0 },
24 #define PROGSW  5
25         { "moreproc program", 0 },
26 #define NPROGSW  6
27         { "nomoreproc", 0 },
28 #define LENSW  7
29         { "length lines", 0 },
30 #define WIDTHSW  8
31         { "width columns", 0 },
32 #define SHOWSW  9
33         { "showproc program", 0 },
34 #define SHOWMIMESW  10
35         { "showmimeproc program", 0 },
36 #define NSHOWSW  11
37         { "noshowproc", 0 },
38 #define DRFTSW  12
39         { "draft", 0 },
40 #define FILESW  13
41         { "file file", -4 },  /* interface from showfile */
42 #define VERSIONSW  14
43         { "version", 0 },
44 #define HELPSW  15
45         { "help", 0 },
46         { NULL, 0 }
47 };
48
49 /*
50  * static prototypes
51  */
52 static int is_nontext(char *);
53
54 /* prototype from mhlsbr.c */
55 int mhl (int, char **);
56
57 #define SHOW  0
58 #define NEXT  1
59 #define PREV  2
60
61
62 int
63 main (int argc, char **argv)
64 {
65         int draftsw = 0, headersw = 1, msgp = 0;
66         int nshow = 0, checkmime = 1, mime;
67         int vecp = 1, procp = 1, isdf = 0, mode = SHOW, msgnum;
68         char *cp, *maildir, *file = NULL, *folder = NULL, *proc;
69         char buf[BUFSIZ], **argp, **arguments;
70         char *msgs[MAXARGS], *vec[MAXARGS];
71         struct msgs *mp = NULL;
72
73 #ifdef LOCALE
74         setlocale(LC_ALL, "");
75 #endif
76         invo_name = r1bindex (argv[0], '/');
77
78         /* read user profile/context */
79         context_read();
80
81         if (!mh_strcasecmp (invo_name, "next")) {
82                 mode = NEXT;
83         } else if (!mh_strcasecmp (invo_name, "prev")) {
84                 mode = PREV;
85         }
86         arguments = getarguments (invo_name, argc, argv, 1);
87         argp = arguments;
88
89         while ((cp = *argp++)) {
90                 if (*cp == '-') {
91                         switch (smatch (++cp, switches)) {
92                                 case AMBIGSW:
93                                         ambigsw (cp, switches);
94                                         done (1);
95                                 case UNKWNSW:
96                                 case NPROGSW:
97                                         vec[vecp++] = --cp;
98                                         continue;
99
100                                 case HELPSW:
101                                         snprintf (buf, sizeof(buf),
102                                                 "%s [+folder] %s[switches] [switches for showproc]",
103                                                 invo_name, mode == SHOW ? "[msgs] ": "");
104                                         print_help (buf, switches, 1);
105                                         done (1);
106                                 case VERSIONSW:
107                                         print_version(invo_name);
108                                         done (1);
109
110                                 case DRFTSW:
111                                         if (file)
112                                                 adios (NULL, "only one file at a time!");
113                                         draftsw++;
114                                         if (mode == SHOW)
115                                                 continue;
116 usage:
117                                         adios (NULL,
118                                                         "usage: %s [+folder] [switches] [switches for showproc]",
119                                                         invo_name);
120                                 case FILESW:
121                                         if (mode != SHOW)
122                                                 goto usage;
123                                         if (draftsw || file)
124                                                 adios (NULL, "only one file at a time!");
125                                         if (!(cp = *argp++) || *cp == '-')
126                                                 adios (NULL, "missing argument to %s", argp[-2]);
127                                         file = path (cp, TFILE);
128                                         continue;
129
130                                 case HEADSW:
131                                         headersw++;
132                                         continue;
133                                 case NHEADSW:
134                                         headersw = 0;
135                                         continue;
136
137                                 case FORMSW:
138                                         vec[vecp++] = --cp;
139                                         if (!(cp = *argp++) || *cp == '-')
140                                                 adios (NULL, "missing argument to %s", argp[-2]);
141                                         vec[vecp++] = getcpy (etcpath(cp));
142                                         continue;
143
144                                 case PROGSW:
145                                 case LENSW:
146                                 case WIDTHSW:
147                                         vec[vecp++] = --cp;
148                                         if (!(cp = *argp++) || *cp == '-')
149                                                 adios (NULL, "missing argument to %s", argp[-2]);
150                                         vec[vecp++] = cp;
151                                         continue;
152
153                                 case SHOWSW:
154                                         if (!(showproc = *argp++) || *showproc == '-')
155                                                 adios (NULL, "missing argument to %s", argp[-2]);
156                                         nshow = 0;
157                                         continue;
158                                 case NSHOWSW:
159                                         nshow++;
160                                         continue;
161
162                                 case SHOWMIMESW:
163                                         if (!(showmimeproc = *argp++) || *showmimeproc == '-')
164                                                 adios (NULL, "missing argument to %s", argp[-2]);
165                                         nshow = 0;
166                                         continue;
167                                 case CHECKMIMESW:
168                                         checkmime++;
169                                         continue;
170                                 case NOCHECKMIMESW:
171                                         checkmime = 0;
172                                         continue;
173                         }
174                 }
175                 if (*cp == '+' || *cp == '@') {
176                         if (folder)
177                                 adios (NULL, "only one folder at a time!");
178                         else
179                                 folder = pluspath (cp);
180                 } else {
181                         if (mode != SHOW)
182                                 goto usage;
183                         else
184                                 msgs[msgp++] = cp;
185                 }
186         }
187         procp = vecp;
188
189         if (!context_find ("path"))
190                 free (path ("./", TFOLDER));
191
192         if (draftsw || file) {
193                 if (msgp)
194                         adios (NULL, "only one file at a time!");
195                 vec[vecp++] = draftsw
196                         ? getcpy (m_draft (folder, msgp ? msgs[0] : NULL, 1, &isdf))
197                         : file;
198                 goto go_to_it;
199         }
200
201 #ifdef WHATNOW
202         if (!msgp && !folder && mode == SHOW && (cp = getenv ("mhdraft")) && *cp) {
203                 draftsw++;
204                 vec[vecp++] = cp;
205                 goto go_to_it;
206         }
207 #endif /* WHATNOW */
208
209         if (!msgp) {
210                 switch (mode) {
211                         case NEXT:
212                                 msgs[msgp++] = "next";
213                                 break;
214                         case PREV:
215                                 msgs[msgp++] = "prev";
216                                 break;
217                         default:
218                                 msgs[msgp++] = "cur";
219                                 break;
220                 }
221         }
222
223         if (!folder)
224                 folder = getfolder (1);
225         maildir = m_maildir (folder);
226
227         if (chdir (maildir) == NOTOK)
228                 adios (maildir, "unable to change directory to");
229
230         /* read folder and create message structure */
231         if (!(mp = folder_read (folder)))
232                 adios (NULL, "unable to read folder %s", folder);
233
234         /* check for empty folder */
235         if (mp->nummsg == 0)
236                 adios (NULL, "no messages in %s", folder);
237
238         /* parse all the message ranges/sequences and set SELECTED */
239         for (msgnum = 0; msgnum < msgp; msgnum++)
240                 if (!m_convert (mp, msgs[msgnum]))
241                         done (1);
242
243         /*
244          * Set the SELECT_UNSEEN bit for all the SELECTED messages,
245          * since we will use that as a tag to know which messages
246          * to remove from the "unseen" sequence.
247          */
248         for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++)
249                 if (is_selected(mp, msgnum))
250                         set_unseen (mp, msgnum);
251
252         seq_setprev (mp);  /* set the Previous-Sequence */
253         seq_setunseen (mp, 1);  /* unset the Unseen-Sequence */
254
255         if (mp->numsel > MAXARGS - 2)
256                 adios (NULL, "more than %d messages for show exec", MAXARGS - 2);
257
258         for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++)
259                 if (is_selected(mp, msgnum))
260                         vec[vecp++] = getcpy (m_name (msgnum));
261
262         seq_setcur (mp, mp->hghsel);  /* update current message  */
263         seq_save (mp);  /* synchronize sequences   */
264         context_replace (pfolder, folder);  /* update current folder   */
265         context_save ();  /* save the context file   */
266
267         if (headersw && vecp == 2)
268                 printf ("(Message %s:%s)\n", folder, vec[1]);
269
270 go_to_it: ;
271         fflush (stdout);
272
273         vec[vecp] = NULL;
274
275         /*
276          * Decide which "proc" to use
277          */
278         mime = 0;
279         if (nshow) {
280                 proc = catproc;
281         } else {
282                 /* check if any messages are non-text MIME messages */
283                 if (checkmime && !getenv ("NOMHNPROC")) {
284                         if (!draftsw && !file) {
285                                 /* loop through selected messages and check for MIME */
286                                 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++)
287                                         if (is_selected (mp, msgnum) && is_nontext (m_name (msgnum))) {
288                                                 mime = 1;
289                                                 break;
290                                         }
291                         } else {
292                                 /* check the file or draft for MIME */
293                                 if (is_nontext (vec[vecp - 1]))
294                                         mime = 1;
295                         }
296                 }
297
298                 /* Set the "proc" */
299                 if (mime)
300                         proc = showmimeproc;
301                 else
302                         proc = showproc;
303         }
304
305         if (folder && !draftsw && !file)
306                 m_putenv ("mhfolder", folder);
307
308         /*
309          * For backward compatibility, if the "proc" is mhn,
310          * then add "-show" option.  Add "-file" if showing
311          * file or draft.
312          */
313         if (strcmp (r1bindex (proc, '/'), "mhn") == 0) {
314                 if (draftsw || file) {
315                         vec[vecp] = vec[vecp - 1];
316                         vec[vecp - 1] = "-file";
317                         vecp++;
318                 }
319                 vec[vecp++] = "-show";
320                 vec[vecp] = NULL;
321         }
322
323         /* If the "proc" is "mhshow", add "-file" if showing file or draft.
324          */
325         if (strcmp (r1bindex (proc, '/'), "mhshow") == 0 && (draftsw || file) ) {
326            vec[vecp] = vec[vecp - 1];
327            vec[vecp - 1] = "-file";
328            vec[++vecp] = NULL;
329         }
330
331         /*
332          * If "proc" is mhl, then run it internally
333          * rather than exec'ing it.
334          */
335         if (strcmp (r1bindex (proc, '/'), "mhl") == 0) {
336                 vec[0] = "mhl";
337                 mhl (vecp, vec);
338                 done (0);
339         }
340
341         /*
342          * If you are not using a nmh command as your "proc", then
343          * add the path to the message names.  Currently, we are just
344          * checking for mhn here, since we've already taken care of mhl.
345          */
346         if (!strcmp (r1bindex (proc, '/'), "mhl")
347                         && !draftsw
348                         && !file
349                         && chdir (maildir = concat (m_maildir (""), "/", NULL)) != NOTOK) {
350                 mp->foldpath = concat (mp->foldpath, "/", NULL);
351                 cp = ssequal (maildir, mp->foldpath)
352                         ? mp->foldpath + strlen (maildir)
353                         : mp->foldpath;
354                 for (msgnum = procp; msgnum < vecp; msgnum++)
355                         vec[msgnum] = concat (cp, vec[msgnum], NULL);
356         }
357
358         vec[0] = r1bindex (proc, '/');
359         execvp (proc, vec);
360         adios (proc, "unable to exec");
361         return 0;  /* dead code to satisfy the compiler */
362 }
363
364 /*
365  * Check if a message or file contains any non-text parts
366  */
367 static int
368 is_nontext (char *msgnam)
369 {
370         int result, state;
371         unsigned char *bp, *dp;
372         char *cp;
373         char buf[BUFSIZ], name[NAMESZ];
374         FILE *fp;
375
376         if ((fp = fopen (msgnam, "r")) == NULL)
377                 return 0;
378
379         for (state = FLD;;) {
380                 switch (state = m_getfld (state, name, buf, sizeof(buf), fp)) {
381                 case FLD:
382                 case FLDPLUS:
383                 case FLDEOF:
384                         /*
385                          * Check Content-Type field
386                          */
387                         if (!mh_strcasecmp (name, TYPE_FIELD)) {
388                                 int passno;
389                                 char c;
390
391                                 cp = add (buf, NULL);
392                                 while (state == FLDPLUS) {
393                                         state = m_getfld (state, name, buf, sizeof(buf), fp);
394                                         cp = add (buf, cp);
395                                 }
396                                 bp = cp;
397                                 passno = 1;
398
399 again:
400                                 for (; isspace (*bp); bp++)
401                                         continue;
402                                 if (*bp == '(') {
403                                         int i;
404
405                                         for (bp++, i = 0;;) {
406                                                 switch (*bp++) {
407                                                 case '\0':
408 invalid:
409                                                         result = 0;
410                                                         goto out;
411                                                 case '\\':
412                                                         if (*bp++ == '\0')
413                                                                 goto invalid;
414                                                         continue;
415                                                 case '(':
416                                                         i++;
417                                                         /* and fall... */
418                                                 default:
419                                                         continue;
420                                                 case ')':
421                                                         if (--i < 0)
422                                                                 break;
423                                                         continue;
424                                                 }
425                                                 break;
426                                         }
427                                 }
428                                 if (passno == 2) {
429                                         if (*bp != '/')
430                                                 goto invalid;
431                                         bp++;
432                                         passno = 3;
433                                         goto again;
434                                 }
435                                 for (dp = bp; istoken (*dp); dp++)
436                                         continue;
437                                 c = *dp;
438                                 *dp = '\0';
439                                 if (!*bp)
440                                         goto invalid;
441                                 if (passno > 1) {
442                                         if ((result = (mh_strcasecmp (bp, "plain") != 0)))
443                                                 goto out;
444                                         *dp = c;
445                                         for (dp++; isspace (*dp); dp++)
446                                                 continue;
447                                         if (*dp) {
448                                                 if ((result = !uprf (dp, "charset")))
449                                                         goto out;
450                                                 dp += sizeof("charset") - 1;
451                                                 while (isspace (*dp))
452                                                         dp++;
453                                                 if (*dp++ != '=')
454                                                         goto invalid;
455                                                 while (isspace (*dp))
456                                                         dp++;
457                                                 if (*dp == '"') {
458                                                         if ((bp = strchr(++dp, '"')))
459                                                                 *bp = '\0';
460                                                 } else {
461                                                         for (bp = dp; *bp; bp++)
462                                                                 if (!istoken (*bp)) {
463                                                                         *bp = '\0';
464                                                                         break;
465                                                                 }
466                                                 }
467                                         } else {
468                                                 /* Default character set */
469                                                 dp = "US-ASCII";
470                                         }
471                                         /* Check the character set */
472                                         result = !check_charset (dp, strlen (dp));
473                                 } else {
474                                         if (!(result = (mh_strcasecmp (bp, "text") != 0))) {
475                                                 *dp = c;
476                                                 bp = dp;
477                                                 passno = 2;
478                                                 goto again;
479                                         }
480                                 }
481 out:
482                                 free (cp);
483                                 if (result) {
484                                         fclose (fp);
485                                         return result;
486                                 }
487                                 break;
488                         }
489
490                         /*
491                          * Check Content-Transfer-Encoding field
492                          */
493                         if (!mh_strcasecmp (name, ENCODING_FIELD)) {
494                                 cp = add (buf, NULL);
495                                 while (state == FLDPLUS) {
496                                         state = m_getfld (state, name, buf, sizeof(buf), fp);
497                                         cp = add (buf, cp);
498                                 }
499                                 for (bp = cp; isspace (*bp); bp++)
500                                         continue;
501                                 for (dp = bp; istoken (*dp); dp++)
502                                         continue;
503                                 *dp = '\0';
504                                 result = (mh_strcasecmp (bp, "7bit")
505                                            && mh_strcasecmp (bp, "8bit")
506                                            && mh_strcasecmp (bp, "binary"));
507
508                                 free (cp);
509                                 if (result) {
510                                         fclose (fp);
511                                         return result;
512                                 }
513                                 break;
514                         }
515
516                         /*
517                          * Just skip the rest of this header
518                          * field and go to next one.
519                          */
520                         while (state == FLDPLUS)
521                                 state = m_getfld (state, name, buf, sizeof(buf), fp);
522                         break;
523
524                         /*
525                          * We've passed the message header,
526                          * so message is just text.
527                          */
528                 default:
529                         fclose (fp);
530                         return 0;
531                 }
532         }
533 }