README: mention *crawl* branch
[s-mailx.git] / cmd1.c
blob7f77605756764bfc293cf36f837718fdd99d42a5
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ User commands.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2013 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
40 #include "rcv.h"
42 #ifdef HAVE_WCWIDTH
43 # include <wchar.h>
44 #endif
46 #include "extern.h"
49 * Print the current active headings.
50 * Don't change dot if invoker didn't give an argument.
53 static int screen;
55 /* Prepare and print "[Message: xy]:" intro */
56 static void _show_msg_overview(struct message *mp, int msg_no, FILE *obuf);
58 static void onpipe(int signo);
59 static int dispc(struct message *mp, const char *a);
60 static int scroll1(char *arg, int onlynew);
62 static void _parse_head(struct message *mp, char date[FROM_DATEBUF]);
63 static void hprf(const char *fmt, int mesg, FILE *f, int threaded,
64 const char *attrlist);
65 static int putindent(FILE *fp, struct message *mp, int maxwidth);
66 static int type1(int *msgvec, int doign, int page, int pipe, int decode,
67 char *cmd, off_t *tstats);
68 static int pipe1(char *str, int doign);
70 static void
71 _show_msg_overview(struct message *mp, int msg_no, FILE *obuf)
73 fprintf(obuf, tr(17, "[-- Message %2d -- %lu lines, %lu bytes --]:\n"),
74 msg_no, (ul_it)mp->m_lines, (ul_it)mp->m_size);
77 int
78 ccmdnotsupp(void *v)
80 (void)v;
81 fprintf(stderr, tr(10, "The requested feature is not compiled in\n"));
82 return (1);
85 char const *
86 get_pager(void)
88 char const *cp;
90 cp = value("PAGER");
91 if (cp == NULL || *cp == '\0')
92 cp = PAGER;
93 return cp;
96 int
97 headers(void *v)
99 int *msgvec = v;
100 int g, k, n, mesg, flag = 0, lastg = 1;
101 struct message *mp, *mq, *lastmq = NULL;
102 int size;
103 enum mflag fl = MNEW|MFLAGGED;
105 time_current_update(&time_current, FAL0);
107 size = screensize();
108 n = msgvec[0]; /* n == {-2, -1, 0}: called from scroll() */
109 if (screen < 0)
110 screen = 0;
111 k = screen * size;
112 if (k >= msgCount)
113 k = msgCount - size;
114 if (k < 0)
115 k = 0;
116 if (mb.mb_threaded == 0) {
117 g = 0;
118 mq = &message[0];
119 for (mp = &message[0]; mp < &message[msgCount]; mp++)
120 if (visible(mp)) {
121 if (g % size == 0)
122 mq = mp;
123 if (mp->m_flag&fl) {
124 lastg = g;
125 lastmq = mq;
127 if ((n > 0 && mp == &message[n-1]) ||
128 (n == 0 && g == k) ||
129 (n == -2 && g == k + size &&
130 lastmq) ||
131 (n < 0 && g >= k &&
132 (mp->m_flag & fl) != 0))
133 break;
134 g++;
136 if (lastmq && (n==-2 || (n==-1 && mp == &message[msgCount]))) {
137 g = lastg;
138 mq = lastmq;
140 screen = g / size;
141 mp = mq;
142 mesg = mp - &message[0];
143 if (dot != &message[n-1]) {
144 for (mq = mp; mq < &message[msgCount]; mq++)
145 if (visible(mq)) {
146 setdot(mq);
147 break;
150 #ifdef USE_IMAP
151 if (mb.mb_type == MB_IMAP)
152 imap_getheaders(mesg+1, mesg + size);
153 #endif
154 for (; mp < &message[msgCount]; mp++) {
155 mesg++;
156 if (!visible(mp))
157 continue;
158 if (flag++ >= size)
159 break;
160 printhead(mesg, stdout, 0);
162 } else { /* threaded */
163 g = 0;
164 mq = threadroot;
165 for (mp = threadroot; mp; mp = next_in_thread(mp))
166 if (visible(mp) && (mp->m_collapsed <= 0 ||
167 mp == &message[n-1])) {
168 if (g % size == 0)
169 mq = mp;
170 if (mp->m_flag&fl) {
171 lastg = g;
172 lastmq = mq;
174 if ((n > 0 && mp == &message[n-1]) ||
175 (n == 0 && g == k) ||
176 (n == -2 && g == k + size &&
177 lastmq) ||
178 (n < 0 && g >= k &&
179 (mp->m_flag & fl) != 0))
180 break;
181 g++;
183 if (lastmq && (n==-2 || (n==-1 && mp==&message[msgCount]))) {
184 g = lastg;
185 mq = lastmq;
187 screen = g / size;
188 mp = mq;
189 if (dot != &message[n-1]) {
190 for (mq = mp; mq; mq = next_in_thread(mq))
191 if (visible(mq) && mq->m_collapsed <= 0) {
192 setdot(mq);
193 break;
196 while (mp) {
197 if (visible(mp) && (mp->m_collapsed <= 0 ||
198 mp == &message[n-1])) {
199 if (flag++ >= size)
200 break;
201 printhead(mp - &message[0] + 1, stdout,
202 mb.mb_threaded);
204 mp = next_in_thread(mp);
207 if (flag == 0) {
208 printf(tr(6, "No more mail.\n"));
209 return(1);
211 return(0);
215 * Scroll to the next/previous screen
218 scroll(void *v)
220 return scroll1(v, 0);
224 Scroll(void *v)
226 return scroll1(v, 1);
229 static int
230 scroll1(char *arg, int onlynew)
232 int size;
233 int cur[1];
235 cur[0] = onlynew ? -1 : 0;
236 size = screensize();
237 switch (*arg) {
238 case '1': case '2': case '3': case '4': case '5':
239 case '6': case '7': case '8': case '9': case '0':
240 screen = atoi(arg);
241 goto scroll_forward;
242 case '\0':
243 screen++;
244 goto scroll_forward;
245 case '$':
246 screen = msgCount / size;
247 goto scroll_forward;
248 case '+':
249 if (arg[1] == '\0')
250 screen++;
251 else
252 screen += atoi(arg + 1);
253 scroll_forward:
254 if (screen * size > msgCount) {
255 screen = msgCount / size;
256 printf(catgets(catd, CATSET, 7,
257 "On last screenful of messages\n"));
259 break;
261 case '-':
262 if (arg[1] == '\0')
263 screen--;
264 else
265 screen -= atoi(arg + 1);
266 if (screen < 0) {
267 screen = 0;
268 printf(catgets(catd, CATSET, 8,
269 "On first screenful of messages\n"));
271 if (cur[0] == -1)
272 cur[0] = -2;
273 break;
275 default:
276 printf(catgets(catd, CATSET, 9,
277 "Unrecognized scrolling command \"%s\"\n"), arg);
278 return(1);
280 return(headers(cur));
284 * Compute screen size.
286 int
287 screensize(void)
289 int s;
290 char *cp;
292 if ((cp = value("screen")) != NULL && (s = atoi(cp)) > 0)
293 return s;
294 return scrnheight - 4;
297 static sigjmp_buf pipejmp;
299 /*ARGSUSED*/
300 static void
301 onpipe(int signo)
303 (void)signo;
304 siglongjmp(pipejmp, 1);
308 * Print out the headlines for each message
309 * in the passed message list.
311 int
312 from(void *v)
314 int *msgvec = v, *ip, n;
315 char *cp;
316 FILE *volatile obuf = stdout;
318 time_current_update(&time_current, FAL0);
320 /* TODO unfixable memory leaks still */
321 if (IS_TTY_SESSION() && (cp = value("crt")) != NULL) {
322 for (n = 0, ip = msgvec; *ip; ip++)
323 n++;
324 if (n > (*cp == '\0' ? screensize() : atoi((char*)cp)) + 3) {
325 char const *p;
326 if (sigsetjmp(pipejmp, 1))
327 goto endpipe;
328 p = get_pager();
329 if ((obuf = Popen(p, "w", NULL, 1)) == NULL) {
330 perror(p);
331 obuf = stdout;
332 cp=NULL;
333 } else
334 safe_signal(SIGPIPE, onpipe);
337 for (ip = msgvec; *ip != 0; ip++)
338 printhead(*ip, obuf, mb.mb_threaded);
339 if (--ip >= msgvec)
340 setdot(&message[*ip - 1]);
341 endpipe:
342 if (obuf != stdout) {
343 safe_signal(SIGPIPE, SIG_IGN);
344 Pclose(obuf, TRU1);
345 safe_signal(SIGPIPE, dflpipe);
347 return(0);
350 static int
351 dispc(struct message *mp, const char *a)
353 int dispc = ' ';
356 * Bletch!
358 if ((mp->m_flag & (MREAD|MNEW)) == MREAD)
359 dispc = a[3];
360 if ((mp->m_flag & (MREAD|MNEW)) == (MREAD|MNEW))
361 dispc = a[2];
362 if (mp->m_flag & MANSWERED)
363 dispc = a[8];
364 if (mp->m_flag & MDRAFTED)
365 dispc = a[9];
366 if ((mp->m_flag & (MREAD|MNEW)) == MNEW)
367 dispc = a[0];
368 if ((mp->m_flag & (MREAD|MNEW)) == 0)
369 dispc = a[1];
370 if (mp->m_flag & MJUNK)
371 dispc = a[13];
372 if (mp->m_flag & MSAVED)
373 dispc = a[4];
374 if (mp->m_flag & MPRESERVE)
375 dispc = a[5];
376 if (mp->m_flag & (MBOX|MBOXED))
377 dispc = a[6];
378 if (mp->m_flag & MFLAGGED)
379 dispc = a[7];
380 if (mp->m_flag & MKILL)
381 dispc = a[10];
382 if (mb.mb_threaded == 1 && mp->m_collapsed > 0)
383 dispc = a[12];
384 if (mb.mb_threaded == 1 && mp->m_collapsed < 0)
385 dispc = a[11];
386 return dispc;
389 static void
390 _parse_head(struct message *mp, char date[FROM_DATEBUF])
392 FILE *ibuf;
393 int hlen;
394 char *hline = NULL;
395 size_t hsize = 0;
397 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) != NULL &&
398 (hlen = readline_restart(ibuf, &hline, &hsize, 0)) > 0)
399 (void)extract_date_from_from_(hline, hlen, date);
400 if (hline != NULL)
401 free(hline);
404 static void
405 hprf(const char *fmt, int mesg, FILE *f, int threaded, const char *attrlist)
407 char datebuf[FROM_DATEBUF], *subjline, *cp;
408 struct str in, out;
409 char const *datefmt, *date, *name, *fp;
410 int B, c, i, n, s, fromlen, subjlen = scrnwidth, isto = 0, isaddr = 0;
411 struct message *mp = &message[mesg - 1];
412 time_t datet = mp->m_time;
414 date = NULL;
415 if ((datefmt = value("datefield")) != NULL) {
416 fp = hfield1("date", mp);/* TODO use m_date field! */
417 if (fp == NULL) {
418 datefmt = NULL;
419 goto jdate_set;
421 datet = rfctime(fp);
422 date = fakedate(datet);
423 fp = value("datefield-markout-older");
424 i = (*datefmt != '\0');
425 if (fp != NULL)
426 i |= (*fp != '\0') ? 2 | 4 : 2;
427 /* May we strftime(3)? */
428 if (i & (1 | 4))
429 memcpy(&time_current.tc_local, localtime(&datet),
430 sizeof time_current.tc_local);
431 if ((i & 2) && (datet > time_current.tc_time ||
432 #define _6M ((DATE_DAYSYEAR / 2) * DATE_SECSDAY)
433 (datet + _6M < time_current.tc_time))) {
434 #undef _6M
435 if ((datefmt = (i & 4) ? fp : NULL) == NULL) {
436 memset(datebuf, ' ', FROM_DATEBUF); /* xxx ur */
437 memcpy(datebuf + 4, date + 4, 7);
438 datebuf[4 + 7] = ' ';
439 memcpy(datebuf + 4 + 7 + 1, date + 20, 4);
440 datebuf[4 + 7 + 1 + 4] = '\0';
441 date = datebuf;
443 } else if ((i & 1) == 0)
444 datefmt = NULL;
445 } else if (datet == (time_t)0 && (mp->m_flag & MNOFROM) == 0) {
446 /* TODO eliminate this path, query the FROM_ date in setptr(),
447 * TODO all other codepaths do so by themselves ALREADY ?????
448 * TODO assert(mp->m_time != 0);, then
449 * TODO ALSO changes behaviour of markout-non-current */
450 _parse_head(mp, datebuf);
451 date = datebuf;
452 } else {
453 jdate_set:
454 date = fakedate(datet);
457 out.s = NULL;
458 out.l = 0;
459 if ((subjline = hfield1("subject", mp)) != NULL) {
460 in.s = subjline;
461 in.l = strlen(subjline);
462 mime_fromhdr(&in, &out, TD_ICONV | TD_ISPR);
463 subjline = out.s;
466 isaddr = 1;
467 name = name1(mp, 0);
468 if (name != NULL && value("showto") && is_myname(skin(name))) {
469 if ((cp = hfield1("to", mp)) != NULL) {
470 name = cp;
471 isto = 1;
474 if (name == NULL) {
475 name = "";
476 isaddr = 0;
478 if (isaddr) {
479 if (value("showname"))
480 name = realname(name);
481 else {
482 name = prstr(skin(name));
486 for (fp = fmt; *fp; fp++) {
487 if (*fp == '%') {
488 if (*++fp == '-') {
489 fp++;
490 } else if (*fp == '+')
491 fp++;
492 while (digitchar(*fp))
493 fp++;
494 if (*fp == '\0')
495 break;
496 } else {
497 #if defined (HAVE_MBTOWC) && defined (HAVE_WCWIDTH)
498 if (mb_cur_max > 1) {
499 wchar_t wc;
500 if ((s = mbtowc(&wc, fp, mb_cur_max)) < 0)
501 n = s = 1;
502 else {
503 if ((n = wcwidth(wc)) < 0)
504 n = 1;
506 } else
507 #endif /* HAVE_MBTOWC && HAVE_WCWIDTH */
509 n = s = 1;
511 subjlen -= n;
512 while (--s > 0)
513 fp++;
517 for (fp = fmt; *fp; fp++) {
518 if ((c = *fp & 0xFF) == '%') {
519 B = 0;
520 n = 0;
521 s = 1;
522 if (*++fp == '-') {
523 s = -1;
524 fp++;
525 } else if (*fp == '+')
526 fp++;
527 if (digitchar(*fp)) {
529 n = 10*n + *fp - '0';
530 while (fp++, digitchar(*fp));
532 if (*fp == '\0')
533 break;
534 n *= s;
535 switch ((c = *fp & 0xFF)) {
536 case '%':
537 goto jputc;
538 case '>':
539 case '<':
540 if (dot != mp)
541 c = ' ';
542 goto jputc;
543 case 'a':
544 c = dispc(mp, attrlist);
545 jputc:
546 n = fprintf(f, "%*c", n, c);
547 if (n >= 0)
548 subjlen -= n;
549 break;
550 case 'm':
551 if (n == 0) {
552 n = 3;
553 if (threaded)
554 for (i=msgCount; i>999; i/=10)
555 n++;
557 subjlen -= fprintf(f, "%*d", n, mesg);
558 break;
559 case 'f':
560 if (n == 0) {
561 n = 18;
562 if (s < 0)
563 n = -n;
565 fromlen = ABS(n);
566 if (isto) /* XXX tr()! */
567 fromlen -= 3;
568 subjlen -= fprintf(f, "%s%s", isto ? "To " : "",
569 colalign(name, fromlen, n));
570 break;
571 case 'd':
572 if (datefmt != NULL) {
573 i = strftime(datebuf, sizeof datebuf,
574 datefmt,
575 &time_current.tc_local);
576 if (i != 0)
577 date = datebuf;
578 else
579 fprintf(stderr, tr(174,
580 "Ignored date format, "
581 "it excesses the "
582 "target buffer "
583 "(%lu bytes)\n"),
584 (ul_it)sizeof datebuf);
585 datefmt = NULL;
587 if (n == 0)
588 n = 16;
589 subjlen -= fprintf(f, "%*.*s", n, n, date);
590 break;
591 case 'l':
592 if (n == 0)
593 n = 4;
594 if (mp->m_xlines)
595 subjlen -= fprintf(f, "%*ld", n,
596 mp->m_xlines);
597 else {
598 n = ABS(n);
599 subjlen -= n;
600 while (n--)
601 putc(' ', f);
603 break;
604 case 'o':
605 if (n == 0)
606 n = -5;
607 subjlen -= fprintf(f, "%*lu", n,
608 (long)mp->m_xsize);
609 break;
610 case 'i':
611 if (threaded)
612 subjlen -= putindent(f, mp,
613 scrnwidth - 60);
614 break;
615 case 'S':
616 B = 1;
617 /*FALLTHRU*/
618 case 's':
619 if (n == 0)
620 n = subjlen - 2;
621 if (n > 0 && s < 0)
622 n = -n;
623 if (B)
624 n -= (n < 0) ? -2 : 2;
625 if (subjline != NULL && n != 0) {
626 /* pretty pathetic */
627 fprintf(f, B ? "\"%s\"" : "%s",
628 colalign(subjline, ABS(n), n));
630 break;
631 case 'U':
632 #ifdef USE_IMAP
633 if (n == 0)
634 n = 9;
635 subjlen -= fprintf(f, "%*lu", n, mp->m_uid);
636 break;
637 #else
638 c = '?';
639 goto jputc;
640 #endif
641 case 'e':
642 if (n == 0)
643 n = 2;
644 subjlen -= fprintf(f, "%*u", n, threaded == 1 ?
645 mp->m_level : 0);
646 break;
647 case 't':
648 if (n == 0) {
649 n = 3;
650 if (threaded)
651 for (i=msgCount; i>999; i/=10)
652 n++;
654 subjlen -= fprintf(f, "%*ld", n, threaded ?
655 mp->m_threadpos : mesg);
656 break;
657 case 'c':
658 #ifdef USE_SCORE
659 if (n == 0)
660 n = 6;
661 subjlen -= fprintf(f, "%*g", n, mp->m_score);
662 break;
663 #else
664 c = '?';
665 goto jputc;
666 #endif
668 } else
669 putc(c, f);
671 putc('\n', f);
673 if (out.s)
674 free(out.s);
678 * Print out the indenting in threaded display.
680 static int
681 putindent(FILE *fp, struct message *mp, int maxwidth)
683 struct message *mq;
684 int indent, i;
685 int *us;
686 char *cs;
687 int important = MNEW|MFLAGGED;
689 if (mp->m_level == 0)
690 return 0;
691 cs = ac_alloc(mp->m_level);
692 us = ac_alloc(mp->m_level * sizeof *us);
693 i = mp->m_level - 1;
694 if (mp->m_younger && (unsigned)i + 1 == mp->m_younger->m_level) {
695 if (mp->m_parent && mp->m_parent->m_flag & important)
696 us[i] = mp->m_flag & important ? 0x2523 : 0x2520;
697 else
698 us[i] = mp->m_flag & important ? 0x251D : 0x251C;
699 cs[i] = '+';
700 } else {
701 if (mp->m_parent && mp->m_parent->m_flag & important)
702 us[i] = mp->m_flag & important ? 0x2517 : 0x2516;
703 else
704 us[i] = mp->m_flag & important ? 0x2515 : 0x2514;
705 cs[i] = '\\';
707 mq = mp->m_parent;
708 for (i = mp->m_level - 2; i >= 0; i--) {
709 if (mq) {
710 if ((unsigned)i > mq->m_level - 1) {
711 us[i] = cs[i] = ' ';
712 continue;
714 if (mq->m_younger) {
715 if (mq->m_parent &&
716 mq->m_parent->m_flag&important)
717 us[i] = 0x2503;
718 else
719 us[i] = 0x2502;
720 cs[i] = '|';
721 } else
722 us[i] = cs[i] = ' ';
723 mq = mq->m_parent;
724 } else
725 us[i] = cs[i] = ' ';
727 for (indent = 0; (unsigned)indent < mp->m_level && indent < maxwidth;
728 ++indent) {
729 if (indent < maxwidth - 1)
730 putuc(us[indent], cs[indent] & 0377, fp);
731 else
732 putuc(0x21B8, '^', fp);
734 ac_free(us);
735 ac_free(cs);
736 return indent;
739 void
740 printhead(int mesg, FILE *f, int threaded)
742 int bsdflags, bsdheadline, sz;
743 char attrlist[30], *cp;
744 char const *fmt;
746 bsdflags = value("bsdcompat") != NULL || value("bsdflags") != NULL ||
747 getenv("SYSV3") != NULL;
748 strcpy(attrlist, bsdflags ? "NU *HMFATK+-J" : "NUROSPMFATK+-J");
749 if ((cp = value("attrlist")) != NULL) {
750 sz = strlen(cp);
751 if (sz > (int)sizeof attrlist - 1)
752 sz = (int)sizeof attrlist - 1;
753 memcpy(attrlist, cp, sz);
755 bsdheadline = value("bsdcompat") != NULL ||
756 value("bsdheadline") != NULL;
757 if ((fmt = value("headline")) == NULL)
758 fmt = bsdheadline ?
759 "%>%a%m %-20f %16d %3l/%-5o %i%-S" :
760 "%>%a%m %-18f %16d %4l/%-5o %i%-s";
761 hprf(fmt, mesg, f, threaded, attrlist);
765 * Print out the value of dot.
767 /*ARGSUSED*/
768 int
769 pdot(void *v)
771 (void)v;
772 printf(catgets(catd, CATSET, 13, "%d\n"),
773 (int)(dot - &message[0] + 1));
774 return(0);
778 * Print out all the possible commands.
781 static int
782 _pcmd_cmp(void const *s1, void const *s2)
784 struct cmd const *const*c1 = s1, *const*c2 = s2;
785 return (strcmp((*c1)->c_name, (*c2)->c_name));
788 /*ARGSUSED*/
789 int
790 pcmdlist(void *v)
792 extern struct cmd const cmdtab[];
793 struct cmd const **cpa, *cp, **cursor;
794 size_t i;
795 (void)v;
797 for (i = 0; cmdtab[i].c_name != NULL; ++i)
799 ++i;
800 cpa = ac_alloc(sizeof(cp) * i);
802 for (i = 0; (cp = cmdtab + i)->c_name != NULL; ++i)
803 cpa[i] = cp;
804 cpa[i] = NULL;
806 qsort(cpa, i, sizeof(cp), &_pcmd_cmp);
808 printf(tr(14, "Commands are:\n"));
809 for (i = 0, cursor = cpa; (cp = *cursor++) != NULL;) {
810 size_t j;
811 if (cp->c_func == &ccmdnotsupp)
812 continue;
813 j = strlen(cp->c_name) + 2;
814 if ((i += j) > 72) {
815 i = j;
816 printf("\n");
818 printf((*cursor != NULL ? "%s, " : "%s\n"), cp->c_name);
821 ac_free(cpa);
822 return (0);
826 * Type out the messages requested.
828 static sigjmp_buf pipestop;
830 /*ARGSUSED*/
831 static void
832 brokpipe(int signo)
834 (void)signo;
835 siglongjmp(pipestop, 1);
838 static int
839 type1(int *msgvec, int doign, int page, int pipe, int decode,
840 char *cmd, off_t *tstats)
842 int *ip;
843 struct message *mp;
844 char const *cp;
845 int nlines;
846 off_t mstats[2];
847 FILE *volatile obuf;
849 obuf = stdout;
850 if (sigsetjmp(pipestop, 1))
851 goto close_pipe;
852 if (pipe) {
853 cp = value("SHELL");
854 if (cp == NULL)
855 cp = SHELL;
856 obuf = Popen(cmd, "w", cp, 1);
857 if (obuf == NULL) {
858 perror(cmd);
859 obuf = stdout;
860 } else {
861 safe_signal(SIGPIPE, brokpipe);
863 } else if ((options & OPT_TTYOUT) &&
864 (page || (cp = value("crt")) != NULL)) {
865 nlines = 0;
866 if (!page) {
867 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
868 if ((message[*ip-1].m_have & HAVE_BODY) == 0) {
869 if ((get_body(&message[*ip - 1])) !=
870 OKAY)
871 return 1;
873 nlines += message[*ip - 1].m_lines;
876 if (page || nlines > (*cp ? atoi(cp) : realscreenheight)) {
877 char const *p = get_pager();
878 obuf = Popen(p, "w", NULL, 1);
879 if (obuf == NULL) {
880 perror(p);
881 obuf = stdout;
882 } else
883 safe_signal(SIGPIPE, brokpipe);
886 for (ip = msgvec; *ip && ip - msgvec < msgCount; ip++) {
887 mp = &message[*ip - 1];
888 touch(mp);
889 setdot(mp);
890 uncollapse1(mp, 1);
891 if (! pipe && ip != msgvec)
892 fprintf(obuf, "\n");
893 _show_msg_overview(mp, *ip, obuf);
894 send(mp, obuf, doign ? ignore : 0, NULL,
895 pipe && value("piperaw") ? SEND_MBOX :
896 decode ? SEND_SHOW :
897 doign ? SEND_TODISP : SEND_TODISP_ALL,
898 mstats);
899 if (pipe && value("page")) {
900 putc('\f', obuf);
902 if (tstats) {
903 tstats[0] += mstats[0];
904 tstats[1] += mstats[1];
907 close_pipe:
908 if (obuf != stdout) {
910 * Ignore SIGPIPE so it can't cause a duplicate close.
912 safe_signal(SIGPIPE, SIG_IGN);
913 Pclose(obuf, TRU1);
914 safe_signal(SIGPIPE, dflpipe);
916 return(0);
920 * Get the last, possibly quoted part of linebuf.
922 char *
923 laststring(char *linebuf, int *flag, int strip)
925 char *cp, *p;
926 char quoted;
928 *flag = 1;
929 cp = strlen(linebuf) + linebuf - 1;
932 * Strip away trailing blanks.
934 while (cp > linebuf && whitechar(*cp & 0377))
935 cp--;
936 *++cp = 0;
937 if (cp == linebuf) {
938 *flag = 0;
939 return NULL;
943 * Now search for the beginning of the command name.
945 quoted = *(cp - 1);
946 if (quoted == '\'' || quoted == '\"') {
947 cp--;
948 if (strip)
949 *cp = '\0';
950 cp--;
951 while (cp > linebuf) {
952 if (*cp != quoted) {
953 cp--;
954 } else if (*(cp - 1) != '\\') {
955 break;
956 } else {
957 p = --cp;
958 do {
959 *p = *(p + 1);
960 } while (*p++);
961 cp--;
964 if (cp == linebuf)
965 *flag = 0;
966 if (*cp == quoted) {
967 if (strip)
968 *cp++ = 0;
969 } else
970 *flag = 0;
971 } else {
972 while (cp > linebuf && !whitechar(*cp & 0377))
973 cp--;
974 if (whitechar(*cp & 0377))
975 *cp++ = 0;
976 else
977 *flag = 0;
979 if (*cp == '\0') {
980 return(NULL);
982 return(cp);
986 * Pipe the messages requested.
988 static int
989 pipe1(char *str, int doign)
991 char *cmd;
992 int f, *msgvec, ret;
993 off_t stats[2];
995 /*LINTED*/
996 msgvec = (int *)salloc((msgCount + 2) * sizeof *msgvec);
997 if ((cmd = laststring(str, &f, 1)) == NULL) {
998 cmd = value("cmd");
999 if (cmd == NULL || *cmd == '\0') {
1000 fputs(catgets(catd, CATSET, 16,
1001 "variable cmd not set\n"), stderr);
1002 return 1;
1005 if (!f) {
1006 *msgvec = first(0, MMNORM);
1007 if (*msgvec == 0) {
1008 if (inhook)
1009 return 0;
1010 puts(catgets(catd, CATSET, 18, "No messages to pipe."));
1011 return 1;
1013 msgvec[1] = 0;
1014 } else if (getmsglist(str, msgvec, 0) < 0)
1015 return 1;
1016 if (*msgvec == 0) {
1017 if (inhook)
1018 return 0;
1019 printf("No applicable messages.\n");
1020 return 1;
1022 printf(catgets(catd, CATSET, 268, "Pipe to: \"%s\"\n"), cmd);
1023 stats[0] = stats[1] = 0;
1024 if ((ret = type1(msgvec, doign, 0, 1, 0, cmd, stats)) == 0) {
1025 printf("\"%s\" ", cmd);
1026 if (stats[0] >= 0)
1027 printf("%lu", (long)stats[0]);
1028 else
1029 printf(catgets(catd, CATSET, 27, "binary"));
1030 printf("/%lu\n", (long)stats[1]);
1032 return ret;
1036 * Paginate messages, honor ignored fields.
1038 int
1039 more(void *v)
1041 int *msgvec = v;
1042 return (type1(msgvec, 1, 1, 0, 0, NULL, NULL));
1046 * Paginate messages, even printing ignored fields.
1048 int
1049 More(void *v)
1051 int *msgvec = v;
1053 return (type1(msgvec, 0, 1, 0, 0, NULL, NULL));
1057 * Type out messages, honor ignored fields.
1059 int
1060 type(void *v)
1062 int *msgvec = v;
1064 return(type1(msgvec, 1, 0, 0, 0, NULL, NULL));
1068 * Type out messages, even printing ignored fields.
1070 int
1071 Type(void *v)
1073 int *msgvec = v;
1075 return(type1(msgvec, 0, 0, 0, 0, NULL, NULL));
1079 * Show MIME-encoded message text, including all fields.
1082 show(void *v)
1084 int *msgvec = v;
1086 return(type1(msgvec, 0, 0, 0, 1, NULL, NULL));
1090 * Pipe messages, honor ignored fields.
1092 int
1093 pipecmd(void *v)
1095 char *str = v;
1096 return(pipe1(str, 1));
1099 * Pipe messages, not respecting ignored fields.
1101 int
1102 Pipecmd(void *v)
1104 char *str = v;
1105 return(pipe1(str, 0));
1109 * Print the top so many lines of each desired message.
1110 * The number of lines is taken from the variable "toplines"
1111 * and defaults to 5.
1113 int
1114 top(void *v)
1116 int *msgvec = v, *ip, c, topl, lines, empty_last;
1117 struct message *mp;
1118 char *cp, *linebuf = NULL;
1119 size_t linesize;
1120 FILE *ibuf;
1122 topl = 5;
1123 cp = value("toplines");
1124 if (cp != NULL) {
1125 topl = atoi(cp);
1126 if (topl < 0 || topl > 10000)
1127 topl = 5;
1129 empty_last = 1;
1130 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
1131 mp = &message[*ip - 1];
1132 touch(mp);
1133 setdot(mp);
1134 did_print_dot = TRU1;
1135 if (! empty_last)
1136 printf("\n");
1137 _show_msg_overview(mp, *ip, stdout);
1138 if (mp->m_flag & MNOFROM)
1139 printf("From %s %s\n", fakefrom(mp),
1140 fakedate(mp->m_time));
1141 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL) { /* XXX could use TOP */
1142 v = NULL;
1143 break;
1145 c = mp->m_lines;
1146 for (lines = 0; lines < c && lines <= topl; lines++) {
1147 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1148 break;
1149 puts(linebuf);
1151 for (cp = linebuf; *cp && blankchar(*cp); ++cp)
1153 empty_last = (*cp == '\0');
1157 if (linebuf != NULL)
1158 free(linebuf);
1159 return (v != NULL);
1163 * Touch all the given messages so that they will
1164 * get mboxed.
1166 int
1167 stouch(void *v)
1169 int *msgvec = v;
1170 int *ip;
1172 for (ip = msgvec; *ip != 0; ip++) {
1173 setdot(&message[*ip-1]);
1174 dot->m_flag |= MTOUCH;
1175 dot->m_flag &= ~MPRESERVE;
1177 * POSIX interpretation necessary.
1179 did_print_dot = TRU1;
1181 return(0);
1185 * Make sure all passed messages get mboxed.
1187 int
1188 mboxit(void *v)
1190 int *msgvec = v;
1191 int *ip;
1193 for (ip = msgvec; *ip != 0; ip++) {
1194 setdot(&message[*ip-1]);
1195 dot->m_flag |= MTOUCH|MBOX;
1196 dot->m_flag &= ~MPRESERVE;
1198 * POSIX interpretation necessary.
1200 did_print_dot = TRU1;
1202 return(0);
1206 * List the folders the user currently has.
1208 int
1209 folders(void *v)
1211 char dirname[MAXPATHLEN], *name, **argv = v;
1212 char const *cmd;
1214 if (*argv) {
1215 name = expand(*argv);
1216 if (name == NULL)
1217 return 1;
1218 } else if (! getfold(dirname, sizeof dirname)) {
1219 fprintf(stderr, tr(20, "No value set for \"folder\"\n"));
1220 return 1;
1221 } else
1222 name = dirname;
1224 if (which_protocol(name) == PROTO_IMAP) {
1225 #ifdef USE_IMAP
1226 imap_folders(name, *argv == NULL);
1227 #else
1228 return ccmdnotsupp(NULL);
1229 #endif
1230 } else {
1231 if ((cmd = value("LISTER")) == NULL)
1232 cmd = LISTER;
1233 run_command(cmd, 0, -1, -1, name, NULL, NULL);
1235 return 0;