Add *message-id-disable*
[s-mailx.git] / cmd1.c
blob5161c47ecda94ad69a2434958e44a872b5125712
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 /* ... And place the extracted date in `date' */
63 static void _parse_from_(struct message *mp, char date[FROM_DATEBUF]);
65 /* Get the Subject:, but return NULL if in threaded mode and the message
66 * printed before was in the same thread and had the same subject */
67 static char * _get_subject(struct message *mp, bool_t threaded);
68 static char * __subject_trim(char *s);
70 static void hprf(const char *fmt, int mesg, FILE *f, int threaded,
71 const char *attrlist);
72 static int putindent(FILE *fp, struct message *mp, int maxwidth);
73 static int type1(int *msgvec, int doign, int page, int pipe, int decode,
74 char *cmd, off_t *tstats);
75 static int pipe1(char *str, int doign);
77 static void
78 _show_msg_overview(struct message *mp, int msg_no, FILE *obuf)
80 fprintf(obuf, tr(17, "[-- Message %2d -- %lu lines, %lu bytes --]:\n"),
81 msg_no, (ul_it)mp->m_lines, (ul_it)mp->m_size);
84 int
85 ccmdnotsupp(void *v)
87 (void)v;
88 fprintf(stderr, tr(10, "The requested feature is not compiled in\n"));
89 return (1);
92 char const *
93 get_pager(void)
95 char const *cp;
97 cp = value("PAGER");
98 if (cp == NULL || *cp == '\0')
99 cp = PAGER;
100 return cp;
103 int
104 headers(void *v)
106 int *msgvec = v;
107 int g, k, n, mesg, flag = 0, lastg = 1;
108 struct message *mp, *mq, *lastmq = NULL;
109 int size;
110 enum mflag fl = MNEW|MFLAGGED;
112 time_current_update(&time_current, FAL0);
114 size = screensize();
115 n = msgvec[0]; /* n == {-2, -1, 0}: called from scroll() */
116 if (screen < 0)
117 screen = 0;
118 k = screen * size;
119 if (k >= msgCount)
120 k = msgCount - size;
121 if (k < 0)
122 k = 0;
123 if (mb.mb_threaded == 0) {
124 g = 0;
125 mq = &message[0];
126 for (mp = &message[0]; mp < &message[msgCount]; mp++)
127 if (visible(mp)) {
128 if (g % size == 0)
129 mq = mp;
130 if (mp->m_flag&fl) {
131 lastg = g;
132 lastmq = mq;
134 if ((n > 0 && mp == &message[n-1]) ||
135 (n == 0 && g == k) ||
136 (n == -2 && g == k + size &&
137 lastmq) ||
138 (n < 0 && g >= k &&
139 (mp->m_flag & fl) != 0))
140 break;
141 g++;
143 if (lastmq && (n==-2 || (n==-1 && mp == &message[msgCount]))) {
144 g = lastg;
145 mq = lastmq;
147 screen = g / size;
148 mp = mq;
149 mesg = mp - &message[0];
150 if (dot != &message[n-1]) {
151 for (mq = mp; mq < &message[msgCount]; mq++)
152 if (visible(mq)) {
153 setdot(mq);
154 break;
157 #ifdef HAVE_IMAP
158 if (mb.mb_type == MB_IMAP)
159 imap_getheaders(mesg+1, mesg + size);
160 #endif
161 for (; mp < &message[msgCount]; mp++) {
162 mesg++;
163 if (!visible(mp))
164 continue;
165 if (flag++ >= size)
166 break;
167 printhead(mesg, stdout, 0);
169 } else { /* threaded */
170 g = 0;
171 mq = threadroot;
172 for (mp = threadroot; mp; mp = next_in_thread(mp))
173 if (visible(mp) && (mp->m_collapsed <= 0 ||
174 mp == &message[n-1])) {
175 if (g % size == 0)
176 mq = mp;
177 if (mp->m_flag&fl) {
178 lastg = g;
179 lastmq = mq;
181 if ((n > 0 && mp == &message[n-1]) ||
182 (n == 0 && g == k) ||
183 (n == -2 && g == k + size &&
184 lastmq) ||
185 (n < 0 && g >= k &&
186 (mp->m_flag & fl) != 0))
187 break;
188 g++;
190 if (lastmq && (n==-2 || (n==-1 && mp==&message[msgCount]))) {
191 g = lastg;
192 mq = lastmq;
194 screen = g / size;
195 mp = mq;
196 if (dot != &message[n-1]) {
197 for (mq = mp; mq; mq = next_in_thread(mq))
198 if (visible(mq) && mq->m_collapsed <= 0) {
199 setdot(mq);
200 break;
203 while (mp) {
204 if (visible(mp) && (mp->m_collapsed <= 0 ||
205 mp == &message[n-1])) {
206 if (flag++ >= size)
207 break;
208 printhead(mp - &message[0] + 1, stdout,
209 mb.mb_threaded);
211 mp = next_in_thread(mp);
214 if (flag == 0) {
215 printf(tr(6, "No more mail.\n"));
216 return(1);
218 return(0);
222 * Scroll to the next/previous screen
225 scroll(void *v)
227 return scroll1(v, 0);
231 Scroll(void *v)
233 return scroll1(v, 1);
236 static int
237 scroll1(char *arg, int onlynew)
239 int size;
240 int cur[1];
242 cur[0] = onlynew ? -1 : 0;
243 size = screensize();
244 switch (*arg) {
245 case '1': case '2': case '3': case '4': case '5':
246 case '6': case '7': case '8': case '9': case '0':
247 screen = atoi(arg);
248 goto scroll_forward;
249 case '\0':
250 screen++;
251 goto scroll_forward;
252 case '$':
253 screen = msgCount / size;
254 goto scroll_forward;
255 case '+':
256 if (arg[1] == '\0')
257 screen++;
258 else
259 screen += atoi(arg + 1);
260 scroll_forward:
261 if (screen * size > msgCount) {
262 screen = msgCount / size;
263 printf(catgets(catd, CATSET, 7,
264 "On last screenful of messages\n"));
266 break;
268 case '-':
269 if (arg[1] == '\0')
270 screen--;
271 else
272 screen -= atoi(arg + 1);
273 if (screen < 0) {
274 screen = 0;
275 printf(catgets(catd, CATSET, 8,
276 "On first screenful of messages\n"));
278 if (cur[0] == -1)
279 cur[0] = -2;
280 break;
282 default:
283 printf(catgets(catd, CATSET, 9,
284 "Unrecognized scrolling command \"%s\"\n"), arg);
285 return(1);
287 return(headers(cur));
291 * Compute screen size.
293 int
294 screensize(void)
296 int s;
297 char *cp;
299 if ((cp = value("screen")) != NULL && (s = atoi(cp)) > 0)
300 return s;
301 return scrnheight - 4;
304 static sigjmp_buf pipejmp;
306 /*ARGSUSED*/
307 static void
308 onpipe(int signo)
310 (void)signo;
311 siglongjmp(pipejmp, 1);
315 * Print out the headlines for each message
316 * in the passed message list.
318 int
319 from(void *v)
321 int *msgvec = v, *ip, n;
322 char *cp;
323 FILE *volatile obuf = stdout;
325 time_current_update(&time_current, FAL0);
327 /* TODO unfixable memory leaks still */
328 if (IS_TTY_SESSION() && (cp = value("crt")) != NULL) {
329 for (n = 0, ip = msgvec; *ip; ip++)
330 n++;
331 if (n > (*cp == '\0' ? screensize() : atoi((char*)cp)) + 3) {
332 char const *p;
333 if (sigsetjmp(pipejmp, 1))
334 goto endpipe;
335 p = get_pager();
336 if ((obuf = Popen(p, "w", NULL, 1)) == NULL) {
337 perror(p);
338 obuf = stdout;
339 cp=NULL;
340 } else
341 safe_signal(SIGPIPE, onpipe);
344 for (ip = msgvec; *ip != 0; ip++)
345 printhead(*ip, obuf, mb.mb_threaded);
346 if (--ip >= msgvec)
347 setdot(&message[*ip - 1]);
348 endpipe:
349 if (obuf != stdout) {
350 safe_signal(SIGPIPE, SIG_IGN);
351 Pclose(obuf, TRU1);
352 safe_signal(SIGPIPE, dflpipe);
354 return(0);
357 static int
358 _dispc(struct message *mp, const char *a)
360 int i = ' ';
363 * Bletch!
365 if ((mp->m_flag & (MREAD|MNEW)) == MREAD)
366 i = a[3];
367 if ((mp->m_flag & (MREAD|MNEW)) == (MREAD|MNEW))
368 i = a[2];
369 if (mp->m_flag & MANSWERED)
370 i = a[8];
371 if (mp->m_flag & MDRAFTED)
372 i = a[9];
373 if ((mp->m_flag & (MREAD|MNEW)) == MNEW)
374 i = a[0];
375 if ((mp->m_flag & (MREAD|MNEW)) == 0)
376 i = a[1];
377 if (mp->m_flag & MSPAM)
378 i = a[12];
379 if (mp->m_flag & MSAVED)
380 i = a[4];
381 if (mp->m_flag & MPRESERVE)
382 i = a[5];
383 if (mp->m_flag & (MBOX|MBOXED))
384 i = a[6];
385 if (mp->m_flag & MFLAGGED)
386 i = a[7];
387 if (mb.mb_threaded == 1 && mp->m_collapsed > 0)
388 i = a[11];
389 if (mb.mb_threaded == 1 && mp->m_collapsed < 0)
390 i = a[10];
391 return i;
394 static void
395 _parse_from_(struct message *mp, char date[FROM_DATEBUF])
397 FILE *ibuf;
398 int hlen;
399 char *hline = NULL;
400 size_t hsize = 0;
402 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) != NULL &&
403 (hlen = readline_restart(ibuf, &hline, &hsize, 0)) > 0)
404 (void)extract_date_from_from_(hline, hlen, date);
405 if (hline != NULL)
406 free(hline);
409 static char *
410 __subject_trim(char *s)
412 while (*s != '\0') {
413 while (spacechar(*s))
414 ++s;
415 if (is_asccaseprefix("re:", s)) {
416 s += 3;
417 continue;
419 if (is_asccaseprefix("fwd:", s)) {
420 s += 4;
421 continue;
423 break;
425 return s;
428 static char *
429 _get_subject(struct message *mp, bool_t threaded)
431 struct str in, out;
432 char *rv = (char*)-1, *ms, *mso, *os;
434 if ((ms = hfield1("subject", mp)) == NULL)
435 goto jleave;
437 if (! threaded || mp->m_level == 0)
438 goto jconv;
440 /* In a display thread - check wether this message uses the same
441 * Subject: as it's parent or elder neighbour, suppress printing it if
442 * this is the case. To extend this a bit, ignore any leading Re: or
443 * Fwd: plus follow-up WS; XXX NOTE: because of efficiency reasons we
444 * XXX simply ignore any encoded parts and use ASCII case-insensitive
445 * XXX comparison */
446 mso = __subject_trim(ms);
448 if (mp->m_elder != NULL &&
449 (os = hfield1("subject", mp->m_elder)) != NULL &&
450 asccasecmp(mso, __subject_trim(os)) == 0)
451 goto jleave;
453 if (mp->m_parent != NULL &&
454 (os = hfield1("subject", mp->m_parent)) != NULL &&
455 asccasecmp(mso, __subject_trim(os)) == 0)
456 goto jleave;
458 jconv:
459 in.s = ms;
460 in.l = strlen(ms);
461 mime_fromhdr(&in, &out, TD_ICONV | TD_ISPR);
462 rv = out.s;
463 jleave:
464 return rv;
467 static void
468 hprf(const char *fmt, int mesg, FILE *f, int threaded, const char *attrlist)
470 char datebuf[FROM_DATEBUF], *cp, *subjline;
471 char const *datefmt, *date, *name, *fp;
472 int B, c, i, n, s, wleft, subjlen, isto = 0, isaddr = 0;
473 struct message *mp = &message[mesg - 1];
474 time_t datet = mp->m_time;
476 date = NULL;
477 if ((datefmt = value("datefield")) != NULL) {
478 fp = hfield1("date", mp);/* TODO use m_date field! */
479 if (fp == NULL) {
480 datefmt = NULL;
481 goto jdate_set;
483 datet = rfctime(fp);
484 date = fakedate(datet);
485 fp = value("datefield-markout-older");
486 i = (*datefmt != '\0');
487 if (fp != NULL)
488 i |= (*fp != '\0') ? 2 | 4 : 2;
489 /* May we strftime(3)? */
490 if (i & (1 | 4))
491 memcpy(&time_current.tc_local, localtime(&datet),
492 sizeof time_current.tc_local);
493 if ((i & 2) &&
494 /* TODO *datefield-markout-older* we accept
495 * TODO one day in the future, should be UTC
496 * TODO offset only? and Stephen Isard had
497 * TODO one week once he proposed the patch! */
498 (datet > time_current.tc_time + DATE_SECSDAY ||
499 #define _6M ((DATE_DAYSYEAR / 2) * DATE_SECSDAY)
500 (datet + _6M < time_current.tc_time))) {
501 #undef _6M
502 if ((datefmt = (i & 4) ? fp : NULL) == NULL) {
503 memset(datebuf, ' ', FROM_DATEBUF); /* xxx ur */
504 memcpy(datebuf + 4, date + 4, 7);
505 datebuf[4 + 7] = ' ';
506 memcpy(datebuf + 4 + 7 + 1, date + 20, 4);
507 datebuf[4 + 7 + 1 + 4] = '\0';
508 date = datebuf;
510 } else if ((i & 1) == 0)
511 datefmt = NULL;
512 } else if (datet == (time_t)0 && (mp->m_flag & MNOFROM) == 0) {
513 /* TODO eliminate this path, query the FROM_ date in setptr(),
514 * TODO all other codepaths do so by themselves ALREADY ?????
515 * TODO assert(mp->m_time != 0);, then
516 * TODO ALSO changes behaviour of markout-non-current */
517 _parse_from_(mp, datebuf);
518 date = datebuf;
519 } else {
520 jdate_set:
521 date = fakedate(datet);
524 isaddr = 1;
525 name = name1(mp, 0);
526 if (name != NULL && value("showto") && is_myname(skin(name))) {
527 if ((cp = hfield1("to", mp)) != NULL) {
528 name = cp;
529 isto = 1;
532 if (name == NULL) {
533 name = "";
534 isaddr = 0;
536 if (isaddr) {
537 if (value("showname"))
538 name = realname(name);
539 else {
540 name = prstr(skin(name));
544 subjline = NULL;
546 /* Detect the width of the non-format characters in *headline*;
547 * like that we can simply use putc() in the next loop, since we have
548 * already calculated their column widths (TODO it's sick) */
549 wleft =
550 subjlen = scrnwidth;
552 for (fp = fmt; *fp; ++fp) {
553 if (*fp == '%') {
554 if (*++fp == '-') {
555 ++fp;
556 } else if (*fp == '+')
557 ++fp;
558 if (digitchar(*fp)) {
559 n = 0;
561 n = 10*n + *fp - '0';
562 while (++fp, digitchar(*fp));
563 subjlen -= n;
566 if (*fp == '\0')
567 break;
568 } else {
569 #if defined HAVE_MBTOWC && defined HAVE_WCWIDTH
570 if (mb_cur_max > 1) {
571 wchar_t wc;
572 if ((s = mbtowc(&wc, fp, mb_cur_max)) < 0)
573 n = s = 1;
574 else if ((n = wcwidth(wc)) < 0)
575 n = 1;
576 } else
577 #endif
578 n = s = 1;
579 subjlen -= n;
580 wleft -= n;
581 while (--s > 0)
582 ++fp;
586 /* Walk *headline*, producing output */
587 for (fp = fmt; *fp; ++fp) {
588 if ((c = *fp & 0xFF) == '%') {
589 B = 0;
590 n = 0;
591 s = 1;
592 if (*++fp == '-') {
593 s = -1;
594 ++fp;
595 } else if (*fp == '+')
596 ++fp;
597 if (digitchar(*fp)) {
599 n = 10*n + *fp - '0';
600 while (++fp, digitchar(*fp));
602 if (*fp == '\0')
603 break;
605 n *= s;
606 switch ((c = *fp & 0xFF)) {
607 case '%':
608 goto jputc;
609 case '>':
610 case '<':
611 if (dot != mp)
612 c = ' ';
613 goto jputc;
614 case 'a':
615 c = _dispc(mp, attrlist);
616 jputc:
617 if (ABS(n) > wleft)
618 n = (n < 0) ? -wleft : wleft;
619 n = fprintf(f, "%*c", n, c);
620 wleft = (n >= 0) ? wleft - n : 0;
621 break;
622 case 'm':
623 if (n == 0) {
624 n = 3;
625 if (threaded)
626 for (i=msgCount; i>999; i/=10)
627 n++;
629 if (ABS(n) > wleft)
630 n = (n < 0) ? -wleft : wleft;
631 n = fprintf(f, "%*d", n, mesg);
632 wleft = (n >= 0) ? wleft - n : 0;
633 break;
634 case 'f':
635 if (n == 0) {
636 n = 18;
637 if (s < 0)
638 n = -n;
640 i = ABS(n);
641 if (i > wleft) {
642 i = wleft;
643 n = (n < 0) ? -wleft : wleft;
645 if (isto) /* XXX tr()! */
646 i -= 3;
647 n = fprintf(f, "%s%s", (isto ? "To " : ""),
648 colalign(name, i, n, &wleft));
649 if (n < 0)
650 wleft = 0;
651 else if (isto)
652 wleft -= 3;
653 break;
654 case 'd':
655 if (datefmt != NULL) {
656 i = strftime(datebuf, sizeof datebuf,
657 datefmt,
658 &time_current.tc_local);
659 if (i != 0)
660 date = datebuf;
661 else
662 fprintf(stderr, tr(174,
663 "Ignored date format, "
664 "it excesses the "
665 "target buffer "
666 "(%lu bytes)\n"),
667 (ul_it)sizeof datebuf);
668 datefmt = NULL;
670 if (n == 0)
671 n = 16;
672 if (ABS(n) > wleft)
673 n = (n < 0) ? -wleft : wleft;
674 n = fprintf(f, "%*.*s", n, n, date);
675 wleft = (n >= 0) ? wleft - n : 0;
676 break;
677 case 'l':
678 if (n == 0)
679 n = 4;
680 if (ABS(n) > wleft)
681 n = (n < 0) ? -wleft : wleft;
682 if (mp->m_xlines) {
683 n = fprintf(f, "%*ld", n, mp->m_xlines);
684 wleft = (n >= 0) ? wleft - n : 0;
685 } else {
686 n = ABS(n);
687 wleft -= n;
688 while (n-- != 0)
689 putc(' ', f);
691 break;
692 case 'o':
693 if (n == 0)
694 n = -5;
695 if (ABS(n) > wleft)
696 n = (n < 0) ? -wleft : wleft;
697 n = fprintf(f, "%*lu", n, (long)mp->m_xsize);
698 wleft = (n >= 0) ? wleft - n : 0;
699 break;
700 case 'i':
701 if (threaded) {
702 n = putindent(f, mp, MIN(wleft,
703 scrnwidth - 60));
704 wleft = (n >= 0) ? wleft - n : 0;
706 break;
707 case 'S':
708 B = 1;
709 /*FALLTHRU*/
710 case 's':
711 if (n == 0)
712 n = subjlen - 2;
713 if (n > 0 && s < 0)
714 n = -n;
715 if (subjlen > wleft)
716 subjlen = wleft;
717 if (ABS(n) > subjlen)
718 n = (n < 0) ? -subjlen : subjlen;
719 if (B)
720 n -= (n < 0) ? -2 : 2;
721 if (n == 0)
722 break;
723 if (subjline == NULL)
724 subjline = _get_subject(mp, threaded);
725 if (subjline == (char*)-1) {
726 n = fprintf(f, "%*s", n, "");
727 wleft = (n >= 0) ? wleft-n : 0;
728 } else {
729 n = fprintf(f, (B ? "\"%s\"" : "%s"),
730 colalign(subjline, ABS(n), n,
731 &wleft));
732 if (n < 0)
733 wleft = 0;
735 break;
736 case 'U':
737 #ifdef HAVE_IMAP
738 if (n == 0)
739 n = 9;
740 if (ABS(n) > wleft)
741 n = (n < 0) ? -wleft : wleft;
742 n = fprintf(f, "%*lu", n, mp->m_uid);
743 wleft = (n >= 0) ? wleft - n : 0;
744 break;
745 #else
746 c = '?';
747 goto jputc;
748 #endif
749 case 'e':
750 if (n == 0)
751 n = 2;
752 if (ABS(n) > wleft)
753 n = (n < 0) ? -wleft : wleft;
754 n = fprintf(f, "%*u", n,
755 threaded == 1 ? mp->m_level : 0);
756 wleft = (n >= 0) ? wleft - n : 0;
757 break;
758 case 't':
759 if (n == 0) {
760 n = 3;
761 if (threaded)
762 for (i=msgCount; i>999; i/=10)
763 n++;
765 if (ABS(n) > wleft)
766 n = (n < 0) ? -wleft : wleft;
767 n = fprintf(f, "%*ld", n,
768 threaded ? mp->m_threadpos : mesg);
769 wleft = (n >= 0) ? wleft - n : 0;
770 break;
771 case '$':
772 #ifdef HAVE_SPAM
773 if (n == 0)
774 n = 4;
775 if (ABS(n) > wleft)
776 n = (n < 0) ? -wleft : wleft;
777 { char buf[16];
778 snprintf(buf, sizeof buf, "%u.%u",
779 (mp->m_spamscore >> 8),
780 (mp->m_spamscore & 0xFF));
781 n = fprintf(f, "%*s", n, buf);
782 wleft = (n >= 0) ? wleft - n : 0;
784 #else
785 c = '?';
786 goto jputc;
787 #endif
790 if (wleft <= 0)
791 break;
792 } else
793 putc(c, f);
795 putc('\n', f);
797 if (subjline != NULL && subjline != (char*)-1)
798 free(subjline);
802 * Print out the indenting in threaded display.
804 static int
805 putindent(FILE *fp, struct message *mp, int maxwidth)/* XXX no magic consts */
807 struct message *mq;
808 int *us, indlvl, indw, i, important = MNEW|MFLAGGED;
809 char *cs;
811 if (mp->m_level == 0 || maxwidth == 0)
812 return 0;
813 cs = ac_alloc(mp->m_level);
814 us = ac_alloc(mp->m_level * sizeof *us);
816 i = mp->m_level - 1;
817 if (mp->m_younger && (unsigned)i + 1 == mp->m_younger->m_level) {
818 if (mp->m_parent && mp->m_parent->m_flag & important)
819 us[i] = mp->m_flag & important ? 0x2523 : 0x2520;
820 else
821 us[i] = mp->m_flag & important ? 0x251D : 0x251C;
822 cs[i] = '+';
823 } else {
824 if (mp->m_parent && mp->m_parent->m_flag & important)
825 us[i] = mp->m_flag & important ? 0x2517 : 0x2516;
826 else
827 us[i] = mp->m_flag & important ? 0x2515 : 0x2514;
828 cs[i] = '\\';
831 mq = mp->m_parent;
832 for (i = mp->m_level - 2; i >= 0; i--) {
833 if (mq) {
834 if ((unsigned)i > mq->m_level - 1) {
835 us[i] = cs[i] = ' ';
836 continue;
838 if (mq->m_younger) {
839 if (mq->m_parent &&
840 mq->m_parent->m_flag&important)
841 us[i] = 0x2503;
842 else
843 us[i] = 0x2502;
844 cs[i] = '|';
845 } else
846 us[i] = cs[i] = ' ';
847 mq = mq->m_parent;
848 } else
849 us[i] = cs[i] = ' ';
852 --maxwidth;
853 for (indlvl = indw = 0; (uc_it)indlvl < mp->m_level &&
854 indw < maxwidth; ++indlvl) {
855 if (indw < maxwidth - 1)
856 indw += (int)putuc(us[indlvl], cs[indlvl] & 0377, fp);
857 else
858 indw += (int)putuc(0x21B8, '^', fp);
860 indw += /*putuc(0x261E, fp)*/putc('>', fp) != EOF;
862 ac_free(us);
863 ac_free(cs);
864 return indw;
867 void
868 printhead(int mesg, FILE *f, int threaded)
870 int bsdflags, bsdheadline, sz;
871 char attrlist[30], *cp;
872 char const *fmt;
874 bsdflags = value("bsdcompat") != NULL || value("bsdflags") != NULL ||
875 getenv("SYSV3") != NULL;
876 strcpy(attrlist, bsdflags ? "NU *HMFAT+-$" : "NUROSPMFAT+-$");
877 if ((cp = value("attrlist")) != NULL) {
878 sz = strlen(cp);
879 if (sz > (int)sizeof attrlist - 1)
880 sz = (int)sizeof attrlist - 1;
881 memcpy(attrlist, cp, sz);
883 bsdheadline = value("bsdcompat") != NULL ||
884 value("bsdheadline") != NULL;
885 if ((fmt = value("headline")) == NULL)
886 fmt = bsdheadline ?
887 "%>%a%m %-20f %16d %3l/%-5o %i%-S" :
888 "%>%a%m %-18f %16d %4l/%-5o %i%-s";
889 hprf(fmt, mesg, f, threaded, attrlist);
893 * Print out the value of dot.
895 /*ARGSUSED*/
896 int
897 pdot(void *v)
899 (void)v;
900 printf("%d\n", (int)(dot - &message[0] + 1));
901 return(0);
905 * Print out all the possible commands.
908 static int
909 _pcmd_cmp(void const *s1, void const *s2)
911 struct cmd const *const*c1 = s1, *const*c2 = s2;
912 return (strcmp((*c1)->c_name, (*c2)->c_name));
915 /*ARGSUSED*/
916 int
917 pcmdlist(void *v)
919 extern struct cmd const cmdtab[];
920 struct cmd const **cpa, *cp, **cursor;
921 size_t i;
922 (void)v;
924 for (i = 0; cmdtab[i].c_name != NULL; ++i)
926 ++i;
927 cpa = ac_alloc(sizeof(cp) * i);
929 for (i = 0; (cp = cmdtab + i)->c_name != NULL; ++i)
930 cpa[i] = cp;
931 cpa[i] = NULL;
933 qsort(cpa, i, sizeof(cp), &_pcmd_cmp);
935 printf(tr(14, "Commands are:\n"));
936 for (i = 0, cursor = cpa; (cp = *cursor++) != NULL;) {
937 size_t j;
938 if (cp->c_func == &ccmdnotsupp)
939 continue;
940 j = strlen(cp->c_name) + 2;
941 if ((i += j) > 72) {
942 i = j;
943 printf("\n");
945 printf((*cursor != NULL ? "%s, " : "%s\n"), cp->c_name);
948 ac_free(cpa);
949 return (0);
953 * Type out the messages requested.
955 static sigjmp_buf pipestop;
957 /*ARGSUSED*/
958 static void
959 brokpipe(int signo)
961 (void)signo;
962 siglongjmp(pipestop, 1);
965 static int
966 type1(int *msgvec, int doign, int page, int pipe, int decode,
967 char *cmd, off_t *tstats)
969 int *ip;
970 struct message *mp;
971 char const *cp;
972 int nlines;
973 off_t mstats[2];
974 FILE *volatile obuf;
976 obuf = stdout;
977 if (sigsetjmp(pipestop, 1))
978 goto close_pipe;
979 if (pipe) {
980 cp = value("SHELL");
981 if (cp == NULL)
982 cp = SHELL;
983 obuf = Popen(cmd, "w", cp, 1);
984 if (obuf == NULL) {
985 perror(cmd);
986 obuf = stdout;
987 } else {
988 safe_signal(SIGPIPE, brokpipe);
990 } else if ((options & OPT_TTYOUT) &&
991 (page || (cp = value("crt")) != NULL)) {
992 nlines = 0;
993 if (!page) {
994 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
995 if ((message[*ip-1].m_have & HAVE_BODY) == 0) {
996 if ((get_body(&message[*ip - 1])) !=
997 OKAY)
998 return 1;
1000 nlines += message[*ip - 1].m_lines;
1003 if (page || nlines > (*cp ? atoi(cp) : realscreenheight)) {
1004 char const *p = get_pager();
1005 obuf = Popen(p, "w", NULL, 1);
1006 if (obuf == NULL) {
1007 perror(p);
1008 obuf = stdout;
1009 } else
1010 safe_signal(SIGPIPE, brokpipe);
1013 for (ip = msgvec; *ip && ip - msgvec < msgCount; ip++) {
1014 mp = &message[*ip - 1];
1015 touch(mp);
1016 setdot(mp);
1017 uncollapse1(mp, 1);
1018 if (! pipe && ip != msgvec)
1019 fprintf(obuf, "\n");
1020 _show_msg_overview(mp, *ip, obuf);
1021 send(mp, obuf, doign ? ignore : 0, NULL,
1022 pipe && value("piperaw") ? SEND_MBOX :
1023 decode ? SEND_SHOW :
1024 doign ? SEND_TODISP : SEND_TODISP_ALL,
1025 mstats);
1026 if (pipe && value("page")) {
1027 putc('\f', obuf);
1029 if (tstats) {
1030 tstats[0] += mstats[0];
1031 tstats[1] += mstats[1];
1034 close_pipe:
1035 if (obuf != stdout) {
1037 * Ignore SIGPIPE so it can't cause a duplicate close.
1039 safe_signal(SIGPIPE, SIG_IGN);
1040 Pclose(obuf, TRU1);
1041 safe_signal(SIGPIPE, dflpipe);
1043 return(0);
1047 * Get the last, possibly quoted part of linebuf.
1049 char *
1050 laststring(char *linebuf, int *flag, int strip)
1052 char *cp, *p;
1053 char quoted;
1055 *flag = 1;
1056 cp = strlen(linebuf) + linebuf - 1;
1059 * Strip away trailing blanks.
1061 while (cp > linebuf && whitechar(*cp & 0377))
1062 cp--;
1063 *++cp = 0;
1064 if (cp == linebuf) {
1065 *flag = 0;
1066 return NULL;
1070 * Now search for the beginning of the command name.
1072 quoted = *(cp - 1);
1073 if (quoted == '\'' || quoted == '\"') {
1074 cp--;
1075 if (strip)
1076 *cp = '\0';
1077 cp--;
1078 while (cp > linebuf) {
1079 if (*cp != quoted) {
1080 cp--;
1081 } else if (*(cp - 1) != '\\') {
1082 break;
1083 } else {
1084 p = --cp;
1085 do {
1086 *p = *(p + 1);
1087 } while (*p++);
1088 cp--;
1091 if (cp == linebuf)
1092 *flag = 0;
1093 if (*cp == quoted) {
1094 if (strip)
1095 *cp++ = 0;
1096 } else
1097 *flag = 0;
1098 } else {
1099 while (cp > linebuf && !whitechar(*cp & 0377))
1100 cp--;
1101 if (whitechar(*cp & 0377))
1102 *cp++ = 0;
1103 else
1104 *flag = 0;
1106 if (*cp == '\0') {
1107 return(NULL);
1109 return(cp);
1113 * Pipe the messages requested.
1115 static int
1116 pipe1(char *str, int doign)
1118 char *cmd;
1119 int f, *msgvec, ret;
1120 off_t stats[2];
1122 /*LINTED*/
1123 msgvec = (int *)salloc((msgCount + 2) * sizeof *msgvec);
1124 if ((cmd = laststring(str, &f, 1)) == NULL) {
1125 cmd = value("cmd");
1126 if (cmd == NULL || *cmd == '\0') {
1127 fputs(catgets(catd, CATSET, 16,
1128 "variable cmd not set\n"), stderr);
1129 return 1;
1132 if (!f) {
1133 *msgvec = first(0, MMNORM);
1134 if (*msgvec == 0) {
1135 if (inhook)
1136 return 0;
1137 puts(catgets(catd, CATSET, 18, "No messages to pipe."));
1138 return 1;
1140 msgvec[1] = 0;
1141 } else if (getmsglist(str, msgvec, 0) < 0)
1142 return 1;
1143 if (*msgvec == 0) {
1144 if (inhook)
1145 return 0;
1146 printf("No applicable messages.\n");
1147 return 1;
1149 printf(catgets(catd, CATSET, 268, "Pipe to: \"%s\"\n"), cmd);
1150 stats[0] = stats[1] = 0;
1151 if ((ret = type1(msgvec, doign, 0, 1, 0, cmd, stats)) == 0) {
1152 printf("\"%s\" ", cmd);
1153 if (stats[0] >= 0)
1154 printf("%lu", (long)stats[0]);
1155 else
1156 printf(catgets(catd, CATSET, 27, "binary"));
1157 printf("/%lu\n", (long)stats[1]);
1159 return ret;
1163 * Paginate messages, honor ignored fields.
1165 int
1166 more(void *v)
1168 int *msgvec = v;
1169 return (type1(msgvec, 1, 1, 0, 0, NULL, NULL));
1173 * Paginate messages, even printing ignored fields.
1175 int
1176 More(void *v)
1178 int *msgvec = v;
1180 return (type1(msgvec, 0, 1, 0, 0, NULL, NULL));
1184 * Type out messages, honor ignored fields.
1186 int
1187 type(void *v)
1189 int *msgvec = v;
1191 return(type1(msgvec, 1, 0, 0, 0, NULL, NULL));
1195 * Type out messages, even printing ignored fields.
1197 int
1198 Type(void *v)
1200 int *msgvec = v;
1202 return(type1(msgvec, 0, 0, 0, 0, NULL, NULL));
1206 * Show MIME-encoded message text, including all fields.
1209 show(void *v)
1211 int *msgvec = v;
1213 return(type1(msgvec, 0, 0, 0, 1, NULL, NULL));
1217 * Pipe messages, honor ignored fields.
1219 int
1220 pipecmd(void *v)
1222 char *str = v;
1223 return(pipe1(str, 1));
1226 * Pipe messages, not respecting ignored fields.
1228 int
1229 Pipecmd(void *v)
1231 char *str = v;
1232 return(pipe1(str, 0));
1236 * Print the top so many lines of each desired message.
1237 * The number of lines is taken from the variable "toplines"
1238 * and defaults to 5.
1240 int
1241 top(void *v)
1243 int *msgvec = v, *ip, c, topl, lines, empty_last;
1244 struct message *mp;
1245 char *cp, *linebuf = NULL;
1246 size_t linesize;
1247 FILE *ibuf;
1249 topl = 5;
1250 cp = value("toplines");
1251 if (cp != NULL) {
1252 topl = atoi(cp);
1253 if (topl < 0 || topl > 10000)
1254 topl = 5;
1256 empty_last = 1;
1257 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
1258 mp = &message[*ip - 1];
1259 touch(mp);
1260 setdot(mp);
1261 did_print_dot = TRU1;
1262 if (! empty_last)
1263 printf("\n");
1264 _show_msg_overview(mp, *ip, stdout);
1265 if (mp->m_flag & MNOFROM)
1266 printf("From %s %s\n", fakefrom(mp),
1267 fakedate(mp->m_time));
1268 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL) { /* XXX could use TOP */
1269 v = NULL;
1270 break;
1272 c = mp->m_lines;
1273 for (lines = 0; lines < c && lines <= topl; lines++) {
1274 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1275 break;
1276 puts(linebuf);
1278 for (cp = linebuf; *cp && blankchar(*cp); ++cp)
1280 empty_last = (*cp == '\0');
1284 if (linebuf != NULL)
1285 free(linebuf);
1286 return (v != NULL);
1290 * Touch all the given messages so that they will
1291 * get mboxed.
1293 int
1294 stouch(void *v)
1296 int *msgvec = v;
1297 int *ip;
1299 for (ip = msgvec; *ip != 0; ip++) {
1300 setdot(&message[*ip-1]);
1301 dot->m_flag |= MTOUCH;
1302 dot->m_flag &= ~MPRESERVE;
1304 * POSIX interpretation necessary.
1306 did_print_dot = TRU1;
1308 return(0);
1312 * Make sure all passed messages get mboxed.
1314 int
1315 mboxit(void *v)
1317 int *msgvec = v;
1318 int *ip;
1320 for (ip = msgvec; *ip != 0; ip++) {
1321 setdot(&message[*ip-1]);
1322 dot->m_flag |= MTOUCH|MBOX;
1323 dot->m_flag &= ~MPRESERVE;
1325 * POSIX interpretation necessary.
1327 did_print_dot = TRU1;
1329 return(0);
1333 * List the folders the user currently has.
1335 int
1336 folders(void *v)
1338 char dirname[MAXPATHLEN], *name, **argv = v;
1339 char const *cmd;
1341 if (*argv) {
1342 name = expand(*argv);
1343 if (name == NULL)
1344 return 1;
1345 } else if (! getfold(dirname, sizeof dirname)) {
1346 fprintf(stderr, tr(20, "No value set for \"folder\"\n"));
1347 return 1;
1348 } else
1349 name = dirname;
1351 if (which_protocol(name) == PROTO_IMAP) {
1352 #ifdef HAVE_IMAP
1353 imap_folders(name, *argv == NULL);
1354 #else
1355 return ccmdnotsupp(NULL);
1356 #endif
1357 } else {
1358 if ((cmd = value("LISTER")) == NULL)
1359 cmd = LISTER;
1360 run_command(cmd, 0, -1, -1, name, NULL, NULL);
1362 return 0;