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