1 /* $NetBSD: format.c,v 1.14 2009/04/10 13:08:24 christos Exp $ */
4 * Copyright (c) 2006 The NetBSD Foundation, Inc.
7 * This code is derived from software contributed to The NetBSD Foundation
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
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>
34 __RCSID("$NetBSD: format.c,v 1.14 2009/04/10 13:08:24 christos Exp $");
35 #endif /* not __lint__ */
49 #define DPRINTF(a) printf a
55 check_bufsize(char **buf
, size_t *bufsize
, char **p
, size_t cnt
)
58 if (*p
+ cnt
< *buf
+ *bufsize
)
61 q
= erealloc(*buf
, *bufsize
);
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' */
73 len
= fmtch
- *fmtbeg
+ sizeof(PRId64
);
75 (void)strlcpy(newfmt
, *fmtbeg
, len
- sizeof(PRId64
) + 1);
76 (void)strlcat(newfmt
, PRId64
, len
);
78 (void)sasprintf(&p
, newfmt
, off
);
83 sfmtint(const char **fmtbeg
, const char *fmtch
, int num
)
89 len
= fmtch
- *fmtbeg
+ 2; /* space for 'd' and '\0' */
91 (void)strlcpy(newfmt
, *fmtbeg
, len
);
92 newfmt
[len
-2] = 'd'; /* convert to printf format */
94 (void)sasprintf(&p
, newfmt
, num
);
99 sfmtstr(const char **fmtbeg
, const char *fmtch
, const char *str
)
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 */
110 (void)sasprintf(&p
, newfmt
, str
? str
: "");
114 #ifdef THREAD_SUPPORT
116 sfmtdepth(char *str
, int depth
)
120 (void)sasprintf(&p
, "%d", depth
);
124 for (/*EMPTY*/; depth
> 0; depth
--)
125 (void)sasprintf(&p
, "%s%s", p
, str
);
131 sfmtfield(const char **fmtbeg
, const char *fmtch
, struct message
*mp
)
134 q
= strchr(fmtch
+ 1, '?');
140 #ifdef THREAD_SUPPORT
147 #ifdef THREAD_SUPPORT
151 switch (fmtch
[1]) { /* check the '?' modifier */
152 #ifdef THREAD_SUPPORT
153 case '&': /* use the relative depth */
154 depth
-= thread_depth();
156 case '*': /* use the absolute depth */
159 (void)strlcpy(p
, fmtch
+ 2, len
);
160 p
= sfmtdepth(p
, depth
);
167 len
= q
- fmtch
- skin_it
;
169 (void)strlcpy(p
, fmtch
+ skin_it
+ 1, len
);
175 str
= sfmtstr(fmtbeg
, fmtch
, p
);
185 int f_new
; /* some message in the thread is new */
186 int f_unread
; /* some message in the thread is unread */
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
);
202 sfmtflag(const char **fmtbeg
, const char *fmtch
, struct message
*mp
)
205 struct flags_s flags
;
211 is_thread
= mp
->m_clink
!= NULL
;
212 disp
[0] = is_thread
? '+' : ' ';
215 flags
.f_and
= mp
->m_flag
;
216 flags
.f_or
= mp
->m_flag
;
219 #ifdef THREAD_SUPPORT
221 get_and_or_flags(mp
->m_clink
, &flags
);
224 if (flags
.f_or
& MTAGGED
)
226 if (flags
.f_and
& MTAGGED
)
229 if (flags
.f_or
& MMODIFY
)
231 if (flags
.f_and
& MMODIFY
)
234 if (flags
.f_or
& MSAVED
)
236 if (flags
.f_and
& MSAVED
)
239 if (flags
.f_or
& MPRESERVE
)
241 if (flags
.f_and
& MPRESERVE
)
246 if ((flags
.f_or
& (MREAD
|MNEW
)) == 0)
251 if ((flags
.f_and
& (MREAD
|MNEW
)) == MNEW
)
254 if (flags
.f_or
& MBOX
)
256 if (flags
.f_and
& MBOX
)
259 return sfmtstr(fmtbeg
, fmtch
, disp
);
263 login_name(const char *addr
)
266 p
= strchr(addr
, '@');
272 (void)strlcpy(q
, addr
, len
);
279 * A simple routine to get around a lint warning.
281 static inline const char *
282 skip_fmt(const char **src
, const char *p
)
289 subformat(const char **src
, struct message
*mp
, const char *addr
,
290 const char *user
, const char *subj
, int tm_isdst
)
293 /* XXX - lint doesn't like this, hence skip_fmt(). */
294 #define MP(a) mp ? a : (*src = (p + 1), NULL)
296 #define MP(a) mp ? a : skip_fmt(src, p + 1);
305 for (p
= *src
; *p
&& !isalpha((unsigned char)*p
) && *p
!= '?'; p
++)
310 * Our format extensions to strftime(3)
313 return sfmtfield(src
, p
, mp
);
315 return MP(sfmtint(src
, p
, (int)(mp
->m_lines
- mp
->m_blines
)));
317 return MP(sfmtint(src
, p
, (int)mp
->m_blines
));
319 return MP(sfmtint(src
, p
, (int)mp
->m_lines
));
321 return sfmtstr(src
, p
, user
);
323 return MP(sfmtoff(src
, p
, mp
->m_size
));
325 return MP(sfmtstr(src
, p
, mp
== dot
? ">" : " "));
327 return MP(sfmtflag(src
, p
, mp
));
329 return sfmtstr(src
, p
, addr
);
331 return sfmtint(src
, p
, get_msgnum(mp
)); /* '0' if mp == NULL */
333 return sfmtstr(src
, p
, login_name(addr
));
335 return sfmtstr(src
, p
, subj
);
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.
350 return "???"; /* XXX - not ideal */
356 return "-0000"; /* consistent with RFC 2822 */
360 /* everything else is handled by strftime(3) */
368 snarf_comment(char **buf
, char *bufend
, const char *string
)
375 q
= buf
? *buf
: NULL
;
376 qend
= buf
? bufend
: NULL
;
379 for (p
= string
+ 1; *p
!= '\0'; p
++) {
380 DPRINTF(("snarf_comment: %s\n", p
));
390 if (*p
== '\\' && p
[1] != 0)
398 DPRINTF(("snarf_comment: terminating: %s\n", *buf
));
407 snarf_quote(char **buf
, char *bufend
, const char *string
)
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')
426 DPRINTF(("snarf_quote: terminating: %s\n", *buf
));
435 * Grab the comments, separating each by a space.
438 get_comments(char *name
)
452 qend
= nbuf
+ sizeof(nbuf
) - 1;
453 for (p
= skip_WSP(name
); *p
!= '\0'; p
++) {
454 DPRINTF(("get_comments: %s\n", p
));
456 case '"': /* quoted-string ... skip it! */
457 p
= snarf_quote(NULL
, NULL
, p
);
461 p
= snarf_comment(&q
, qend
, p
);
463 if (q
< qend
) /* separate comments by space */
472 return savestr(nbuf
);
476 * Convert a possible obs_zone (see RFC 2822, sec 4.3) to a valid
480 convert_obs_zone(const char *obs_zone
)
482 static const struct obs_zone_tbl_s
{
498 const struct obs_zone_tbl_s
*zp
;
500 if (obs_zone
[0] == '+' || obs_zone
[0] == '-')
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':
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':
518 return "-0000"; /* See RFC 2822, sec 4.3 */
523 for (zp
= obs_zone_tbl
; zp
->zone
; zp
++) {
524 if (strcmp(obs_zone
, zp
->zone
) == 0)
531 * Parse the 'Date:" field into a tm structure and return the gmtoff
532 * string or NULL on error.
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)
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
) {
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
)
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
))
600 else if ((tail
= strptime(tail
, " %Y", &tmp_tm
)) == 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
)
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
) {
615 tm
->tm_sec
= tmp_tm
.tm_sec
;
618 tail
= skip_WSP(tail
);
621 * The timezone name is frequently in a comment following the
624 * XXX - this will get overwritten later by timegm(3).
626 if ((p
= strchr(tail
, '(')) != NULL
)
627 tm
->tm_zone
= get_comments(p
);
631 /* what remains should be the gmtoff string */
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().
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 ****************************************************************/
660 struct tm tmp_tm
; /* see comment in date_to_tm() */
664 if ((tail
= strptime(buf
, " %a %b %d %H:%M", &tmp_tm
)) == 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
) {
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) {
685 tm
->tm_zone
= savestr(zone
);
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 */
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.
704 dateof(struct tm
*tm
, struct message
*mp
, int use_hl_date
)
706 static int tzinit
= 0;
710 (void)memset(tm
, 0, sizeof(*tm
));
712 /* Make sure the time zone info is initialized. */
717 if (mp
== NULL
) { /* use local time */
720 (void)localtime_r(&now
, tm
);
725 * See RFC 2822 sec 3.3 for date-time format used in
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
) {
747 * Scan the gmtoff and use it to convert the time to a
750 * Note: "-0000" means no valid zone info. See
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) {
763 if (sign
[0] == '-') {
771 if ((otime
= timegm(tm
)) == (time_t)-1 ||
772 localtime_r(&otime
, tm
) == NULL
) {
774 warnx("cannot convert date: \"%s\"", date
);
778 else { /* Unable to do the conversion to local time. */
780 /* tm->tm_isdst = -1; */ /* Set above */
787 char headline
[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 */
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
||
806 warnx("invalid headline date: `%s'", hl
.l_date
);
811 * Get the sender's address for display. Let nameof() do this.
814 addrof(struct message
*mp
)
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
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
840 * atext = ALPHA / DIGIT / ; Any character except controls,
841 * "!" / "#" / ; SP, and specials.
842 * "$" / "%" / ; Used for atoms
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 "[",
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] ";"
870 * address = mailbox / group
871 ************************************************************************/
873 get_display_name(char *name
)
887 qend
= nbuf
+ sizeof(nbuf
) - 1; /* reserve space for '\0' */
889 for (p
= skip_WSP(name
); *p
!= '\0'; p
++) {
890 DPRINTF(("get_display_name: %s\n", p
));
892 case '"': /* quoted-string */
894 p
= snarf_quote(&q
, qend
, p
);
900 case ':': /* group */
901 case '<': /* angle-address */
904 *lastq
= '\0'; /* NULL termination */
905 return savestr(nbuf
);
907 case '(': /* comment - skip it! */
908 p
= snarf_comment(NULL
, NULL
, p
);
912 if (!quoted
&& q
< qend
) {
915 /* && !is_specials((unsigned char)*p) */)
921 return NULL
; /* no group or angle-address */
925 * See RFC 2822 sec 3.4 and 3.6.2.
928 userof(struct message
*mp
)
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
)
950 * Grab the subject line.
953 subjof(struct message
*mp
)
960 if ((subj
= hfield("subject", mp
)) == NULL
)
961 subj
= hfield("subj", mp
);
966 * Protect a string against strftime() conversion.
969 protect(const char *str
)
974 if (str
== NULL
|| (size
= strlen(str
)) == 0)
977 p
= salloc(2 * size
+ 1);
978 for (q
= p
; *str
; str
++) {
988 preformat(struct tm
*tm
, const char *oldfmt
, struct message
*mp
, int use_hl_date
)
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
);
1006 newfmt
= ecalloc(1, fmtsize
); /* so we can realloc() in check_bufsize() */
1012 fp
= subformat(&p
, mp
, addr
, user
, subj
, tm
->tm_isdst
);
1016 check_bufsize(&newfmt
, &fmtsize
, &q
, len
);
1017 (void)strcpy(q
, fp
);
1022 check_bufsize(&newfmt
, &fmtsize
, &q
, 1);
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 "%??"
1041 smsgprintf(const char *fmtstr
, struct message
*mp
)
1049 if (strncmp(fmtstr
, USE_HL_DATE
, sizeof(USE_HL_DATE
) - 1) != 0)
1053 fmtstr
+= sizeof(USE_HL_DATE
) - 1;
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() */
1065 fmsgprintf(FILE *fo
, const char *fmtstr
, struct message
*mp
)
1069 buf
= smsgprintf(fmtstr
, mp
);
1070 (void)fprintf(fo
, "%s\n", buf
); /* XXX - add the newline here? */