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