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