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