Fix up mix of man(7)/mdoc(7).
[netbsd-mini2440.git] / usr.bin / mail / format.c
blob248ff3457d20389939c6a189399e0f655da80ad3
1 /* $NetBSD: format.c,v 1.14 2009/04/10 13:08:24 christos Exp $ */
3 /*-
4 * Copyright (c) 2006 The NetBSD Foundation, Inc.
5 * All rights reserved.
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Anon Ymous.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
32 #include <sys/cdefs.h>
33 #ifndef __lint__
34 __RCSID("$NetBSD: format.c,v 1.14 2009/04/10 13:08:24 christos Exp $");
35 #endif /* not __lint__ */
37 #include <time.h>
38 #include <stdio.h>
39 #include <util.h>
41 #include "def.h"
42 #include "extern.h"
43 #include "format.h"
44 #include "glob.h"
45 #include "thread.h"
47 #undef DEBUG
48 #ifdef DEBUG
49 #define DPRINTF(a) printf a
50 #else
51 #define DPRINTF(a)
52 #endif
54 static void
55 check_bufsize(char **buf, size_t *bufsize, char **p, size_t cnt)
57 char *q;
58 if (*p + cnt < *buf + *bufsize)
59 return;
60 *bufsize *= 2;
61 q = erealloc(*buf, *bufsize);
62 *p = q + (*p - *buf);
63 *buf = q;
66 static const char *
67 sfmtoff(const char **fmtbeg, const char *fmtch, off_t off)
69 char *newfmt; /* pointer to new format string */
70 size_t len; /* space for "lld" including '\0' */
71 char *p;
73 len = fmtch - *fmtbeg + sizeof(PRId64);
74 newfmt = salloc(len);
75 (void)strlcpy(newfmt, *fmtbeg, len - sizeof(PRId64) + 1);
76 (void)strlcat(newfmt, PRId64, len);
77 *fmtbeg = fmtch + 1;
78 (void)sasprintf(&p, newfmt, off);
79 return p;
82 static const char *
83 sfmtint(const char **fmtbeg, const char *fmtch, int num)
85 char *newfmt;
86 size_t len;
87 char *p;
89 len = fmtch - *fmtbeg + 2; /* space for 'd' and '\0' */
90 newfmt = salloc(len);
91 (void)strlcpy(newfmt, *fmtbeg, len);
92 newfmt[len-2] = 'd'; /* convert to printf format */
93 *fmtbeg = fmtch + 1;
94 (void)sasprintf(&p, newfmt, num);
95 return p;
98 static const char *
99 sfmtstr(const char **fmtbeg, const char *fmtch, const char *str)
101 char *newfmt;
102 size_t len;
103 char *p;
105 len = fmtch - *fmtbeg + 2; /* space for 's' and '\0' */
106 newfmt = salloc(len);
107 (void)strlcpy(newfmt, *fmtbeg, len);
108 newfmt[len-2] = 's'; /* convert to printf format */
109 *fmtbeg = fmtch + 1;
110 (void)sasprintf(&p, newfmt, str ? str : "");
111 return p;
114 #ifdef THREAD_SUPPORT
115 static char*
116 sfmtdepth(char *str, int depth)
118 char *p;
119 if (*str == '\0') {
120 (void)sasprintf(&p, "%d", depth);
121 return p;
123 p = __UNCONST("");
124 for (/*EMPTY*/; depth > 0; depth--)
125 (void)sasprintf(&p, "%s%s", p, str);
126 return p;
128 #endif
130 static const char *
131 sfmtfield(const char **fmtbeg, const char *fmtch, struct message *mp)
133 char *q;
134 q = strchr(fmtch + 1, '?');
135 if (q) {
136 size_t len;
137 char *p;
138 const char *str;
139 int skin_it;
140 #ifdef THREAD_SUPPORT
141 int depth;
142 #endif
143 if (mp == NULL) {
144 *fmtbeg = q + 1;
145 return NULL;
147 #ifdef THREAD_SUPPORT
148 depth = mp->m_depth;
149 #endif
150 skin_it = 0;
151 switch (fmtch[1]) { /* check the '?' modifier */
152 #ifdef THREAD_SUPPORT
153 case '&': /* use the relative depth */
154 depth -= thread_depth();
155 /* FALLTHROUGH */
156 case '*': /* use the absolute depth */
157 len = q - fmtch - 1;
158 p = salloc(len);
159 (void)strlcpy(p, fmtch + 2, len);
160 p = sfmtdepth(p, depth);
161 break;
162 #endif
163 case '-':
164 skin_it = 1;
165 /* FALLTHROUGH */
166 default:
167 len = q - fmtch - skin_it;
168 p = salloc(len);
169 (void)strlcpy(p, fmtch + skin_it + 1, len);
170 p = hfield(p, mp);
171 if (skin_it)
172 p = skin(p);
173 break;
175 str = sfmtstr(fmtbeg, fmtch, p);
176 *fmtbeg = q + 1;
177 return str;
179 return NULL;
182 struct flags_s {
183 int f_and;
184 int f_or;
185 int f_new; /* some message in the thread is new */
186 int f_unread; /* some message in the thread is unread */
189 static void
190 get_and_or_flags(struct message *mp, struct flags_s *flags)
192 for (/*EMPTY*/; mp; mp = mp->m_flink) {
193 flags->f_and &= mp->m_flag;
194 flags->f_or |= mp->m_flag;
195 flags->f_new |= (mp->m_flag & (MREAD|MNEW)) == MNEW;
196 flags->f_unread |= (mp->m_flag & (MREAD|MNEW)) == 0;
197 get_and_or_flags(mp->m_clink, flags);
201 static const char *
202 sfmtflag(const char **fmtbeg, const char *fmtch, struct message *mp)
204 char disp[2];
205 struct flags_s flags;
206 int is_thread;
208 if (mp == NULL)
209 return NULL;
211 is_thread = mp->m_clink != NULL;
212 disp[0] = is_thread ? '+' : ' ';
213 disp[1] = '\0';
215 flags.f_and = mp->m_flag;
216 flags.f_or = mp->m_flag;
217 flags.f_new = 0;
218 flags.f_unread = 0;
219 #ifdef THREAD_SUPPORT
220 if (thread_hidden())
221 get_and_or_flags(mp->m_clink, &flags);
222 #endif
224 if (flags.f_or & MTAGGED)
225 disp[0] = 't';
226 if (flags.f_and & MTAGGED)
227 disp[0] = 'T';
229 if (flags.f_or & MMODIFY)
230 disp[0] = 'e';
231 if (flags.f_and & MMODIFY)
232 disp[0] = 'E';
234 if (flags.f_or & MSAVED)
235 disp[0] = '&';
236 if (flags.f_and & MSAVED)
237 disp[0] = '*';
239 if (flags.f_or & MPRESERVE)
240 disp[0] = 'p';
241 if (flags.f_and & MPRESERVE)
242 disp[0] = 'P';
244 if (flags.f_unread)
245 disp[0] = 'u';
246 if ((flags.f_or & (MREAD|MNEW)) == 0)
247 disp[0] = 'U';
249 if (flags.f_new)
250 disp[0] = 'n';
251 if ((flags.f_and & (MREAD|MNEW)) == MNEW)
252 disp[0] = 'N';
254 if (flags.f_or & MBOX)
255 disp[0] = 'm';
256 if (flags.f_and & MBOX)
257 disp[0] = 'M';
259 return sfmtstr(fmtbeg, fmtch, disp);
262 static const char *
263 login_name(const char *addr)
265 char *p;
266 p = strchr(addr, '@');
267 if (p) {
268 char *q;
269 size_t len;
270 len = p - addr + 1;
271 q = salloc(len);
272 (void)strlcpy(q, addr, len);
273 return q;
275 return addr;
279 * A simple routine to get around a lint warning.
281 static inline const char *
282 skip_fmt(const char **src, const char *p)
284 *src = p;
285 return NULL;
288 static const char *
289 subformat(const char **src, struct message *mp, const char *addr,
290 const char *user, const char *subj, int tm_isdst)
292 #if 0
293 /* XXX - lint doesn't like this, hence skip_fmt(). */
294 #define MP(a) mp ? a : (*src = (p + 1), NULL)
295 #else
296 #define MP(a) mp ? a : skip_fmt(src, p + 1);
297 #endif
298 const char *p;
300 p = *src;
301 if (p[1] == '%') {
302 *src += 2;
303 return "%%";
305 for (p = *src; *p && !isalpha((unsigned char)*p) && *p != '?'; p++)
306 continue;
308 switch (*p) {
310 * Our format extensions to strftime(3)
312 case '?':
313 return sfmtfield(src, p, mp);
314 case 'J':
315 return MP(sfmtint(src, p, (int)(mp->m_lines - mp->m_blines)));
316 case 'K':
317 return MP(sfmtint(src, p, (int)mp->m_blines));
318 case 'L':
319 return MP(sfmtint(src, p, (int)mp->m_lines));
320 case 'N':
321 return sfmtstr(src, p, user);
322 case 'O':
323 return MP(sfmtoff(src, p, mp->m_size));
324 case 'P':
325 return MP(sfmtstr(src, p, mp == dot ? ">" : " "));
326 case 'Q':
327 return MP(sfmtflag(src, p, mp));
328 case 'f':
329 return sfmtstr(src, p, addr);
330 case 'i':
331 return sfmtint(src, p, get_msgnum(mp)); /* '0' if mp == NULL */
332 case 'n':
333 return sfmtstr(src, p, login_name(addr));
334 case 'q':
335 return sfmtstr(src, p, subj);
336 case 't':
337 return sfmtint(src, p, get_msgCount());
340 * strftime(3) special cases:
342 * When 'tm_isdst' was not determined (i.e., < 0), a C99
343 * compliant strftime(3) will output an empty string for the
344 * "%Z" and "%z" formats. This messes up alignment so we
345 * handle these ourselves.
347 case 'Z':
348 if (tm_isdst < 0) {
349 *src = p + 1;
350 return "???"; /* XXX - not ideal */
352 return NULL;
353 case 'z':
354 if (tm_isdst < 0) {
355 *src = p + 1;
356 return "-0000"; /* consistent with RFC 2822 */
358 return NULL;
360 /* everything else is handled by strftime(3) */
361 default:
362 return NULL;
364 #undef MP
367 static const char *
368 snarf_comment(char **buf, char *bufend, const char *string)
370 const char *p;
371 char *q;
372 char *qend;
373 int clevel;
375 q = buf ? *buf : NULL;
376 qend = buf ? bufend : NULL;
378 clevel = 1;
379 for (p = string + 1; *p != '\0'; p++) {
380 DPRINTF(("snarf_comment: %s\n", p));
381 if (*p == '(') {
382 clevel++;
383 continue;
385 if (*p == ')') {
386 if (--clevel == 0)
387 break;
388 continue;
390 if (*p == '\\' && p[1] != 0)
391 p++;
393 if (q < qend)
394 *q++ = *p;
396 if (buf) {
397 *q = '\0';
398 DPRINTF(("snarf_comment: terminating: %s\n", *buf));
399 *buf = q;
401 if (*p == '\0')
402 p--;
403 return p;
406 static const char *
407 snarf_quote(char **buf, char *bufend, const char *string)
409 const char *p;
410 char *q;
411 char *qend;
413 q = buf ? *buf : NULL;
414 qend = buf ? bufend : NULL;
416 for (p = string + 1; *p != '\0' && *p != '"'; p++) {
417 DPRINTF(("snarf_quote: %s\n", p));
418 if (*p == '\\' && p[1] != '\0')
419 p++;
421 if (q < qend)
422 *q++ = *p;
424 if (buf) {
425 *q = '\0';
426 DPRINTF(("snarf_quote: terminating: %s\n", *buf));
427 *buf = q;
429 if (*p == '\0')
430 p--;
431 return p;
435 * Grab the comments, separating each by a space.
437 static char *
438 get_comments(char *name)
440 char nbuf[LINESIZE];
441 const char *p;
442 char *qend;
443 char *q;
444 char *lastq;
446 if (name == NULL)
447 return NULL;
449 p = name;
450 q = nbuf;
451 lastq = nbuf;
452 qend = nbuf + sizeof(nbuf) - 1;
453 for (p = skip_WSP(name); *p != '\0'; p++) {
454 DPRINTF(("get_comments: %s\n", p));
455 switch (*p) {
456 case '"': /* quoted-string ... skip it! */
457 p = snarf_quote(NULL, NULL, p);
458 break;
460 case '(':
461 p = snarf_comment(&q, qend, p);
462 lastq = q;
463 if (q < qend) /* separate comments by space */
464 *q++ = ' ';
465 break;
467 default:
468 break;
471 *lastq = '\0';
472 return savestr(nbuf);
476 * Convert a possible obs_zone (see RFC 2822, sec 4.3) to a valid
477 * gmtoff string.
479 static const char *
480 convert_obs_zone(const char *obs_zone)
482 static const struct obs_zone_tbl_s {
483 const char *zone;
484 const char *gmtoff;
485 } obs_zone_tbl[] = {
486 {"UT", "+0000"},
487 {"GMT", "+0000"},
488 {"EST", "-0500"},
489 {"EDT", "-0400"},
490 {"CST", "-0600"},
491 {"CDT", "-0500"},
492 {"MST", "-0700"},
493 {"MDT", "-0600"},
494 {"PST", "-0800"},
495 {"PDT", "-0700"},
496 {NULL, NULL},
498 const struct obs_zone_tbl_s *zp;
500 if (obs_zone[0] == '+' || obs_zone[0] == '-')
501 return obs_zone;
503 if (obs_zone[1] == 0) { /* possible military zones */
504 /* be explicit here - avoid C extensions and ctype(3) */
505 switch((unsigned char)obs_zone[0]) {
506 case 'A': case 'B': case 'C': case 'D': case 'E':
507 case 'F': case 'G': case 'H': case 'I':
508 case 'K': case 'L': case 'M': case 'N': case 'O':
509 case 'P': case 'Q': case 'R': case 'S': case 'T':
510 case 'U': case 'V': case 'W': case 'X': case 'Y':
511 case 'Z':
512 case 'a': case 'b': case 'c': case 'd': case 'e':
513 case 'f': case 'g': case 'h': case 'i':
514 case 'k': case 'l': case 'm': case 'n': case 'o':
515 case 'p': case 'q': case 'r': case 's': case 't':
516 case 'u': case 'v': case 'w': case 'x': case 'y':
517 case 'z':
518 return "-0000"; /* See RFC 2822, sec 4.3 */
519 default:
520 return obs_zone;
523 for (zp = obs_zone_tbl; zp->zone; zp++) {
524 if (strcmp(obs_zone, zp->zone) == 0)
525 return zp->gmtoff;
527 return obs_zone;
531 * Parse the 'Date:" field into a tm structure and return the gmtoff
532 * string or NULL on error.
534 static const char *
535 date_to_tm(char *date, struct tm *tm)
537 /****************************************************************
538 * The header 'date-time' syntax - From RFC 2822 sec 3.3 and 4.3:
540 * date-time = [ day-of-week "," ] date FWS time [CFWS]
541 * day-of-week = ([FWS] day-name) / obs-day-of-week
542 * day-name = "Mon" / "Tue" / "Wed" / "Thu" /
543 * "Fri" / "Sat" / "Sun"
544 * date = day month year
545 * year = 4*DIGIT / obs-year
546 * month = (FWS month-name FWS) / obs-month
547 * month-name = "Jan" / "Feb" / "Mar" / "Apr" /
548 * "May" / "Jun" / "Jul" / "Aug" /
549 * "Sep" / "Oct" / "Nov" / "Dec"
550 * day = ([FWS] 1*2DIGIT) / obs-day
551 * time = time-of-day FWS zone
552 * time-of-day = hour ":" minute [ ":" second ]
553 * hour = 2DIGIT / obs-hour
554 * minute = 2DIGIT / obs-minute
555 * second = 2DIGIT / obs-second
556 * zone = (( "+" / "-" ) 4DIGIT) / obs-zone
558 * obs-day-of-week = [CFWS] day-name [CFWS]
559 * obs-year = [CFWS] 2*DIGIT [CFWS]
560 * obs-month = CFWS month-name CFWS
561 * obs-day = [CFWS] 1*2DIGIT [CFWS]
562 * obs-hour = [CFWS] 2DIGIT [CFWS]
563 * obs-minute = [CFWS] 2DIGIT [CFWS]
564 * obs-second = [CFWS] 2DIGIT [CFWS]
565 ****************************************************************/
567 * For example, a typical date might look like:
569 * Date: Mon, 1 Oct 2007 05:38:10 +0000 (UTC)
571 char *tail;
572 char *p;
573 struct tm tmp_tm;
575 * NOTE: Rather than depend on strptime(3) modifying only
576 * those fields specified in its format string, we use tmp_tm
577 * and copy the appropriate result to tm. This is not
578 * required with the NetBSD strptime(3) implementation.
581 /* Check for an optional 'day-of-week' */
582 if ((tail = strptime(date, " %a,", &tmp_tm)) == NULL) {
583 tail = date;
584 tm->tm_wday = tmp_tm.tm_wday;
587 /* Get the required 'day' and 'month' */
588 if ((tail = strptime(tail, " %d %b", &tmp_tm)) == NULL)
589 return NULL;
591 tm->tm_mday = tmp_tm.tm_mday;
592 tm->tm_mon = tmp_tm.tm_mon;
594 /* Check for 'obs-year' (2 digits) before 'year' (4 digits) */
595 /* XXX - Portable? This depends on strptime not scanning off
596 * trailing whitespace unless specified in the format string.
598 if ((p = strptime(tail, " %y", &tmp_tm)) != NULL && is_WSP(*p))
599 tail = p;
600 else if ((tail = strptime(tail, " %Y", &tmp_tm)) == NULL)
601 return NULL;
603 tm->tm_year = tmp_tm.tm_year;
605 /* Get the required 'hour' and 'minute' */
606 if ((tail = strptime(tail, " %H:%M", &tmp_tm)) == NULL)
607 return NULL;
609 tm->tm_hour = tmp_tm.tm_hour;
610 tm->tm_min = tmp_tm.tm_min;
612 /* Check for an optional 'seconds' field */
613 if ((p = strptime(tail, ":%S", &tmp_tm)) != NULL) {
614 tail = p;
615 tm->tm_sec = tmp_tm.tm_sec;
618 tail = skip_WSP(tail);
621 * The timezone name is frequently in a comment following the
622 * zone offset.
624 * XXX - this will get overwritten later by timegm(3).
626 if ((p = strchr(tail, '(')) != NULL)
627 tm->tm_zone = get_comments(p);
628 else
629 tm->tm_zone = NULL;
631 /* what remains should be the gmtoff string */
632 tail = skin(tail);
633 return convert_obs_zone(tail);
637 * Parse the headline string into a tm structure. Returns a pointer
638 * to first non-whitespace after the date or NULL on error.
640 * XXX - This needs to be consistent with isdate().
642 static char *
643 hl_date_to_tm(const char *buf, struct tm *tm)
645 /****************************************************************
646 * The BSD and System V headline date formats differ
647 * and each have an optional timezone field between
648 * the time and date (see head.c). Unfortunately,
649 * strptime(3) doesn't know about timezone fields, so
650 * we have to handle it ourselves.
652 * char ctype[] = "Aaa Aaa O0 00:00:00 0000";
653 * char tmztype[] = "Aaa Aaa O0 00:00:00 AAA 0000";
654 * char SysV_ctype[] = "Aaa Aaa O0 00:00 0000";
655 * char SysV_tmztype[] = "Aaa Aaa O0 00:00 AAA 0000";
656 ****************************************************************/
657 char *tail;
658 char *p;
659 char zone[4];
660 struct tm tmp_tm; /* see comment in date_to_tm() */
661 int len;
663 zone[0] = '\0';
664 if ((tail = strptime(buf, " %a %b %d %H:%M", &tmp_tm)) == NULL)
665 return NULL;
667 tm->tm_wday = tmp_tm.tm_wday;
668 tm->tm_mday = tmp_tm.tm_mday;
669 tm->tm_mon = tmp_tm.tm_mon;
670 tm->tm_hour = tmp_tm.tm_hour;
671 tm->tm_min = tmp_tm.tm_min;
673 /* Check for an optional 'seconds' field */
674 if ((p = strptime(tail, ":%S", &tmp_tm)) != NULL) {
675 tail = p;
676 tm->tm_sec = tmp_tm.tm_sec;
679 /* Grab an optional timezone name */
681 * XXX - Is the zone name always 3 characters as in isdate()?
683 if (sscanf(tail, " %3[A-Z] %n", zone, &len) == 1) {
684 if (zone[0])
685 tm->tm_zone = savestr(zone);
686 tail += len;
689 /* Grab the required year field */
690 tail = strptime(tail, " %Y ", &tmp_tm);
691 tm->tm_year = tmp_tm.tm_year; /* save this even if it failed */
693 return tail;
697 * Get the date and time info from the "Date:" line, parse it into a
698 * tm structure as much as possible.
700 * Note: We return the gmtoff as a string as "-0000" has special
701 * meaning. See RFC 2822, sec 3.3.
703 PUBLIC void
704 dateof(struct tm *tm, struct message *mp, int use_hl_date)
706 static int tzinit = 0;
707 char *date = NULL;
708 const char *gmtoff;
710 (void)memset(tm, 0, sizeof(*tm));
712 /* Make sure the time zone info is initialized. */
713 if (!tzinit) {
714 tzinit = 1;
715 tzset();
717 if (mp == NULL) { /* use local time */
718 time_t now;
719 (void)time(&now);
720 (void)localtime_r(&now, tm);
721 return;
725 * See RFC 2822 sec 3.3 for date-time format used in
726 * the "Date:" field.
728 * NOTE: The range for the time is 00:00 to 23:60 (to allow
729 * for a leep second), but I have seen this violated making
730 * strptime() fail, e.g.,
732 * Date: Tue, 24 Oct 2006 24:07:58 +0400
734 * In this case we (silently) fall back to the headline time
735 * which was written locally when the message was received.
736 * Of course, this is not the same time as in the Date field.
738 if (use_hl_date == 0 &&
739 (date = hfield("date", mp)) != NULL &&
740 (gmtoff = date_to_tm(date, tm)) != NULL) {
741 int hour;
742 int min;
743 char sign[2];
744 struct tm save_tm;
747 * Scan the gmtoff and use it to convert the time to a
748 * local time.
750 * Note: "-0000" means no valid zone info. See
751 * RFC 2822, sec 3.3.
753 * XXX - This is painful! Is there a better way?
756 tm->tm_isdst = -1; /* let timegm(3) determine tm_isdst */
757 save_tm = *tm; /* use this if we fail */
759 if (strcmp(gmtoff, "-0000") != 0 &&
760 sscanf(gmtoff, " %1[+-]%2d%2d ", sign, &hour, &min) == 3) {
761 time_t otime;
763 if (sign[0] == '-') {
764 tm->tm_hour += hour;
765 tm->tm_min += min;
767 else {
768 tm->tm_hour -= hour;
769 tm->tm_min -= min;
771 if ((otime = timegm(tm)) == (time_t)-1 ||
772 localtime_r(&otime, tm) == NULL) {
773 if (debug)
774 warnx("cannot convert date: \"%s\"", date);
775 *tm = save_tm;
778 else { /* Unable to do the conversion to local time. */
779 *tm = save_tm;
780 /* tm->tm_isdst = -1; */ /* Set above */
781 tm->tm_gmtoff = 0;
782 tm->tm_zone = NULL;
785 else {
786 struct headline hl;
787 char headline[LINESIZE];
788 char pbuf[LINESIZE];
790 if (debug && use_hl_date == 0)
791 warnx("invalid date: \"%s\"", date ? date : "<null>");
794 * The headline is written locally so failures here
795 * should be seen (i.e., not conditional on 'debug').
797 tm->tm_isdst = -1; /* let mktime(3) determine tm_isdst */
798 headline[0] = '\0';
799 (void)readline(setinput(mp), headline, (int)sizeof(headline), 0);
800 parse(headline, &hl, pbuf);
801 if (hl.l_date == NULL)
802 warnx("invalid headline: `%s'", headline);
804 else if (hl_date_to_tm(hl.l_date, tm) == NULL ||
805 mktime(tm) == -1)
806 warnx("invalid headline date: `%s'", hl.l_date);
811 * Get the sender's address for display. Let nameof() do this.
813 static const char *
814 addrof(struct message *mp)
816 if (mp == NULL)
817 return NULL;
819 return nameof(mp, 0);
822 /************************************************************************
823 * The 'address' syntax - from RFC 2822:
825 * specials = "(" / ")" / ; Special characters used in
826 * "<" / ">" / ; other parts of the syntax
827 * "[" / "]" /
828 * ":" / ";" /
829 * "@" / "\" /
830 * "," / "." /
831 * DQUOTE
832 * qtext = NO-WS-CTL / ; Non white space controls
833 * %d33 / ; The rest of the US-ASCII
834 * %d35-91 / ; characters not including "\"
835 * %d93-126 ; or the quote character
836 * qcontent = qtext / quoted-pair
837 * quoted-string = [CFWS]
838 * DQUOTE *([FWS] qcontent) [FWS] DQUOTE
839 * [CFWS]
840 * atext = ALPHA / DIGIT / ; Any character except controls,
841 * "!" / "#" / ; SP, and specials.
842 * "$" / "%" / ; Used for atoms
843 * "&" / "'" /
844 * "*" / "+" /
845 * "-" / "/" /
846 * "=" / "?" /
847 * "^" / "_" /
848 * "`" / "{" /
849 * "|" / "}" /
850 * "~"
851 * atom = [CFWS] 1*atext [CFWS]
852 * word = atom / quoted-string
853 * phrase = 1*word / obs-phrase
854 * display-name = phrase
855 * dtext = NO-WS-CTL / ; Non white space controls
856 * %d33-90 / ; The rest of the US-ASCII
857 * %d94-126 ; characters not including "[",
858 * ; "]", or "\"
859 * dcontent = dtext / quoted-pair
860 * domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
861 * domain = dot-atom / domain-literal / obs-domain
862 * local-part = dot-atom / quoted-string / obs-local-part
863 * addr-spec = local-part "@" domain
864 * angle-addr = [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr
865 * name-addr = [display-name] angle-addr
866 * mailbox = name-addr / addr-spec
867 * mailbox-list = (mailbox *("," mailbox)) / obs-mbox-list
868 * group = display-name ":" [mailbox-list / CFWS] ";"
869 * [CFWS]
870 * address = mailbox / group
871 ************************************************************************/
872 static char *
873 get_display_name(char *name)
875 char nbuf[LINESIZE];
876 const char *p;
877 char *q;
878 char *qend;
879 char *lastq;
880 int quoted;
882 if (name == NULL)
883 return NULL;
885 q = nbuf;
886 lastq = nbuf;
887 qend = nbuf + sizeof(nbuf) - 1; /* reserve space for '\0' */
888 quoted = 0;
889 for (p = skip_WSP(name); *p != '\0'; p++) {
890 DPRINTF(("get_display_name: %s\n", p));
891 switch (*p) {
892 case '"': /* quoted-string */
893 q = nbuf;
894 p = snarf_quote(&q, qend, p);
895 if (!quoted)
896 lastq = q;
897 quoted = 1;
898 break;
900 case ':': /* group */
901 case '<': /* angle-address */
902 if (lastq == nbuf)
903 return NULL;
904 *lastq = '\0'; /* NULL termination */
905 return savestr(nbuf);
907 case '(': /* comment - skip it! */
908 p = snarf_comment(NULL, NULL, p);
909 break;
911 default:
912 if (!quoted && q < qend) {
913 *q++ = *p;
914 if (!is_WSP(*p)
915 /* && !is_specials((unsigned char)*p) */)
916 lastq = q;
918 break;
921 return NULL; /* no group or angle-address */
925 * See RFC 2822 sec 3.4 and 3.6.2.
927 static const char *
928 userof(struct message *mp)
930 char *sender;
931 char *dispname;
933 if (mp == NULL)
934 return NULL;
936 if ((sender = hfield("from", mp)) != NULL ||
937 (sender = hfield("sender", mp)) != NULL)
939 * Try to get the display-name. If one doesn't exist,
940 * then the best we can hope for is that the user's
941 * name is in the comments.
943 if ((dispname = get_display_name(sender)) != NULL ||
944 (dispname = get_comments(sender)) != NULL)
945 return dispname;
946 return NULL;
950 * Grab the subject line.
952 static const char *
953 subjof(struct message *mp)
955 const char *subj;
957 if (mp == NULL)
958 return NULL;
960 if ((subj = hfield("subject", mp)) == NULL)
961 subj = hfield("subj", mp);
962 return subj;
966 * Protect a string against strftime() conversion.
968 static const char*
969 protect(const char *str)
971 char *p, *q;
972 size_t size;
974 if (str == NULL || (size = strlen(str)) == 0)
975 return str;
977 p = salloc(2 * size + 1);
978 for (q = p; *str; str++) {
979 *q = *str;
980 if (*q++ == '%')
981 *q++ = '%';
983 *q = '\0';
984 return p;
987 static char *
988 preformat(struct tm *tm, const char *oldfmt, struct message *mp, int use_hl_date)
990 const char *subj;
991 const char *addr;
992 const char *user;
993 const char *p;
994 char *q;
995 char *newfmt;
996 size_t fmtsize;
998 if (mp != NULL && (mp->m_flag & MDELETED) != 0)
999 mp = NULL; /* deleted mail shouldn't show up! */
1001 subj = protect(subjof(mp));
1002 addr = protect(addrof(mp));
1003 user = protect(userof(mp));
1004 dateof(tm, mp, use_hl_date);
1005 fmtsize = LINESIZE;
1006 newfmt = ecalloc(1, fmtsize); /* so we can realloc() in check_bufsize() */
1007 q = newfmt;
1008 p = oldfmt;
1009 while (*p) {
1010 if (*p == '%') {
1011 const char *fp;
1012 fp = subformat(&p, mp, addr, user, subj, tm->tm_isdst);
1013 if (fp) {
1014 size_t len;
1015 len = strlen(fp);
1016 check_bufsize(&newfmt, &fmtsize, &q, len);
1017 (void)strcpy(q, fp);
1018 q += len;
1019 continue;
1022 check_bufsize(&newfmt, &fmtsize, &q, 1);
1023 *q++ = *p++;
1025 *q = '\0';
1027 return newfmt;
1031 * If a format string begins with the USE_HL_DATE string, smsgprintf
1032 * will use the headerline date rather than trying to extract the date
1033 * from the Date field.
1035 * Note: If a 'valid' date cannot be extracted from the Date field,
1036 * then the headline date is used.
1038 #define USE_HL_DATE "%??"
1040 PUBLIC char *
1041 smsgprintf(const char *fmtstr, struct message *mp)
1043 struct tm tm;
1044 int use_hl_date;
1045 char *newfmt;
1046 char *buf;
1047 size_t bufsize;
1049 if (strncmp(fmtstr, USE_HL_DATE, sizeof(USE_HL_DATE) - 1) != 0)
1050 use_hl_date = 0;
1051 else {
1052 use_hl_date = 1;
1053 fmtstr += sizeof(USE_HL_DATE) - 1;
1055 bufsize = LINESIZE;
1056 buf = salloc(bufsize);
1057 newfmt = preformat(&tm, fmtstr, mp, use_hl_date);
1058 (void)strftime(buf, bufsize, newfmt, &tm);
1059 free(newfmt); /* preformat() uses malloc()/realloc() */
1060 return buf;
1064 PUBLIC void
1065 fmsgprintf(FILE *fo, const char *fmtstr, struct message *mp)
1067 char *buf;
1069 buf = smsgprintf(fmtstr, mp);
1070 (void)fprintf(fo, "%s\n", buf); /* XXX - add the newline here? */