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