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