/* $NetBSD: format.c,v 1.15 2009/04/11 14:22:32 christos Exp $ */ /*- * Copyright (c) 2006 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Anon Ymous. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #ifndef __lint__ __RCSID("$NetBSD: format.c,v 1.15 2009/04/11 14:22:32 christos Exp $"); #endif /* not __lint__ */ #include #include #include #include "def.h" #include "extern.h" #include "format.h" #include "glob.h" #include "thread.h" #undef DEBUG #ifdef DEBUG #define DPRINTF(a) printf a #else #define DPRINTF(a) #endif static void check_bufsize(char **buf, size_t *bufsize, char **p, size_t cnt) { char *q; if (*p + cnt < *buf + *bufsize) return; *bufsize *= 2; q = erealloc(*buf, *bufsize); *p = q + (*p - *buf); *buf = q; } static const char * sfmtoff(const char **fmtbeg, const char *fmtch, off_t off) { char *newfmt; /* pointer to new format string */ size_t len; /* space for "lld" including '\0' */ char *p; len = fmtch - *fmtbeg + sizeof(PRId64); newfmt = salloc(len); (void)strlcpy(newfmt, *fmtbeg, len - sizeof(PRId64) + 1); (void)strlcat(newfmt, PRId64, len); *fmtbeg = fmtch + 1; (void)sasprintf(&p, newfmt, off); return p; } static const char * sfmtint(const char **fmtbeg, const char *fmtch, int num) { char *newfmt; size_t len; char *p; len = fmtch - *fmtbeg + 2; /* space for 'd' and '\0' */ newfmt = salloc(len); (void)strlcpy(newfmt, *fmtbeg, len); newfmt[len-2] = 'd'; /* convert to printf format */ *fmtbeg = fmtch + 1; (void)sasprintf(&p, newfmt, num); return p; } static const char * sfmtstr(const char **fmtbeg, const char *fmtch, const char *str) { char *newfmt; size_t len; char *p; len = fmtch - *fmtbeg + 2; /* space for 's' and '\0' */ newfmt = salloc(len); (void)strlcpy(newfmt, *fmtbeg, len); newfmt[len-2] = 's'; /* convert to printf format */ *fmtbeg = fmtch + 1; (void)sasprintf(&p, newfmt, str ? str : ""); return p; } #ifdef THREAD_SUPPORT static char* sfmtdepth(char *str, int depth) { char *p; if (*str == '\0') { (void)sasprintf(&p, "%d", depth); return p; } p = __UNCONST(""); for (/*EMPTY*/; depth > 0; depth--) (void)sasprintf(&p, "%s%s", p, str); return p; } #endif static const char * sfmtfield(const char **fmtbeg, const char *fmtch, struct message *mp) { char *q; q = strchr(fmtch + 1, '?'); if (q) { size_t len; char *p; const char *str; int skin_it; #ifdef THREAD_SUPPORT int depth; #endif if (mp == NULL) { *fmtbeg = q + 1; return NULL; } #ifdef THREAD_SUPPORT depth = mp->m_depth; #endif skin_it = 0; switch (fmtch[1]) { /* check the '?' modifier */ #ifdef THREAD_SUPPORT case '&': /* use the relative depth */ depth -= thread_depth(); /* FALLTHROUGH */ case '*': /* use the absolute depth */ len = q - fmtch - 1; p = salloc(len); (void)strlcpy(p, fmtch + 2, len); p = sfmtdepth(p, depth); break; #endif case '-': skin_it = 1; /* FALLTHROUGH */ default: len = q - fmtch - skin_it; p = salloc(len); (void)strlcpy(p, fmtch + skin_it + 1, len); p = hfield(p, mp); if (skin_it) p = skin(p); break; } str = sfmtstr(fmtbeg, fmtch, p); *fmtbeg = q + 1; return str; } return NULL; } struct flags_s { int f_and; int f_or; int f_new; /* some message in the thread is new */ int f_unread; /* some message in the thread is unread */ }; static void get_and_or_flags(struct message *mp, struct flags_s *flags) { for (/*EMPTY*/; mp; mp = mp->m_flink) { flags->f_and &= mp->m_flag; flags->f_or |= mp->m_flag; flags->f_new |= (mp->m_flag & (MREAD|MNEW)) == MNEW; flags->f_unread |= (mp->m_flag & (MREAD|MNEW)) == 0; get_and_or_flags(mp->m_clink, flags); } } static const char * sfmtflag(const char **fmtbeg, const char *fmtch, struct message *mp) { char disp[2]; struct flags_s flags; int is_thread; if (mp == NULL) return NULL; is_thread = mp->m_clink != NULL; disp[0] = is_thread ? '+' : ' '; disp[1] = '\0'; flags.f_and = mp->m_flag; flags.f_or = mp->m_flag; flags.f_new = 0; flags.f_unread = 0; #ifdef THREAD_SUPPORT if (thread_hidden()) get_and_or_flags(mp->m_clink, &flags); #endif if (flags.f_or & MTAGGED) disp[0] = 't'; if (flags.f_and & MTAGGED) disp[0] = 'T'; if (flags.f_or & MMODIFY) disp[0] = 'e'; if (flags.f_and & MMODIFY) disp[0] = 'E'; if (flags.f_or & MSAVED) disp[0] = '&'; if (flags.f_and & MSAVED) disp[0] = '*'; if (flags.f_or & MPRESERVE) disp[0] = 'p'; if (flags.f_and & MPRESERVE) disp[0] = 'P'; if (flags.f_unread) disp[0] = 'u'; if ((flags.f_or & (MREAD|MNEW)) == 0) disp[0] = 'U'; if (flags.f_new) disp[0] = 'n'; if ((flags.f_and & (MREAD|MNEW)) == MNEW) disp[0] = 'N'; if (flags.f_or & MBOX) disp[0] = 'm'; if (flags.f_and & MBOX) disp[0] = 'M'; return sfmtstr(fmtbeg, fmtch, disp); } static const char * login_name(const char *addr) { char *p; p = strchr(addr, '@'); if (p) { char *q; size_t len; len = p - addr + 1; q = salloc(len); (void)strlcpy(q, addr, len); return q; } return addr; } /* * A simple routine to get around a lint warning. */ static inline const char * skip_fmt(const char **src, const char *p) { *src = p; return NULL; } static const char * subformat(const char **src, struct message *mp, const char *addr, const char *user, const char *subj, int tm_isdst) { #if 0 /* XXX - lint doesn't like this, hence skip_fmt(). */ #define MP(a) mp ? a : (*src = (p + 1), NULL) #else #define MP(a) mp ? a : skip_fmt(src, p + 1); #endif const char *p; p = *src; if (p[1] == '%') { *src += 2; return "%%"; } for (p = *src; *p && !isalpha((unsigned char)*p) && *p != '?'; p++) continue; switch (*p) { /* * Our format extensions to strftime(3) */ case '?': return sfmtfield(src, p, mp); case 'J': return MP(sfmtint(src, p, (int)(mp->m_lines - mp->m_blines))); case 'K': return MP(sfmtint(src, p, (int)mp->m_blines)); case 'L': return MP(sfmtint(src, p, (int)mp->m_lines)); case 'N': return sfmtstr(src, p, user); case 'O': return MP(sfmtoff(src, p, mp->m_size)); case 'P': return MP(sfmtstr(src, p, mp == dot ? ">" : " ")); case 'Q': return MP(sfmtflag(src, p, mp)); case 'f': return sfmtstr(src, p, addr); case 'i': return sfmtint(src, p, get_msgnum(mp)); /* '0' if mp == NULL */ case 'n': return sfmtstr(src, p, login_name(addr)); case 'q': return sfmtstr(src, p, subj); case 't': return sfmtint(src, p, get_msgCount()); /* * strftime(3) special cases: * * When 'tm_isdst' was not determined (i.e., < 0), a C99 * compliant strftime(3) will output an empty string for the * "%Z" and "%z" formats. This messes up alignment so we * handle these ourselves. */ case 'Z': if (tm_isdst < 0) { *src = p + 1; return "???"; /* XXX - not ideal */ } return NULL; case 'z': if (tm_isdst < 0) { *src = p + 1; return "-0000"; /* consistent with RFC 2822 */ } return NULL; /* everything else is handled by strftime(3) */ default: return NULL; } #undef MP } static const char * snarf_comment(char **buf, char *bufend, const char *string) { const char *p; char *q; char *qend; int clevel; q = buf ? *buf : NULL; qend = buf ? bufend : NULL; clevel = 1; for (p = string + 1; *p != '\0'; p++) { DPRINTF(("snarf_comment: %s\n", p)); if (*p == '(') { clevel++; continue; } if (*p == ')') { if (--clevel == 0) break; continue; } if (*p == '\\' && p[1] != 0) p++; if (q < qend) *q++ = *p; } if (buf) { *q = '\0'; DPRINTF(("snarf_comment: terminating: %s\n", *buf)); *buf = q; } if (*p == '\0') p--; return p; } static const char * snarf_quote(char **buf, char *bufend, const char *string) { const char *p; char *q; char *qend; q = buf ? *buf : NULL; qend = buf ? bufend : NULL; for (p = string + 1; *p != '\0' && *p != '"'; p++) { DPRINTF(("snarf_quote: %s\n", p)); if (*p == '\\' && p[1] != '\0') p++; if (q < qend) *q++ = *p; } if (buf) { *q = '\0'; DPRINTF(("snarf_quote: terminating: %s\n", *buf)); *buf = q; } if (*p == '\0') p--; return p; } /* * Grab the comments, separating each by a space. */ static char * get_comments(char *name) { char nbuf[LINESIZE]; const char *p; char *qend; char *q; char *lastq; if (name == NULL) return NULL; p = name; q = nbuf; lastq = nbuf; qend = nbuf + sizeof(nbuf) - 1; for (p = skip_WSP(name); *p != '\0'; p++) { DPRINTF(("get_comments: %s\n", p)); switch (*p) { case '"': /* quoted-string ... skip it! */ p = snarf_quote(NULL, NULL, p); break; case '(': p = snarf_comment(&q, qend, p); lastq = q; if (q < qend) /* separate comments by space */ *q++ = ' '; break; default: break; } } *lastq = '\0'; return savestr(nbuf); } /* * Convert a possible obs_zone (see RFC 2822, sec 4.3) to a valid * gmtoff string. */ static const char * convert_obs_zone(const char *obs_zone) { static const struct obs_zone_tbl_s { const char *zone; const char *gmtoff; } obs_zone_tbl[] = { {"UT", "+0000"}, {"GMT", "+0000"}, {"EST", "-0500"}, {"EDT", "-0400"}, {"CST", "-0600"}, {"CDT", "-0500"}, {"MST", "-0700"}, {"MDT", "-0600"}, {"PST", "-0800"}, {"PDT", "-0700"}, {NULL, NULL}, }; const struct obs_zone_tbl_s *zp; if (obs_zone[0] == '+' || obs_zone[0] == '-') return obs_zone; if (obs_zone[1] == 0) { /* possible military zones */ /* be explicit here - avoid C extensions and ctype(3) */ switch((unsigned char)obs_zone[0]) { case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': return "-0000"; /* See RFC 2822, sec 4.3 */ default: return obs_zone; } } for (zp = obs_zone_tbl; zp->zone; zp++) { if (strcmp(obs_zone, zp->zone) == 0) return zp->gmtoff; } return obs_zone; } /* * Parse the 'Date:" field into a tm structure and return the gmtoff * string or NULL on error. */ static const char * date_to_tm(char *date, struct tm *tm) { /**************************************************************** * The header 'date-time' syntax - From RFC 2822 sec 3.3 and 4.3: * * date-time = [ day-of-week "," ] date FWS time [CFWS] * day-of-week = ([FWS] day-name) / obs-day-of-week * day-name = "Mon" / "Tue" / "Wed" / "Thu" / * "Fri" / "Sat" / "Sun" * date = day month year * year = 4*DIGIT / obs-year * month = (FWS month-name FWS) / obs-month * month-name = "Jan" / "Feb" / "Mar" / "Apr" / * "May" / "Jun" / "Jul" / "Aug" / * "Sep" / "Oct" / "Nov" / "Dec" * day = ([FWS] 1*2DIGIT) / obs-day * time = time-of-day FWS zone * time-of-day = hour ":" minute [ ":" second ] * hour = 2DIGIT / obs-hour * minute = 2DIGIT / obs-minute * second = 2DIGIT / obs-second * zone = (( "+" / "-" ) 4DIGIT) / obs-zone * * obs-day-of-week = [CFWS] day-name [CFWS] * obs-year = [CFWS] 2*DIGIT [CFWS] * obs-month = CFWS month-name CFWS * obs-day = [CFWS] 1*2DIGIT [CFWS] * obs-hour = [CFWS] 2DIGIT [CFWS] * obs-minute = [CFWS] 2DIGIT [CFWS] * obs-second = [CFWS] 2DIGIT [CFWS] ****************************************************************/ /* * For example, a typical date might look like: * * Date: Mon, 1 Oct 2007 05:38:10 +0000 (UTC) */ char *tail; char *p; struct tm tmp_tm; /* * NOTE: Rather than depend on strptime(3) modifying only * those fields specified in its format string, we use tmp_tm * and copy the appropriate result to tm. This is not * required with the NetBSD strptime(3) implementation. */ /* Check for an optional 'day-of-week' */ if ((tail = strptime(date, " %a,", &tmp_tm)) == NULL) { tail = date; tm->tm_wday = tmp_tm.tm_wday; } /* Get the required 'day' and 'month' */ if ((tail = strptime(tail, " %d %b", &tmp_tm)) == NULL) return NULL; tm->tm_mday = tmp_tm.tm_mday; tm->tm_mon = tmp_tm.tm_mon; /* Check for 'obs-year' (2 digits) before 'year' (4 digits) */ /* XXX - Portable? This depends on strptime not scanning off * trailing whitespace unless specified in the format string. */ if ((p = strptime(tail, " %y", &tmp_tm)) != NULL && is_WSP(*p)) tail = p; else if ((tail = strptime(tail, " %Y", &tmp_tm)) == NULL) return NULL; tm->tm_year = tmp_tm.tm_year; /* Get the required 'hour' and 'minute' */ if ((tail = strptime(tail, " %H:%M", &tmp_tm)) == NULL) return NULL; tm->tm_hour = tmp_tm.tm_hour; tm->tm_min = tmp_tm.tm_min; /* Check for an optional 'seconds' field */ if ((p = strptime(tail, ":%S", &tmp_tm)) != NULL) { tail = p; tm->tm_sec = tmp_tm.tm_sec; } tail = skip_WSP(tail); /* * The timezone name is frequently in a comment following the * zone offset. * * XXX - this will get overwritten later by timegm(3). */ if ((p = strchr(tail, '(')) != NULL) tm->tm_zone = get_comments(p); else tm->tm_zone = NULL; /* what remains should be the gmtoff string */ tail = skin(tail); return convert_obs_zone(tail); } /* * Parse the headline string into a tm structure. Returns a pointer * to first non-whitespace after the date or NULL on error. * * XXX - This needs to be consistent with isdate(). */ static char * hl_date_to_tm(const char *buf, struct tm *tm) { /**************************************************************** * The BSD and System V headline date formats differ * and each have an optional timezone field between * the time and date (see head.c). Unfortunately, * strptime(3) doesn't know about timezone fields, so * we have to handle it ourselves. * * char ctype[] = "Aaa Aaa O0 00:00:00 0000"; * char tmztype[] = "Aaa Aaa O0 00:00:00 AAA 0000"; * char SysV_ctype[] = "Aaa Aaa O0 00:00 0000"; * char SysV_tmztype[] = "Aaa Aaa O0 00:00 AAA 0000"; ****************************************************************/ char *tail; char *p; char zone[4]; struct tm tmp_tm; /* see comment in date_to_tm() */ int len; zone[0] = '\0'; if ((tail = strptime(buf, " %a %b %d %H:%M", &tmp_tm)) == NULL) return NULL; tm->tm_wday = tmp_tm.tm_wday; tm->tm_mday = tmp_tm.tm_mday; tm->tm_mon = tmp_tm.tm_mon; tm->tm_hour = tmp_tm.tm_hour; tm->tm_min = tmp_tm.tm_min; /* Check for an optional 'seconds' field */ if ((p = strptime(tail, ":%S", &tmp_tm)) != NULL) { tail = p; tm->tm_sec = tmp_tm.tm_sec; } /* Grab an optional timezone name */ /* * XXX - Is the zone name always 3 characters as in isdate()? */ if (sscanf(tail, " %3[A-Z] %n", zone, &len) == 1) { if (zone[0]) tm->tm_zone = savestr(zone); tail += len; } /* Grab the required year field */ tail = strptime(tail, " %Y ", &tmp_tm); tm->tm_year = tmp_tm.tm_year; /* save this even if it failed */ return tail; } /* * Get the date and time info from the "Date:" line, parse it into a * tm structure as much as possible. * * Note: We return the gmtoff as a string as "-0000" has special * meaning. See RFC 2822, sec 3.3. */ PUBLIC void dateof(struct tm *tm, struct message *mp, int use_hl_date) { static int tzinit = 0; char *date = NULL; const char *gmtoff; (void)memset(tm, 0, sizeof(*tm)); /* Make sure the time zone info is initialized. */ if (!tzinit) { tzinit = 1; tzset(); } if (mp == NULL) { /* use local time */ time_t now; (void)time(&now); (void)localtime_r(&now, tm); return; } /* * See RFC 2822 sec 3.3 for date-time format used in * the "Date:" field. * * NOTE: The range for the time is 00:00 to 23:60 (to allow * for a leep second), but I have seen this violated making * strptime() fail, e.g., * * Date: Tue, 24 Oct 2006 24:07:58 +0400 * * In this case we (silently) fall back to the headline time * which was written locally when the message was received. * Of course, this is not the same time as in the Date field. */ if (use_hl_date == 0 && (date = hfield("date", mp)) != NULL && (gmtoff = date_to_tm(date, tm)) != NULL) { int hour; int min; char sign[2]; struct tm save_tm; /* * Scan the gmtoff and use it to convert the time to a * local time. * * Note: "-0000" means no valid zone info. See * RFC 2822, sec 3.3. * * XXX - This is painful! Is there a better way? */ tm->tm_isdst = -1; /* let timegm(3) determine tm_isdst */ save_tm = *tm; /* use this if we fail */ if (strcmp(gmtoff, "-0000") != 0 && sscanf(gmtoff, " %1[+-]%2d%2d ", sign, &hour, &min) == 3) { time_t otime; if (sign[0] == '-') { tm->tm_hour += hour; tm->tm_min += min; } else { tm->tm_hour -= hour; tm->tm_min -= min; } if ((otime = timegm(tm)) == (time_t)-1 || localtime_r(&otime, tm) == NULL) { if (debug) warnx("cannot convert date: \"%s\"", date); *tm = save_tm; } } else { /* Unable to do the conversion to local time. */ *tm = save_tm; /* tm->tm_isdst = -1; */ /* Set above */ tm->tm_gmtoff = 0; tm->tm_zone = NULL; } } else { struct headline hl; char headline[LINESIZE]; char pbuf[LINESIZE]; if (debug && use_hl_date == 0) warnx("invalid date: \"%s\"", date ? date : ""); /* * The headline is written locally so failures here * should be seen (i.e., not conditional on 'debug'). */ tm->tm_isdst = -1; /* let mktime(3) determine tm_isdst */ headline[0] = '\0'; (void)readline(setinput(mp), headline, (int)sizeof(headline), 0); parse(headline, &hl, pbuf); if (hl.l_date == NULL) warnx("invalid headline: `%s'", headline); else if (hl_date_to_tm(hl.l_date, tm) == NULL || mktime(tm) == -1) warnx("invalid headline date: `%s'", hl.l_date); } } /* * Get the sender's address for display. Let nameof() do this. */ static const char * addrof(struct message *mp) { if (mp == NULL) return NULL; return nameof(mp, 0); } /************************************************************************ * The 'address' syntax - from RFC 2822: * * specials = "(" / ")" / ; Special characters used in * "<" / ">" / ; other parts of the syntax * "[" / "]" / * ":" / ";" / * "@" / "\" / * "," / "." / * DQUOTE * qtext = NO-WS-CTL / ; Non white space controls * %d33 / ; The rest of the US-ASCII * %d35-91 / ; characters not including "\" * %d93-126 ; or the quote character * qcontent = qtext / quoted-pair * quoted-string = [CFWS] * DQUOTE *([FWS] qcontent) [FWS] DQUOTE * [CFWS] * atext = ALPHA / DIGIT / ; Any character except controls, * "!" / "#" / ; SP, and specials. * "$" / "%" / ; Used for atoms * "&" / "'" / * "*" / "+" / * "-" / "/" / * "=" / "?" / * "^" / "_" / * "`" / "{" / * "|" / "}" / * "~" * atom = [CFWS] 1*atext [CFWS] * word = atom / quoted-string * phrase = 1*word / obs-phrase * display-name = phrase * dtext = NO-WS-CTL / ; Non white space controls * %d33-90 / ; The rest of the US-ASCII * %d94-126 ; characters not including "[", * ; "]", or "\" * dcontent = dtext / quoted-pair * domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS] * domain = dot-atom / domain-literal / obs-domain * local-part = dot-atom / quoted-string / obs-local-part * addr-spec = local-part "@" domain * angle-addr = [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr * name-addr = [display-name] angle-addr * mailbox = name-addr / addr-spec * mailbox-list = (mailbox *("," mailbox)) / obs-mbox-list * group = display-name ":" [mailbox-list / CFWS] ";" * [CFWS] * address = mailbox / group ************************************************************************/ static char * get_display_name(char *name) { char nbuf[LINESIZE]; const char *p; char *q; char *qend; char *lastq; int quoted; if (name == NULL) return NULL; q = nbuf; lastq = nbuf; qend = nbuf + sizeof(nbuf) - 1; /* reserve space for '\0' */ quoted = 0; for (p = skip_WSP(name); *p != '\0'; p++) { DPRINTF(("get_display_name: %s\n", p)); switch (*p) { case '"': /* quoted-string */ q = nbuf; p = snarf_quote(&q, qend, p); if (!quoted) lastq = q; quoted = 1; break; case ':': /* group */ case '<': /* angle-address */ if (lastq == nbuf) return NULL; *lastq = '\0'; /* NULL termination */ return savestr(nbuf); case '(': /* comment - skip it! */ p = snarf_comment(NULL, NULL, p); break; default: if (!quoted && q < qend) { *q++ = *p; if (!is_WSP(*p) /* && !is_specials((unsigned char)*p) */) lastq = q; } break; } } return NULL; /* no group or angle-address */ } /* * See RFC 2822 sec 3.4 and 3.6.2. */ static const char * userof(struct message *mp) { char *sender; char *dispname; if (mp == NULL) return NULL; if ((sender = hfield("from", mp)) != NULL || (sender = hfield("sender", mp)) != NULL) /* * Try to get the display-name. If one doesn't exist, * then the best we can hope for is that the user's * name is in the comments. */ if ((dispname = get_display_name(sender)) != NULL || (dispname = get_comments(sender)) != NULL) return dispname; return NULL; } /* * Grab the subject line. */ static const char * subjof(struct message *mp) { const char *subj; if (mp == NULL) return NULL; if ((subj = hfield("subject", mp)) == NULL) subj = hfield("subj", mp); return subj; } /* * Protect a string against strftime() conversion. */ static const char* protect(const char *str) { char *p, *q; size_t size; if (str == NULL || (size = strlen(str)) == 0) return str; p = salloc(2 * size + 1); for (q = p; *str; str++) { *q = *str; if (*q++ == '%') *q++ = '%'; } *q = '\0'; return p; } static char * preformat(struct tm *tm, const char *oldfmt, struct message *mp, int use_hl_date) { const char *subj; const char *addr; const char *user; const char *p; char *q; char *newfmt; size_t fmtsize; if (mp != NULL && (mp->m_flag & MDELETED) != 0) mp = NULL; /* deleted mail shouldn't show up! */ subj = protect(subjof(mp)); addr = protect(addrof(mp)); user = protect(userof(mp)); dateof(tm, mp, use_hl_date); fmtsize = LINESIZE; newfmt = ecalloc(1, fmtsize); /* so we can realloc() in check_bufsize() */ q = newfmt; p = oldfmt; while (*p) { if (*p == '%') { const char *fp; fp = subformat(&p, mp, addr, user, subj, tm->tm_isdst); if (fp) { size_t len; len = strlen(fp); check_bufsize(&newfmt, &fmtsize, &q, len); (void)strcpy(q, fp); q += len; continue; } } check_bufsize(&newfmt, &fmtsize, &q, 1); *q++ = *p++; } *q = '\0'; return newfmt; } /* * If a format string begins with the USE_HL_DATE string, smsgprintf * will use the headerline date rather than trying to extract the date * from the Date field. * * Note: If a 'valid' date cannot be extracted from the Date field, * then the headline date is used. */ #define USE_HL_DATE "%??" PUBLIC char * smsgprintf(const char *fmtstr, struct message *mp) { struct tm tm; int use_hl_date; char *newfmt; char *buf; size_t bufsize; if (strncmp(fmtstr, USE_HL_DATE, sizeof(USE_HL_DATE) - 1) != 0) use_hl_date = 0; else { use_hl_date = 1; fmtstr += sizeof(USE_HL_DATE) - 1; } bufsize = LINESIZE; buf = salloc(bufsize); newfmt = preformat(&tm, fmtstr, mp, use_hl_date); (void)strftime(buf, bufsize, newfmt, &tm); free(newfmt); /* preformat() uses malloc()/realloc() */ return buf; } PUBLIC void fmsgprintf(FILE *fo, const char *fmtstr, struct message *mp) { char *buf; buf = smsgprintf(fmtstr, mp); (void)fprintf(fo, "%s\n", buf); /* XXX - add the newline here? */ }