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