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