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