1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934
|
/*
* m_getfld.c -- read/parse a message
*
* This code is Copyright (c) 2002, by the authors of nmh. See the
* COPYRIGHT file in the root directory of the nmh distribution for
* complete copyright information.
*/
#include <h/mh.h>
#include <h/mts.h>
#include <h/utils.h>
/*
Purpose
=======
Reads an Internet message (RFC 5322), or one or more messages
stored in a maildrop in mbox (RFC 4155) or MMDF format, from a file
stream. Each call to m_getfld() reads one header field, or a
portion of the body, in sequence.
Inputs
======
gstate: opaque parse state
bufsz: maximum number of characters to load into buf
iob: input file stream
Outputs
=======
name: header field name (array of size NAMESZ=999)
buf: either a header field body or message body
bufsz: number of characters loaded into buf
(return value): message parse state on return from function
Functions
=========
void m_getfld_state_destroy (m_getfld_state_t *gstate): destroys
the parse state pointed to by the gstate argument.
m_getfld_state_reset (m_getfld_state_t *gstate): resets the parse
state to FLD.
void m_unknown(FILE *iob): Determines the message delimiter string
for the maildrop. Called by inc, scan, and msh when reading from a
maildrop file.
void m_eomsbr (int (*action)(int)): Sets the hook to check for end
of message in a maildrop. Called only by msh.
State variables
===============
m_getfld() retains state internally between calls in the
m_getfld_state_t variable. These are used for detecting the end of
each message when reading maildrops:
char **pat_map
char *fdelim
char *delimend
int fdelimlen
char *edelim
int edelimlen
char *msg_delim
int msg_style
int (*eom_action)(int)
Usage
=====
m_getfld_state_t gstate = 0;
...
int state = m_getfld (&gstate, ...);
...
m_getfld_state_destroy (&gstate);
The state is retained internally by gstate. To reset its state to FLD:
m_getfld_state_reset (&gstate);
*/
/* The following described the old implementation. The high-level
structure hasn't changed, but some of the details have. I'm
leaving this as-is, though, for posterity.
*/
/* This module has a long and checkered history. First, it didn't burst
maildrops correctly because it considered two CTRL-A:s in a row to be
an inter-message delimiter. It really is four CTRL-A:s followed by a
newline. Unfortunately, MMDF will convert this delimiter *inside* a
message to a CTRL-B followed by three CTRL-A:s and a newline. This
caused the old version of m_getfld() to declare eom prematurely. The
fix was a lot slower than
c == '\001' && peekc (iob) == '\001'
but it worked, and to increase generality, MBOX style maildrops could
be parsed as well. Unfortunately the speed issue finally caught up with
us since this routine is at the very heart of MH.
To speed things up considerably, the routine Eom() was made an auxilary
function called by the macro eom(). Unless we are bursting a maildrop,
the eom() macro returns FALSE saying we aren't at the end of the
message.
The next thing to do is to read the mts.conf file and initialize
delimiter[] and delimlen accordingly...
After mhl was made a built-in in msh, m_getfld() worked just fine
(using m_unknown() at startup). Until one day: a message which was
the result of a bursting was shown. Then, since the burst boundaries
aren't CTRL-A:s, m_getfld() would blinding plunge on past the boundary.
Very sad. The solution: introduce m_eomsbr(). This hook gets called
after the end of each line (since testing for eom involves an fseek()).
This worked fine, until one day: a message with no body portion arrived.
Then the
while (eom (c = Getc (iob), iob))
continue;
loop caused m_getfld() to return FMTERR. So, that logic was changed to
check for (*eom_action) and act accordingly.
This worked fine, until one day: someone didn't use four CTRL:A's as
their delimiters. So, the bullet got bit and we read mts.h and
continue to struggle on. It's not that bad though, since the only time
the code gets executed is when inc (or msh) calls it, and both of these
have already called mts_init().
------------------------
(Written by Van Jacobson for the mh6 m_getfld, January, 1986):
This routine was accounting for 60% of the cpu time used by most mh
programs. I spent a bit of time tuning and it now accounts for <10%
of the time used. Like any heavily tuned routine, it's a bit
complex and you want to be sure you understand everything that it's
doing before you start hacking on it. Let me try to emphasize
that: every line in this atrocity depends on every other line,
sometimes in subtle ways. You should understand it all, in detail,
before trying to change any part. If you do change it, test the
result thoroughly (I use a hand-constructed test file that exercises
all the ways a header name, header body, header continuation,
header-body separator, body line and body eom can align themselves
with respect to a buffer boundary). "Minor" bugs in this routine
result in garbaged or lost mail.
If you hack on this and slow it down, I, my children and my
children's children will curse you.
This routine gets used on three different types of files: normal,
single msg files, "packed" unix or mmdf mailboxs (when used by inc)
and packed, directoried bulletin board files (when used by msh).
The biggest impact of different file types is in "eom" testing. The
code has been carefully organized to test for eom at appropriate
times and at no other times (since the check is quite expensive).
I have tried to arrange things so that the eom check need only be
done on entry to this routine. Since an eom can only occur after a
newline, this is easy to manage for header fields. For the msg
body, we try to efficiently search the input buffer to see if
contains the eom delimiter. If it does, we take up to the
delimiter, otherwise we take everything in the buffer. (The change
to the body eom/copy processing produced the most noticeable
performance difference, particularly for "inc" and "show".)
There are three qualitatively different things this routine busts
out of a message: field names, field text and msg bodies. Field
names are typically short (~8 char) and the loop that extracts them
might terminate on a colon, newline or max width. I considered
using a Vax "scanc" to locate the end of the field followed by a
"bcopy" but the routine call overhead on a Vax is too large for this
to work on short names. If Berkeley ever makes "inline" part of the
C optimiser (so things like "scanc" turn into inline instructions) a
change here would be worthwhile.
Field text is typically 60 - 100 characters so there's (barely)
a win in doing a routine call to something that does a "locc"
followed by a "bmove". About 30% of the fields have continuations
(usually the 822 "received:" lines) and each continuation generates
another routine call. "Inline" would be a big win here, as well.
Messages, as of this writing, seem to come in two flavors: small
(~1K) and long (>2K). Most messages have 400 - 600 bytes of headers
so message bodies average at least a few hundred characters.
Assuming your system uses reasonably sized stdio buffers (1K or
more), this routine should be able to remove the body in large
(>500 byte) chunks. The makes the cost of a call to "bcopy"
small but there is a premium on checking for the eom in packed
maildrops. The eom pattern is always a simple string so we can
construct an efficient pattern matcher for it (e.g., a Vax "matchc"
instruction). Some thought went into recognizing the start of
an eom that has been split across two buffers.
This routine wants to deal with large chunks of data so, rather
than "getc" into a local buffer, it uses stdio's buffer. If
you try to use it on a non-buffered file, you'll get what you
deserve. This routine "knows" that struct FILEs have a _ptr
and a _cnt to describe the current state of the buffer and
it knows that _filbuf ignores the _ptr & _cnt and simply fills
the buffer. If stdio on your system doesn't work this way, you
may have to make small changes in this routine.
This routine also "knows" that an EOF indication on a stream is
"sticky" (i.e., you will keep getting EOF until you reposition the
stream). If your system doesn't work this way it is broken and you
should complain to the vendor. As a consequence of the sticky
EOF, this routine will never return any kind of EOF status when
there is data in "name" or "buf").
*/
/*
* static prototypes
*/
struct m_getfld_state;
static int m_Eom (m_getfld_state_t, int);
static char *matchc(int, char *, int, char *);
#define eom(c,s) (s->msg_style != MS_DEFAULT && \
(((c) == *s->msg_delim && m_Eom(s,c)) || \
(s->eom_action && (*s->eom_action)(c))))
/* This replaces the old approach, with its direct access to stdio
* internals. It uses one fread() to load a buffer that we manage.
*
* MSG_INPUT_SIZE is the size of the buffer.
* MAX_DELIMITER_SIZE is the maximum size of the delimiter used to
* separate messages in a maildrop, such as mbox "From ".
*
* Some of the tests in the test suite assume a MSG_INPUT_SIZE
* of 4096.
*/
#define MSG_INPUT_SIZE 4096
#define MAX_DELIMITER_SIZE 5
struct m_getfld_state {
char msg_buf[2 * MSG_INPUT_SIZE + MAX_DELIMITER_SIZE];
char *readpos;
char *end; /* One past the last character read in. */
/* The following support tracking of the read position in the
input file stream so that callers can interleave m_getfld()
calls with ftell() and fseek(). ytes_read replaces the old
m_getfld() msg_count global. last_caller_pos is stored when
leaving m_getfld()/m_unknown(), then checked on the next entry.
last_internal_pos is used to remember the position used
internally by m_getfld() (read_more(), actually). */
off_t bytes_read;
off_t total_bytes_read; /* by caller, not necessarily from input file */
off_t last_caller_pos;
off_t last_internal_pos;
FILE *iob;
char **pat_map;
int msg_style;
/*
* The "full" delimiter string for a packed maildrop consists
* of a newline followed by the actual delimiter. E.g., the
* full string for a Unix maildrop would be: "\n\nFrom ".
* "Fdelim" points to the start of the full string and is used
* in the BODY case of the main routine to search the buffer for
* a possible eom. Msg_delim points to the first character of
* the actual delim. string (i.e., fdelim+1). Edelim
* points to the 2nd character of actual delimiter string. It
* is used in m_Eom because the first character of the string
* has been read and matched before m_Eom is called.
*/
char *msg_delim;
char *fdelim;
char *delimend;
int fdelimlen;
char *edelim;
int edelimlen;
int (*eom_action)(int);
int state;
int track_filepos;
};
static
void
m_getfld_state_init (m_getfld_state_t *gstate, FILE *iob) {
m_getfld_state_t s;
s = *gstate = (m_getfld_state_t) mh_xmalloc(sizeof (struct m_getfld_state));
s->readpos = s->end = s->msg_buf;
s->bytes_read = s->total_bytes_read = 0;
s->last_caller_pos = s->last_internal_pos = 0;
s->iob = iob;
s->pat_map = NULL;
s->msg_style = MS_DEFAULT;
s->msg_delim = "";
s->fdelim = s->delimend = s->edelim = NULL;
s->fdelimlen = s->edelimlen = 0;
s->eom_action = NULL;
s->state = FLD;
s->track_filepos = 0;
}
/* scan() needs to force a state an initial state of FLD for each message. */
void
m_getfld_state_reset (m_getfld_state_t *gstate) {
if (*gstate) {
(*gstate)->state = FLD;
}
}
/* If the caller interleaves ftell*()/fseek*() calls with m_getfld()
calls, m_getfld() must keep track of the file position. The caller
must use this function to inform m_getfld(). */
void
m_getfld_track_filepos (m_getfld_state_t *gstate, FILE *iob) {
if (! *gstate) {
m_getfld_state_init (gstate, iob);
}
(*gstate)->track_filepos = 1;
}
void m_getfld_state_destroy (m_getfld_state_t *gstate) {
m_getfld_state_t s = *gstate;
if (s) {
if (s->fdelim) {
free (s->fdelim-1);
free (s->pat_map);
}
free (s);
*gstate = 0;
}
}
/*
Summary of file and message input buffer positions:
input file -------------------------------------------EOF
| |
last_caller_pos last_internal_pos
msg_buf --------------------EOF
| | |
msg_buf readpos end
|<>|=retained characters, difference
between last_internal_pos and
first readpos value after reading
in new chunk in read_more()
When returning from m_getfld()/m_unknown():
1) Save the internal file position in last_internal_pos. That's the
m_getfld() position reference in the input file.
2) Set file stream position so that callers can use ftell().
When entering m_getfld()/m_unknown():
Check to see if the call had changed the file position. If so,
adjust the internal position reference accordingly. If not, restore
the internal file position from last_internal_pos.
*/
static void
enter_getfld (m_getfld_state_t *gstate, FILE *iob) {
m_getfld_state_t s;
off_t pos = ftello (iob);
if (! *gstate) {
m_getfld_state_init (gstate, iob);
}
s = *gstate;
s->bytes_read = 0;
/* This is ugly and no longer necessary, but is retained just in
case it's needed again. The parser used to open the input file
multiple times, so we had to always use the FILE * that's
passed to m_getfld(). Now the parser inits a new
m_getfld_state for each file. See comment below about the
readpos shift code being currently unused. */
s->iob = iob;
if (s->track_filepos && (pos != 0 || s->last_internal_pos != 0)) {
if (s->last_internal_pos == 0) {
s->total_bytes_read = pos;
} else {
off_t pos_movement = pos - s->last_caller_pos; /* Can be < 0. */
if (pos_movement == 0) {
pos = s->last_internal_pos;
} else {
/* The current file stream position differs from the
last one, so caller must have called ftell/o().
Or, this is the first call and the file position
was not at 0. */
if (s->readpos + pos_movement >= s->msg_buf &&
s->readpos + pos_movement < s->end) {
/* This is currently unused. It could be used by
parse_mime() if it was changed to use a global
m_getfld_state. */
/* We can shift readpos and remain within the
bounds of msg_buf. */
s->readpos += pos_movement;
s->total_bytes_read += pos_movement;
pos = s->last_internal_pos;
} else {
size_t num_read;
/* This seek skips past an integral number of
chunks of size MSG_INPUT_SIZE. */
fseeko (iob, pos/MSG_INPUT_SIZE * MSG_INPUT_SIZE, SEEK_SET);
num_read = fread (s->msg_buf, 1, MSG_INPUT_SIZE, iob);
s->readpos = s->msg_buf + pos % MSG_INPUT_SIZE;
s->end = s->msg_buf + num_read;
s->total_bytes_read = pos;
}
}
fseeko (iob, pos, SEEK_SET);
}
}
}
static void
leave_getfld (m_getfld_state_t s) {
s->total_bytes_read += s->bytes_read;
if (s->track_filepos) {
/* Save the internal file position that we use for the input buffer. */
s->last_internal_pos = ftello (s->iob);
/* Set file stream position so that callers can use ftell(). */
fseeko (s->iob, s->total_bytes_read, SEEK_SET);
s->last_caller_pos = ftello (s->iob);
}
}
static size_t
read_more (m_getfld_state_t s) {
/* Retain at least edelimlen characters that have already been
read so that we can back up to them in m_Eom(). */
ssize_t retain = s->edelimlen;
size_t num_read;
if (retain < s->end - s->readpos) retain = s->end - s->readpos;
assert (retain <= s->readpos - s->msg_buf);
/* Move what we want to retain at end of the buffer to the beginning. */
memmove (s->msg_buf, s->readpos - retain, retain);
s->readpos = s->msg_buf + retain;
num_read = fread (s->readpos, 1, MSG_INPUT_SIZE, s->iob);
s->end = s->readpos + num_read;
return num_read;
}
/* The return values of the following functions are a bit
subtle. They can return 0x00 - 0xff as a valid character,
but EOF is typically 0xffffffff. */
static int
Getc (m_getfld_state_t s) {
if (s->end - s->readpos < 1) {
if (read_more (s) == 0) {
/* Pretend that we read a character. That's what stdio does. */
++s->readpos;
return EOF;
}
}
++s->bytes_read;
return s->readpos < s->end ? (unsigned char) *s->readpos++ : EOF;
}
static int
Peek (m_getfld_state_t s) {
if (s->end - s->readpos < 1) {
if (read_more (s) == 0) {
/* Pretend that we read a character. That's what stdio does. */
++s->readpos;
return EOF;
}
}
return s->readpos < s->end ? (unsigned char) *s->readpos : EOF;
}
static int
Ungetc (int c, m_getfld_state_t s) {
if (s->readpos == s->msg_buf) {
return EOF;
} else {
--s->bytes_read;
return *--s->readpos = (unsigned char) c;
}
}
int
m_getfld (m_getfld_state_t *gstate, char name[NAMESZ], char *buf, int *bufsz,
FILE *iob)
{
m_getfld_state_t s;
register char *cp;
register int max, n, c;
enter_getfld (gstate, iob);
s = *gstate;
if ((c = Getc(s)) < 0) {
*bufsz = *buf = 0;
leave_getfld (s);
return s->state = FILEEOF;
}
if (eom (c, s)) {
if (! s->eom_action) {
/* flush null messages */
while ((c = Getc(s)) >= 0 && eom (c, s))
;
if (c >= 0)
Ungetc(c, s);
}
*bufsz = *buf = 0;
leave_getfld (s);
return s->state = FILEEOF;
}
switch (s->state) {
case FLD:
if (c == '\n' || c == '-') {
/* we hit the header/body separator */
while (c != '\n' && (c = Getc(s)) >= 0) continue;
if (c < 0 || (c = Getc(s)) < 0 || eom (c, s)) {
if (! s->eom_action) {
/* flush null messages */
while ((c = Getc(s)) >= 0 && eom (c, s))
;
if (c >= 0)
Ungetc(c, s);
}
*bufsz = *buf = 0;
leave_getfld (s);
return s->state = FILEEOF;
}
s->state = BODY;
goto body;
}
/*
* get the name of this component. take characters up
* to a ':', a newline or NAMESZ-1 characters, whichever
* comes first.
*/
cp = name;
max = NAMESZ - 1;
/* Get the field name. The first time through the loop,
this copies out the first character, which was loaded
into c prior to loop entry. Initialize n to 1 to
account for that. */
for (n = 1;
c != ':' && c != '\n' && c != EOF && n < max;
++n, c = Getc (s)) {
*cp++ = c;
}
/* Check for next character, which is either the space after
the ':' or the first folded whitespace. */
{
int next_char;
if (c == EOF || (next_char = Peek (s)) == EOF) {
*bufsz = *cp = *buf = 0;
advise (NULL, "eof encountered in field \"%s\"", name);
leave_getfld (s);
return s->state = FMTERR;
}
}
/* If c isn't ':' here, something went wrong. Possibilities are:
* . hit a newline (error)
* . got more than namesz chars. (error)
*/
if (c == ':') {
/* Finished header name, fall through to FLDPLUS below. */
} else if (c == '\n') {
/* We hit the end of the line without seeing ':' to
* terminate the field name. This is usually (always?)
* spam. But, blowing up is lame, especially when
* scan(1)ing a folder with such messages. Pretend such
* lines are the first of the body (at least mutt also
* handles it this way). */
/* See if buf can hold this line, since we were assuming
* we had a buffer of NAMESZ, not bufsz. */
/* + 1 for the newline */
if (*bufsz < n + 1) {
/* No, it can't. Oh well, guess we'll blow up. */
*bufsz = *cp = *buf = 0;
advise (NULL, "eol encountered in field \"%s\"", name);
s->state = FMTERR;
break;
}
memcpy (buf, name, n - 1);
buf[n - 1] = '\n';
buf[n] = '\0';
/* The last character read was '\n'. s->bytes_read
(and n) include that, but it was not put into the
name array in the for loop above. So subtract 1. */
*bufsz = --s->bytes_read; /* == n - 1 */
leave_getfld (s);
return s->state = BODY;
} else if (max <= n) {
/* By design, the loop above discards the last character
it had read. It's in c, use it. */
*cp++ = c;
*bufsz = *cp = *buf = 0;
advise (NULL, "field name \"%s\" exceeds %d bytes", name,
NAMESZ - 2);
s->state = LENERR;
break;
}
/* Trim any trailing spaces from the end of name. */
while (isspace ((unsigned char) *--cp) && cp >= name) continue;
*++cp = 0;
/* readpos points to the first character of the field body. */
/* fall through */
case FLDPLUS: {
/*
* get (more of) the text of a field. Take
* characters up to the end of this field (newline
* followed by non-blank) or bufsz-1 characters.
*/
int finished;
cp = buf;
max = *bufsz-1;
n = 0;
for (finished = 0; ! finished; ) {
while (c != '\n' && c != EOF && n++ < max) {
if ((c = Getc (s)) != EOF) { *cp++ = c; }
}
if (c != EOF) c = Peek (s);
if (max < n) {
/* The dest buffer is full. Need to back the read
pointer up by one because when m_getfld() is
reentered, it will read a character. Then
we'll jump right to the FLDPLUS handling code,
which will not store that character, but
instead move on to the next one. */
if (s->readpos > s->msg_buf) {
--s->readpos;
--s->bytes_read;
}
s->state = FLDPLUS;
finished = 1;
} else if (c != ' ' && c != '\t') {
/* The next character is not folded whitespace, so
prepare to move on to the next field. It's OK
if c is EOF, it will be handled on the next
call to m_getfld (). */
s->state = FLD;
finished = 1;
} else {
/* Folded header field, continues on the next line. */
}
}
*bufsz = s->bytes_read;
break;
}
body:
case BODY: {
/*
* get the message body up to bufsz characters or the
* end of the message.
*/
char *bp;
max = *bufsz-1;
/* Back up and store the current position. */
bp = --s->readpos;
c = s->end - s->readpos < max ? s->end - s->readpos : max;
if (s->msg_style != MS_DEFAULT && c > 1) {
/*
* packed maildrop - only take up to the (possible)
* start of the next message. This "matchc" should
* probably be a Boyer-Moore matcher for non-vaxen,
* particularly since we have the alignment table
* all built for the end-of-buffer test (next).
* But our vax timings indicate that the "matchc"
* instruction is 50% faster than a carefully coded
* B.M. matcher for most strings. (So much for elegant
* algorithms vs. brute force.) Since I (currently)
* run MH on a vax, we use the matchc instruction. --vj
*/
char *ep;
if ((ep = matchc( s->fdelimlen, s->fdelim, c, bp )))
c = ep - bp + 1;
else {
/*
* There's no delim in the buffer but there may be
* a partial one at the end. If so, we want to leave
* it so the "eom" check on the next call picks it up.
* Use a modified Boyer-Moore matcher to make this
* check relatively cheap. The first "if" figures
* out what position in the pattern matches the last
* character in the buffer. The inner "while" matches
* the pattern against the buffer, backwards starting
* at that position. Note that unless the buffer
* ends with one of the characters in the pattern
* (excluding the first and last), we do only one test.
*/
char *sp;
ep = bp + c - 1;
if ((sp = s->pat_map[(unsigned char) *ep])) {
do {
/* This if() is true unless (a) the buffer is too
* small to contain this delimiter prefix, or
* (b) it contains exactly enough chars for the
* delimiter prefix.
* For case (a) obviously we aren't going to match.
* For case (b), if the buffer really contained exactly
* a delim prefix, then the m_eom call at entry
* should have found it. Thus it's not a delim
* and we know we won't get a match.
*/
if (((sp - s->fdelim) + 2) <= c) {
cp = sp;
/* Unfortunately although fdelim has a preceding NUL
* we can't use this as a sentinel in case the buffer
* contains a NUL in exactly the wrong place (this
* would cause us to run off the front of fdelim).
*/
while (*--ep == *--cp)
if (cp < s->fdelim)
break;
if (cp < s->fdelim) {
/* we matched the entire delim prefix,
* so only take the buffer up to there.
* we know ep >= bp -- check above prevents underrun
*/
c = (ep - bp) + 2;
break;
}
}
/* try matching one less char of delim string */
ep = bp + c - 1;
} while (--sp > s->fdelim);
}
}
}
memcpy( buf, bp, c );
/* Advance the current position to reflect the copy out.
c is less than or equal to the number of bytes remaining
in the read buffer, so will not overrun it. */
s->readpos += c;
cp = buf + c;
/* Subtract 1 from c because the first character was read by
Getc(), and therefore already accounted for in s->bytes_read. */
s->bytes_read += c - 1;
*bufsz = s->bytes_read;
break;
}
default:
adios (NULL, "m_getfld() called with bogus state of %d", s->state);
}
*cp = 0;
leave_getfld (s);
return s->state;
}
void
m_unknown(m_getfld_state_t *gstate, FILE *iob)
{
m_getfld_state_t s;
register int c;
char text[MAX_DELIMITER_SIZE];
char from[] = "From ";
register char *cp;
register char *delimstr;
unsigned int i;
enter_getfld (gstate, iob);
s = *gstate;
/*
* Figure out what the message delimitter string is for this
* maildrop. (This used to be part of m_Eom but I didn't like
* the idea of an "if" statement that could only succeed on the
* first call to m_Eom getting executed on each call, i.e., at
* every newline in the message).
*
* If the first line of the maildrop is a Unix "From " line, we
* say the style is MBOX and eat the rest of the line. Otherwise
* we say the style is MMDF and look for the delimiter string
* specified when nmh was built (or from the mts.conf file).
*/
s->msg_style = MS_UNKNOWN;
for (i = 0, cp = text; i < sizeof text; ++i, ++cp) {
if ((signed char) (*cp = Getc (s)) == EOF) {
break;
}
}
if (i == sizeof from-1 && strncmp (text, "From ", sizeof from-1) == 0) {
s->msg_style = MS_MBOX;
delimstr = "\nFrom ";
while ((c = Getc (s)) != '\n' && c >= 0) continue;
} else {
/* not a Unix style maildrop */
s->readpos -= s->bytes_read;
s->bytes_read = 0;
delimstr = mmdlm2;
s->msg_style = MS_MMDF;
}
c = strlen (delimstr);
s->fdelim = mh_xmalloc (c + 3);
*s->fdelim++ = '\0';
*s->fdelim = '\n';
s->msg_delim = s->fdelim+1;
s->edelim = s->msg_delim+1;
s->fdelimlen = c + 1;
s->edelimlen = c - 1; /* == strlen (delimstr) */
strcpy (s->msg_delim, delimstr);
s->delimend = s->msg_delim + s->edelimlen;
if (s->edelimlen <= 1)
adios (NULL, "maildrop delimiter must be at least 2 bytes");
/*
* build a Boyer-Moore end-position map for the matcher in m_getfld.
* N.B. - we don't match just the first char (since it's the newline
* separator) or the last char (since the matchc would have found it
* if it was a real delim).
*/
s->pat_map = (char **) calloc (256, sizeof(char *));
for (cp = s->fdelim + 1; cp < s->delimend; cp++ )
s->pat_map[(unsigned char)*cp] = cp;
if (s->msg_style == MS_MMDF) {
/* flush extra msg hdrs */
while ((c = Getc(s)) >= 0 && eom (c, s))
;
if (c >= 0)
Ungetc(c, s);
}
leave_getfld (s);
}
void
m_eomsbr (m_getfld_state_t s, int (*action)(int))
{
if ((s->eom_action = action)) {
s->msg_style = MS_MSH;
*s->msg_delim = 0;
s->fdelimlen = 1;
s->delimend = s->fdelim;
} else {
s->msg_style = MS_MMDF;
s->msg_delim = s->fdelim + 1;
s->fdelimlen = strlen (s->fdelim);
s->delimend = s->msg_delim + s->edelimlen;
}
}
/*
* test for msg delimiter string
*/
static int
m_Eom (m_getfld_state_t s, int c)
{
register int i;
char text[MAX_DELIMITER_SIZE];
char *cp;
for (i = 0, cp = text; i < s->edelimlen; ++i, ++cp) {
if ((signed char) (*cp = Getc (s)) == EOF) {
break;
}
}
if (i != s->edelimlen ||
strncmp (text, (char *)s->edelim, s->edelimlen)) {
if (i == 0 && s->msg_style == MS_MBOX)
/* the final newline in the (brain damaged) unix-format
* maildrop is part of the delimitter - delete it.
*/
return 1;
/* Did not find delimiter, so restore the read position.
Note that on input, a character had already been read
with Getc(). It will be unget by m_getfld () on return. */
s->readpos -= s->bytes_read - 1;
s->bytes_read = 1;
return 0;
}
if (s->msg_style == MS_MBOX) {
while ((c = Getc (s)) != '\n')
if (c < 0)
break;
}
return 1;
}
static char *
matchc(int patln, char *pat, int strln, char *str)
{
register char *es = str + strln - patln;
register char *sp;
register char *pp;
register char *ep = pat + patln;
register char pc = *pat++;
for(;;) {
while (pc != *str++)
if (str > es)
return 0;
if (str > es+1)
return 0;
sp = str; pp = pat;
while (pp < ep && *sp++ == *pp)
pp++;
if (pp >= ep)
return --str;
}
}
|