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