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