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