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