rfc822.c (15781B)
1 /* 2 * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation; either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program; if not, write to the Free Software Foundation, 16 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,, USA. 17 */ 18 19 /* $Id: rfc822.c,v 1.5 2005-10-29 14:48:11 roland Exp $ */ 20 21 #include <string.h> 22 #include <ctype.h> 23 #include <stdlib.h> 24 #include <stdio.h> 25 26 #include "helpers.h" 27 #include "rfc822.h" 28 29 const char RFC822Specials[] = "@.,:;<>[]\\\"()"; 30 #define is_special(x) strchr(RFC822Specials,x) 31 32 int RFC822Error = 0; 33 34 /* these must defined in the same order as the numerated errors given in rfc822.h */ 35 const char *RFC822Errors[] = { 36 "out of memory", 37 "mismatched parenthesis", 38 "mismatched quotes", 39 "bad route in <>", 40 "bad address in <>", 41 "bad address spec" 42 }; 43 44 void rfc822_dequote_comment (char *s) 45 { 46 char *w = s; 47 48 for (; *s; s++) 49 { 50 if (*s == '\\') 51 { 52 if (!*++s) 53 break; /* error? */ 54 *w++ = *s; 55 } 56 else if (*s != '\"') 57 { 58 if (w != s) 59 *w = *s; 60 w++; 61 } 62 } 63 *w = 0; 64 } 65 66 void rfc822_free_address (ADDRESS **p) 67 { 68 ADDRESS *t; 69 70 while (*p) 71 { 72 t = *p; 73 *p = (*p)->next; 74 #ifdef EXACT_ADDRESS 75 FREE (&t->val); 76 #endif 77 FREE (&t->personal); 78 FREE (&t->mailbox); 79 FREE (&t); 80 } 81 } 82 83 static const char * 84 parse_comment (const char *s, 85 char *comment, size_t *commentlen, size_t commentmax) 86 { 87 int level = 1; 88 89 while (*s && level) 90 { 91 if (*s == '(') 92 level++; 93 else if (*s == ')') 94 { 95 if (--level == 0) 96 { 97 s++; 98 break; 99 } 100 } 101 else if (*s == '\\') 102 { 103 if (!*++s) 104 break; 105 } 106 if (*commentlen < commentmax) 107 comment[(*commentlen)++] = *s; 108 s++; 109 } 110 if (level) 111 { 112 RFC822Error = ERR_MISMATCH_PAREN; 113 return NULL; 114 } 115 return s; 116 } 117 118 static const char * 119 parse_quote (const char *s, char *token, size_t *tokenlen, size_t tokenmax) 120 { 121 if (*tokenlen < tokenmax) 122 token[(*tokenlen)++] = '"'; 123 while (*s) 124 { 125 if (*tokenlen < tokenmax) 126 token[(*tokenlen)++] = *s; 127 if (*s == '"') 128 return (s + 1); 129 if (*s == '\\') 130 { 131 if (!*++s) 132 break; 133 } 134 s++; 135 } 136 RFC822Error = ERR_MISMATCH_QUOTE; 137 return NULL; 138 } 139 140 static const char * 141 next_token (const char *s, char *token, size_t *tokenlen, size_t tokenmax) 142 { 143 if (*s == '(') 144 return (parse_comment (s + 1, token, tokenlen, tokenmax)); 145 if (*s == '"') 146 return (parse_quote (s + 1, token, tokenlen, tokenmax)); 147 if (is_special (*s)) 148 { 149 if (*tokenlen < tokenmax) 150 token[(*tokenlen)++] = *s; 151 return (s + 1); 152 } 153 while (*s) 154 { 155 if (isspace (*s) || is_special (*s)) 156 break; 157 if (*tokenlen < tokenmax) 158 token[(*tokenlen)++] = *s; 159 s++; 160 } 161 return s; 162 } 163 164 static const char * 165 parse_mailboxdomain (const char *s, const char *nonspecial, 166 char *mailbox, size_t *mailboxlen, size_t mailboxmax, 167 char *comment, size_t *commentlen, size_t commentmax) 168 { 169 const char *ps; 170 171 while (*s) 172 { 173 SKIPWS (s); 174 if (strchr (nonspecial, *s) == NULL && is_special (*s)) 175 return s; 176 177 if (*s == '(') 178 { 179 if (*commentlen && *commentlen < commentmax) 180 comment[(*commentlen)++] = ' '; 181 ps = next_token (s, comment, commentlen, commentmax); 182 } 183 else 184 ps = next_token (s, mailbox, mailboxlen, mailboxmax); 185 if (!ps) 186 return NULL; 187 s = ps; 188 } 189 190 return s; 191 } 192 193 static const char * 194 parse_address (const char *s, 195 char *token, size_t *tokenlen, size_t tokenmax, 196 char *comment, size_t *commentlen, size_t commentmax, 197 ADDRESS *addr) 198 { 199 s = parse_mailboxdomain (s, ".\"(\\", 200 token, tokenlen, tokenmax, 201 comment, commentlen, commentmax); 202 if (!s) 203 return NULL; 204 205 if (*s == '@') 206 { 207 if (*tokenlen < tokenmax) 208 token[(*tokenlen)++] = '@'; 209 s = parse_mailboxdomain (s + 1, ".([]\\", 210 token, tokenlen, tokenmax, 211 comment, commentlen, commentmax); 212 if (!s) 213 return NULL; 214 } 215 216 token[*tokenlen] = 0; 217 addr->mailbox = safe_strdup (token); 218 219 if (*commentlen && !addr->personal) 220 { 221 comment[*commentlen] = 0; 222 addr->personal = safe_strdup (comment); 223 } 224 225 return s; 226 } 227 228 static const char * 229 parse_route_addr (const char *s, 230 char *comment, size_t *commentlen, size_t commentmax, 231 ADDRESS *addr) 232 { 233 char token[STRING]; 234 size_t tokenlen = 0; 235 236 SKIPWS (s); 237 238 /* find the end of the route */ 239 if (*s == '@') 240 { 241 while (s && *s == '@') 242 { 243 if (tokenlen < sizeof (token) - 1) 244 token[tokenlen++] = '@'; 245 s = parse_mailboxdomain (s + 1, ".\\[](", token, 246 &tokenlen, sizeof (token) - 1, 247 comment, commentlen, commentmax); 248 } 249 if (!s || *s != ':') 250 { 251 RFC822Error = ERR_BAD_ROUTE; 252 return NULL; /* invalid route */ 253 } 254 255 if (tokenlen < sizeof (token) - 1) 256 token[tokenlen++] = ':'; 257 s++; 258 } 259 260 if ((s = parse_address (s, token, &tokenlen, sizeof (token) - 1, comment, commentlen, commentmax, addr)) == NULL) 261 return NULL; 262 263 if (*s != '>' || !addr->mailbox) 264 { 265 RFC822Error = ERR_BAD_ROUTE_ADDR; 266 return NULL; 267 } 268 269 s++; 270 return s; 271 } 272 273 static const char * 274 parse_addr_spec (const char *s, 275 char *comment, size_t *commentlen, size_t commentmax, 276 ADDRESS *addr) 277 { 278 char token[STRING]; 279 size_t tokenlen = 0; 280 281 s = parse_address (s, token, &tokenlen, sizeof (token) - 1, comment, commentlen, commentmax, addr); 282 if (s && *s && *s != ',' && *s != ';') 283 { 284 RFC822Error = ERR_BAD_ADDR_SPEC; 285 return NULL; 286 } 287 return s; 288 } 289 290 static void 291 add_addrspec (ADDRESS **top, ADDRESS **last, const char *phrase, 292 char *comment, size_t *commentlen, size_t commentmax) 293 { 294 ADDRESS *cur = rfc822_new_address (); 295 296 if (parse_addr_spec (phrase, comment, commentlen, commentmax, cur) == NULL) 297 return; 298 299 if (*last) 300 (*last)->next = cur; 301 else 302 *top = cur; 303 *last = cur; 304 } 305 306 ADDRESS *rfc822_parse_adrlist (ADDRESS *top, const char *s) 307 { 308 const char *begin, *ps; 309 char comment[STRING], phrase[STRING]; 310 size_t phraselen = 0, commentlen = 0; 311 ADDRESS *cur, *last = NULL; 312 313 RFC822Error = 0; 314 315 last = top; 316 while (last && last->next) 317 last = last->next; 318 319 SKIPWS (s); 320 begin = s; 321 while (*s) 322 { 323 if (*s == ',') 324 { 325 if (phraselen) 326 { 327 phrase[phraselen] = 0; 328 add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1); 329 } 330 else if (commentlen && last && !last->personal) 331 { 332 comment[commentlen] = 0; 333 last->personal = safe_strdup (comment); 334 } 335 336 #ifdef EXACT_ADDRESS 337 if (last && !last->val) 338 last->val = mutt_substrdup (begin, s); 339 #endif 340 commentlen = 0; 341 phraselen = 0; 342 s++; 343 begin = s; 344 SKIPWS (begin); 345 } 346 else if (*s == '(') 347 { 348 if (commentlen && commentlen < sizeof (comment) - 1) 349 comment[commentlen++] = ' '; 350 if ((ps = next_token (s, comment, &commentlen, sizeof (comment) - 1)) == NULL) 351 { 352 rfc822_free_address (&top); 353 return NULL; 354 } 355 s = ps; 356 } 357 else if (*s == ':') 358 { 359 cur = rfc822_new_address (); 360 phrase[phraselen] = 0; 361 cur->mailbox = safe_strdup (phrase); 362 cur->group = 1; 363 364 if (last) 365 last->next = cur; 366 else 367 top = cur; 368 last = cur; 369 370 #ifdef EXACT_ADDRESS 371 last->val = mutt_substrdup (begin, s); 372 #endif 373 374 phraselen = 0; 375 commentlen = 0; 376 s++; 377 begin = s; 378 SKIPWS (begin); 379 } 380 else if (*s == ';') 381 { 382 if (phraselen) 383 { 384 phrase[phraselen] = 0; 385 add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1); 386 } 387 else if (commentlen && !last->personal) 388 { 389 comment[commentlen] = 0; 390 last->personal = safe_strdup (comment); 391 } 392 #ifdef EXACT_ADDRESS 393 if (last && !last->val) 394 last->val = mutt_substrdup (begin, s); 395 #endif 396 397 /* add group terminator */ 398 cur = rfc822_new_address (); 399 if (last) 400 { 401 last->next = cur; 402 last = cur; 403 } 404 405 phraselen = 0; 406 commentlen = 0; 407 s++; 408 begin = s; 409 SKIPWS (begin); 410 } 411 else if (*s == '<') 412 { 413 phrase[phraselen] = 0; 414 cur = rfc822_new_address (); 415 if (phraselen) 416 { 417 if (cur->personal) 418 FREE (&cur->personal); 419 /* if we get something like "Michael R. Elkins" remove the quotes */ 420 rfc822_dequote_comment (phrase); 421 cur->personal = safe_strdup (phrase); 422 } 423 if ((ps = parse_route_addr (s + 1, comment, &commentlen, sizeof (comment) - 1, cur)) == NULL) 424 { 425 rfc822_free_address (&top); 426 rfc822_free_address (&cur); 427 return NULL; 428 } 429 430 if (last) 431 last->next = cur; 432 else 433 top = cur; 434 last = cur; 435 436 phraselen = 0; 437 commentlen = 0; 438 s = ps; 439 } 440 else 441 { 442 if (phraselen && phraselen < sizeof (phrase) - 1) 443 phrase[phraselen++] = ' '; 444 if ((ps = next_token (s, phrase, &phraselen, sizeof (phrase) - 1)) == NULL) 445 { 446 rfc822_free_address (&top); 447 return NULL; 448 } 449 s = ps; 450 } 451 SKIPWS (s); 452 } 453 454 if (phraselen) 455 { 456 phrase[phraselen] = 0; 457 comment[commentlen] = 0; 458 add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1); 459 } 460 else if (commentlen && last && !last->personal) 461 { 462 comment[commentlen] = 0; 463 last->personal = safe_strdup (comment); 464 } 465 #ifdef EXACT_ADDRESS 466 if (last) 467 last->val = mutt_substrdup (begin, s); 468 #endif 469 470 return top; 471 } 472 473 void rfc822_qualify (ADDRESS *addr, const char *host) 474 { 475 char *p; 476 477 for (; addr; addr = addr->next) 478 if (!addr->group && addr->mailbox && strchr (addr->mailbox, '@') == NULL) 479 { 480 p = safe_malloc (strlen (addr->mailbox) + strlen (host) + 2); 481 sprintf (p, "%s@%s", addr->mailbox, host); 482 safe_free (&addr->mailbox); 483 addr->mailbox = p; 484 } 485 } 486 487 void 488 rfc822_cat (char *buf, size_t buflen, const char *value, const char *specials) 489 { 490 if (strpbrk (value, specials)) 491 { 492 char tmp[256], *pc = tmp; 493 size_t tmplen = sizeof (tmp) - 3; 494 495 *pc++ = '"'; 496 for (; *value && tmplen > 1; value++) 497 { 498 if (*value == '\\' || *value == '"') 499 { 500 *pc++ = '\\'; 501 tmplen--; 502 } 503 *pc++ = *value; 504 tmplen--; 505 } 506 *pc++ = '"'; 507 *pc = 0; 508 strfcpy (buf, tmp, buflen); 509 } 510 else 511 strfcpy (buf, value, buflen); 512 } 513 514 void rfc822_write_address_single (char *buf, size_t buflen, ADDRESS *addr) 515 { 516 size_t len; 517 char *pbuf = buf; 518 char *pc; 519 520 if (!addr) 521 return; 522 523 buflen--; /* save room for the terminal nul */ 524 525 #ifdef EXACT_ADDRESS 526 if (addr->val) 527 { 528 if (!buflen) 529 goto done; 530 strfcpy (pbuf, addr->val, buflen); 531 len = strlen (pbuf); 532 pbuf += len; 533 buflen -= len; 534 if (addr->group) 535 { 536 if (!buflen) 537 goto done; 538 *pbuf++ = ':'; 539 buflen--; 540 *pbuf = 0; 541 } 542 return; 543 } 544 #endif 545 546 if (addr->personal) 547 { 548 if (strpbrk (addr->personal, RFC822Specials)) 549 { 550 if (!buflen) 551 goto done; 552 *pbuf++ = '"'; 553 buflen--; 554 for (pc = addr->personal; *pc && buflen > 0; pc++) 555 { 556 if (*pc == '"' || *pc == '\\') 557 { 558 if (!buflen) 559 goto done; 560 *pbuf++ = '\\'; 561 buflen--; 562 } 563 if (!buflen) 564 goto done; 565 *pbuf++ = *pc; 566 buflen--; 567 } 568 if (!buflen) 569 goto done; 570 *pbuf++ = '"'; 571 buflen--; 572 } 573 else 574 { 575 if (!buflen) 576 goto done; 577 strfcpy (pbuf, addr->personal, buflen); 578 len = strlen (pbuf); 579 pbuf += len; 580 buflen -= len; 581 } 582 583 if (!buflen) 584 goto done; 585 *pbuf++ = ' '; 586 buflen--; 587 } 588 589 if (addr->personal || (addr->mailbox && *addr->mailbox == '@')) 590 { 591 if (!buflen) 592 goto done; 593 *pbuf++ = '<'; 594 buflen--; 595 } 596 597 if (addr->mailbox) 598 { 599 if (!buflen) 600 goto done; 601 strfcpy (pbuf, addr->mailbox, buflen); 602 len = strlen (pbuf); 603 pbuf += len; 604 buflen -= len; 605 606 if (addr->personal || (addr->mailbox && *addr->mailbox == '@')) 607 { 608 if (!buflen) 609 goto done; 610 *pbuf++ = '>'; 611 buflen--; 612 } 613 614 if (addr->group) 615 { 616 if (!buflen) 617 goto done; 618 *pbuf++ = ':'; 619 buflen--; 620 if (!buflen) 621 goto done; 622 *pbuf++ = ' '; 623 buflen--; 624 } 625 } 626 else 627 { 628 if (!buflen) 629 goto done; 630 *pbuf++ = ';'; 631 buflen--; 632 } 633 done: 634 /* no need to check for length here since we already save space at the 635 beginning of this routine */ 636 *pbuf = 0; 637 } 638 639 /* note: it is assumed that `buf' is nul terminated! */ 640 void rfc822_write_address (char *buf, size_t buflen, ADDRESS *addr) 641 { 642 char *pbuf = buf; 643 size_t len = strlen (buf); 644 645 buflen--; /* save room for the terminal nul */ 646 647 if (len > 0) 648 { 649 if (len > buflen) 650 return; /* safety check for bogus arguments */ 651 652 pbuf += len; 653 buflen -= len; 654 if (!buflen) 655 goto done; 656 *pbuf++ = ','; 657 buflen--; 658 if (!buflen) 659 goto done; 660 *pbuf++ = ' '; 661 buflen--; 662 } 663 664 for (; addr && buflen > 0; addr = addr->next) 665 { 666 /* use buflen+1 here because we already saved space for the trailing 667 nul char, and the subroutine can make use of it */ 668 rfc822_write_address_single (pbuf, buflen + 1, addr); 669 670 /* this should be safe since we always have at least 1 char passed into 671 the above call, which means `pbuf' should always be nul terminated */ 672 len = strlen (pbuf); 673 pbuf += len; 674 buflen -= len; 675 676 /* if there is another address, and its not a group mailbox name or 677 group terminator, add a comma to separate the addresses */ 678 if (addr->next && addr->next->mailbox && !addr->group) 679 { 680 if (!buflen) 681 goto done; 682 *pbuf++ = ','; 683 buflen--; 684 if (!buflen) 685 goto done; 686 *pbuf++ = ' '; 687 buflen--; 688 } 689 } 690 done: 691 *pbuf = 0; 692 } 693 694 /* this should be rfc822_cpy_adr */ 695 ADDRESS *rfc822_cpy_adr_real (ADDRESS *addr) 696 { 697 ADDRESS *p = rfc822_new_address (); 698 699 #ifdef EXACT_ADDRESS 700 p->val = safe_strdup (addr->val); 701 #endif 702 p->personal = safe_strdup (addr->personal); 703 p->mailbox = safe_strdup (addr->mailbox); 704 p->group = addr->group; 705 return p; 706 } 707 708 /* this should be rfc822_cpy_adrlist */ 709 ADDRESS *rfc822_cpy_adr (ADDRESS *addr) 710 { 711 ADDRESS *top = NULL, *last = NULL; 712 713 for (; addr; addr = addr->next) 714 { 715 if (last) 716 { 717 last->next = rfc822_cpy_adr_real (addr); 718 last = last->next; 719 } 720 else 721 top = last = rfc822_cpy_adr_real (addr); 722 } 723 return top; 724 } 725 726 /* append list 'b' to list 'a' and return the last element in the new list */ 727 ADDRESS *rfc822_append (ADDRESS **a, ADDRESS *b) 728 { 729 ADDRESS *tmp = *a; 730 731 while (tmp && tmp->next) 732 tmp = tmp->next; 733 if (!b) 734 return tmp; 735 if (tmp) 736 tmp->next = rfc822_cpy_adr (b); 737 else 738 tmp = *a = rfc822_cpy_adr (b); 739 while (tmp && tmp->next) 740 tmp = tmp->next; 741 return tmp; 742 } 743 744 #ifdef TESTING 745 void safe_free (void *ptr) 746 { 747 void **p = (void **)ptr; 748 if (*p) 749 { 750 free (*p); 751 *p = 0; 752 } 753 } 754 755 int main (int argc, char **argv) 756 { 757 ADDRESS *list; 758 char buf[256]; 759 char *str = "michael, Michael Elkins <me@cs.hmc.edu>, testing a really complex address: this example <@contains.a.source.route@with.multiple.hosts:address@example.com>;, lothar@of.the.hillpeople (lothar)"; 760 761 list = rfc822_parse_adrlist (NULL, str); 762 buf[0] = 0; 763 rfc822_write_address (buf, sizeof (buf), list); 764 rfc822_free_address (&list); 765 puts (buf); 766 exit (0); 767 } 768 #endif