]> diplodocus.org Git - nmh/blobdiff - sbr/m_getfld.c
new.c: Order two return statements to match comment.
[nmh] / sbr / m_getfld.c
index c085bf5f16bdb5db4c5ad1861fb45bcbeb90ca71..f5fa9a4ebe93abfcb585b6375f0be6d0e7309282 100644 (file)
@@ -1,8 +1,4 @@
-
-/*
- * m_getfld.c -- read/parse a message
- *
- * $Id$
+/* 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
@@ -12,6 +8,75 @@
 #include <h/mh.h>
 #include <h/mts.h>
 #include <h/utils.h>
+#include <inttypes.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 and scan when reading from a
+   maildrop file.
+
+   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
+
+   Usage
+   =====
+   m_getfld_state_t gstate;
+
+   gstate = m_getfld_state_init(mailfp);
+   Perhaps m_getfld_track_filepos2(&gstate);
+   ...
+      state = m_getfld2(&gstate, ...);
+      ...Repeat until finished with mailfp.
+   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
@@ -27,9 +92,9 @@
    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
+   To speed things up considerably, the routine Eom() was made an auxiliary
    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
+   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
    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
+   "memmove" 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.
    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"
+   (>500 byte) chunks.  The makes the cost of a call to "memmove"
    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"
    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
    there is data in "name" or "buf").
   */
 
-
 /*
  * static prototypes
  */
-static int m_Eom (int, FILE *);
-static unsigned char *matchc(int, char *, int, char *);
-static unsigned char *locc(int, unsigned char *, unsigned char);
-
-#define Getc(iob)      getc(iob)
-#define eom(c,iob)     (msg_style != MS_DEFAULT && \
-                        (((c) == *msg_delim && m_Eom(c,iob)) ||\
-                         (eom_action && (*eom_action)(c))))
+static void Ungetc(m_getfld_state_t s);
+static int m_Eom (m_getfld_state_t);
 
-static unsigned char **pat_map;
+#define eom(c,s)       (s->msg_style != MS_DEFAULT && \
+                        ((c) == *s->msg_delim && m_Eom(s)))
 
 /*
- * defined in sbr/m_msgdef.c = 0
- * This is a disgusting hack for "inc" so it can know how many
- * characters were stuffed in the buffer on the last call
- * (see comments in uip/scansbr.c).
+ * Maildrop styles
  */
-extern int msg_count;
+#define        MS_DEFAULT      0       /* default (one msg per file) */
+#define        MS_UNKNOWN      1       /* type not known yet         */
+#define        MS_MBOX         2       /* Unix-style "from" lines    */
+#define        MS_MMDF         3       /* string MMDF_DELIM          */
 
-/*
- * defined in sbr/m_msgdef.c = MS_DEFAULT
+/* 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 8192.
  */
-extern int msg_style;
+#define MSG_INPUT_SIZE NMH_BUFSIZ
+#define MAX_DELIMITER_SIZE 5
+
+struct m_getfld_state {
+    /* The file to read from;  I/O block.  Caller keeps passing it after
+     * initialisation due to historic interface so it keeps getting
+     * updated, presumably to the same value. */
+    FILE *iob;
+
+    /* Holds content of iob. */
+    char msg_buf[2 * MSG_INPUT_SIZE + MAX_DELIMITER_SIZE];
+    /* Points to the next byte to read from msg_buf. */
+    char *readpos;
+    /* Points to just after the last valid byte in msg_buf.  If readpos
+     * equals end then msg_buf is empty. */
+    char *end;
+
+    /* Whether the caller intends to ftell(3)/fseek(3) iob's position,
+     * and thus whether m_getfld() needs to detect that and compensate. */
+    int track_filepos;
+    /* Position in iob given what's been consumed ready for returning to
+     * the caller.  Further than this may have been read into msg_buf. */
+    off_t total_bytes_read;
+    /* Bytes of iob consumed during this call. */
+    off_t bytes_read;
+    /* What fseeko(3) tells us iob's position is having just explicitly
+     * set it to total_bytes_read.  Surely always the same? */
+    off_t last_caller_pos;
+    /* Saved position in iob from filling msg_buf, prior to returning. */
+    off_t last_internal_pos;
+
+    /* One of the MS_* macros tracking the type of iob's content and
+     * thus if it's a single email, or several with delimeters.  Default
+     * is MS_DEFAULT. */
+    int msg_style;
+
+    /* The message delimeter if iob has multiple emails, else NULL.  For
+     * MS_MBOX it's the string that separates two emails, "\nFrom ",
+     * i.e. the terminating blank line of the previous email, and the
+     * starting From_ line of the next, but for MS_MMDF it's
+     * "\001\001\001\001\n" that may start or terminate an email. */
+    char *msg_delim;
+    /* The last non-NUL char of msg_delim. */
+    char *delimend;
+    /* When searching for msg_delim after an email, it's only of
+     * interest at the start of the line, i.e. when preceded by a
+     * linefeed.  fdelim points to msg_delim[-1] that contains '\n' so
+     * it can be used as the needle. */
+    char *fdelim;
+    /* strlen(fdelim). */
+    int fdelimlen;
+    /* The second char of msg_delim.  Used when the first char has
+     * already been matched to test the rest. */
+    char *edelim;
+    /* strlen(edelim). */
+    int edelimlen;
+    /* The relationship between all of these pointers and lengths for
+     * the two possible msg_delim values.
+     *
+     *     "\0\n\nFrom \0"   9              "\0\n\001\001\001\001\n\0"   8
+     *         | ||   |                         |   |   |         |
+     *         | ||   s->delimend               |   |   |         s->delimend
+     *         | ||                             |   |   |
+     *         | |s->edelim  s->edelimlen=5     |   |   s->edelim  s->edelimlen=4
+     *         | |                              |   |
+     *         | s->msg_delim                   |   s->msg_delim
+     *         |                                |
+     *         s->fdelim  s->fdelimlen=7        s->fdelim  s->fdelimlen=6
+     */
+
+    /* Maps all the bytes of msg_delim, apart from the last two,
+     * including the NUL, onto the last position in msg_delim where they
+     * occur.  Bytes not present are NULL. */
+    char **pat_map;
+
+    /* The parser's current state.  Also returned to the caller, amongst
+     * other possible values, to indicate the token consumed.  One of
+     * FLD, FLDPLUS, BODY, or FILEEOF. */
+    int state;
+};
+
+m_getfld_state_t m_getfld_state_init(FILE *iob)
+{
+    m_getfld_state_t s;
+
+    NEW(s);
+    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->state = FLD;
+    s->track_filepos = 0;
+
+    return s;
+}
+
+/* scan() needs to force 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) {
+       *gstate = m_getfld_state_init(iob);
+    }
+
+    (*gstate)->track_filepos = 1;
+}
+
+/* m_getfld_track_filepos() with the existing iob. */
+void m_getfld_track_filepos2(m_getfld_state_t *gstate)
+{
+    if (!*gstate)
+       adios(NULL, "m_getfld_track_filepos2 without gstate");
+
+    m_getfld_track_filepos(gstate, (*gstate)->iob);
+}
+
+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;
+    }
+}
 
 /*
- * 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.
- */
-extern char *msg_delim;         /* defined in sbr/m_msgdef.c = "" */
-static unsigned char *fdelim;
-static unsigned char *delimend;
-static int fdelimlen;
-static unsigned char *edelim;
-static int edelimlen;
-
-static int (*eom_action)() = NULL;
-
-#ifdef _FSTDIO
-# define _ptr    _p            /* Gag   */
-# define _cnt    _r            /* Retch */
-# define _filbuf __srget       /* Puke  */
-# define DEFINED__FILBUF_TO_SOMETHING_SPECIFIC
-#endif
-
-#ifdef SCO_5_STDIO
-# define _ptr  __ptr
-# define _cnt  __cnt
-# define _base __base
-# define _filbuf(fp)  ((fp)->__cnt = 0, __filbuf(fp))
-# define DEFINED__FILBUF_TO_SOMETHING_SPECIFIC
-#endif
-
-#ifndef DEFINED__FILBUF_TO_SOMETHING_SPECIFIC
-extern int  _filbuf(FILE*);
-#endif
+  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;
+    off_t pos_movement;
+
+    if (! *gstate) {
+       *gstate = m_getfld_state_init(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)
+        return;
+
+    if ((pos = ftello(iob)) == -1)
+        adios("getfld's iob", "failed to get offset on entry");
+    if (pos == 0 && s->last_internal_pos == 0)
+        return;
+
+    if (s->last_internal_pos == 0) {
+        s->total_bytes_read = pos;
+        return;
+    }
+
+    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 {
+            off_t off;
+            size_t num_read;
+
+            /* This seek skips past an integral number of
+               chunks of size MSG_INPUT_SIZE. */
+            off = pos / MSG_INPUT_SIZE * MSG_INPUT_SIZE;
+            if (fseeko(iob, off, SEEK_SET) == -1)
+                adios("getfld's iob", "failed to set offset to skip: "
+                    "%" PRIdMAX, (intmax_t)off);
+            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;
+        }
+    }
+
+    if (fseeko(iob, pos, SEEK_SET) == -1)
+        adios("getfld's iob", "failed to set offset on entry: %" PRIdMAX,
+            (intmax_t)pos);
+}
+
+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. */
+        if ((s->last_internal_pos = ftello(s->iob)) == -1)
+            adios("getfld's iob", "failed to get offset before seek");
+
+       /* Set file stream position so that callers can use ftell(). */
+        if (fseeko(s->iob, s->total_bytes_read, SEEK_SET) == -1)
+            adios("getfld's iob", "failed to set offset: %" PRIdMAX,
+                (intmax_t)s->total_bytes_read);
+
+        s->last_caller_pos = s->total_bytes_read;
+    }
+}
+
+static size_t
+read_more (m_getfld_state_t s) {
+    /* Retain at least edelimlen characters that have already been read,
+       if at least edelimlen have been read, so that we can back up to them
+       in m_Eom(). */
+    ssize_t retain = s->end - s->msg_buf < s->edelimlen ? 0 : s->edelimlen;
+    size_t num_read;
+
+    if (retain > 0) {
+        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;
+}
+
+/* Return the next character consumed from the input, fetching more of
+ * the input for the buffer if required, or EOF on end of file. */
+static int
+Getc (m_getfld_state_t s) {
+    if ((s->end - s->readpos < 1 && read_more (s) == 0) ||
+        s->readpos >= s->end)
+        return EOF;
+
+    s->bytes_read++;
+    return (unsigned char)*s->readpos++;
+}
+
+/* Return the next character that Getc() would return, which may be EOF. */
+static int
+Peek (m_getfld_state_t s)
+{
+    int c;
+
+    c = Getc(s);
+    if (c != EOF)
+        Ungetc(s);
+
+    return c;
+}
+
+/* If there's room, undo the consumption of one character from msg_buf,
+ * rewinding so it's read next, else die. */
+static void
+Ungetc(m_getfld_state_t s)
+{
+    if (s->readpos == s->msg_buf)
+        adios(NULL, "Ungetc() at start of message buffer.");
+
+    s->readpos--;
+    s->bytes_read--;
+}
 
 
 int
-m_getfld (int state, unsigned char *name, unsigned char *buf,
-          int bufsz, FILE *iob)
+m_getfld (m_getfld_state_t *gstate, char name[NAMESZ], char *buf, int *bufsz,
+          FILE *iob)
 {
-    register unsigned char  *bp, *cp, *ep, *sp;
-    register int cnt, c, i, j;
+    m_getfld_state_t s;
+    char *cp;
+    int max, n, c;
+
+    enter_getfld (gstate, iob);
+    s = *gstate;
 
-    if ((c = Getc(iob)) < 0) {
-       msg_count = 0;
-       *buf = 0;
-       return FILEEOF;
+    if ((c = Getc(s)) == EOF) {
+       *bufsz = *buf = 0;
+       leave_getfld (s);
+       return s->state = FILEEOF;
     }
-    if (eom (c, iob)) {
-       if (! eom_action) {
-           /* flush null messages */
-           while ((c = Getc(iob)) >= 0 && eom (c, iob))
-               ;
-           if (c >= 0)
-               ungetc(c, iob);
-       }
-       msg_count = 0;
-       *buf = 0;
-       return FILEEOF;
+    if (eom (c, s)) {
+       /* flush null messages */
+       while ((c = Getc(s)) != EOF && eom (c, s))
+           ;
+
+       if (c != EOF)
+            Ungetc(s);
+       *bufsz = *buf = 0;
+       leave_getfld (s);
+       return s->state = FILEEOF;
     }
 
-    switch (state) {
-       case FLDEOF: 
-       case BODYEOF: 
-       case FLD: 
+    switch (s->state) {
+       case FLD:
            if (c == '\n' || c == '-') {
                /* we hit the header/body separator */
-               while (c != '\n' && (c = Getc(iob)) >= 0)
-                   ;
-
-               if (c < 0 || (c = Getc(iob)) < 0 || eom (c, iob)) {
-                   if (! eom_action) {
-                       /* flush null messages */
-                       while ((c = Getc(iob)) >= 0 && eom (c, iob))
-                           ;
-                       if (c >= 0)
-                           ungetc(c, iob);
-                   }
-                   msg_count = 0;
-                   *buf = 0;
-                   return FILEEOF;
+               while (c != '\n' && (c = Getc(s)) != EOF)
+                    ;
+
+               if (c == EOF || (c = Getc(s)) == EOF || eom (c, s)) {
+                   /* flush null messages */
+                   while ((c = Getc(s)) != EOF && eom (c, s))
+                       ;
+                   if (c != EOF)
+                        Ungetc(s);
+                   *bufsz = *buf = 0;
+                   leave_getfld (s);
+                   return s->state = FILEEOF;
                }
-               state = BODY;
+               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.  
+            * comes first.
             */
            cp = name;
-           i = NAMESZ - 1;
-           for (;;) {
-#ifdef LINUX_STDIO
-               bp = sp = (unsigned char *) iob->_IO_read_ptr - 1;
-               j = (cnt = ((long) iob->_IO_read_end -
-                       (long) iob->_IO_read_ptr)  + 1) < i ? cnt : i;
-#else
-               bp = sp = (unsigned char *) iob->_ptr - 1;
-               j = (cnt = iob->_cnt+1) < i ? cnt : i;
-#endif
-               while (--j >= 0 && (c = *bp++) != ':' && c != '\n')
-                   *cp++ = c;
-
-               j = bp - sp;
-               if ((cnt -= j) <= 0) {
-#ifdef LINUX_STDIO
-                   iob->_IO_read_ptr = iob->_IO_read_end;
-                   if (__underflow(iob) == EOF) {
-#else
-                   if (_filbuf(iob) == EOF) {
-#endif
-                       *cp = *buf = 0;
-                       advise (NULL, "eof encountered in field \"%s\"", name);
-                       return FMTERR;
-                   }
-#ifdef LINUX_STDIO
-               iob->_IO_read_ptr++; /* NOT automatic in __underflow()! */
-#endif
-               } else {
-#ifdef LINUX_STDIO
-                   iob->_IO_read_ptr = bp + 1;
-#else
-                   iob->_ptr = bp + 1;
-                   iob->_cnt = cnt - 1;
-#endif
-               }
-               if (c == ':')
-                   break;
+           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;
+           }
 
-               /*
-                * something went wrong.  possibilities are:
-                *  . hit a newline (error)
-                *  . got more than namesz chars. (error)
-                *  . hit the end of the buffer. (loop)
-                */
-               if (c == '\n') {
-                   *cp = *buf = 0;
-                   advise (NULL, "eol encountered in field \"%s\"", name);
-                   state = FMTERR;
-                   goto finish;
+           /* 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;
+                   inform("eof encountered in field \"%s\"", name);
+                   leave_getfld (s);
+                   return s->state = FMTERR;
                }
-               if ((i -= j) <= 0) {
-                   *cp = *buf = 0;
-                   advise (NULL, "field name \"%s\" exceeds %d bytes", name, NAMESZ - 1);
-                   state = LENERR;
-                   goto finish;
+           }
+
+           /* 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;
+                   inform("eol encountered in field \"%s\"", name);
+                   s->state = FMTERR;
+                   break;
                }
+               memcpy (buf, name, n - 1);
+               buf[n - 1] = '\n';
+               buf[n] = '\0';
+                /* Indicate this wasn't a header field using a character
+                   that can't appear in a header field. */
+                name[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;
+           }
+            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;
+               inform("field name \"%s\" exceeds %d bytes", name,
+                       NAMESZ - 2);
+               s->state = LENERR;
+               break;
            }
 
-           while (isspace (*--cp) && cp >= name)
-               ;
+           /* Trim any trailing spaces from the end of name. */
+           while (isspace ((unsigned char) *--cp) && cp >= name) continue;
            *++cp = 0;
-           /* fall through */
+           /* readpos points to the first character of the field body. */
+           /* FALLTHRU */
 
-       case FLDPLUS: 
+       case FLDPLUS: {
            /*
-            * get (more of) the text of a field.  take
+            * 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.
             */
-           cp = buf; i = bufsz-1;
-           for (;;) {
-#ifdef LINUX_STDIO
-               cnt = (long) iob->_IO_read_end - (long) iob->_IO_read_ptr;
-               bp = (unsigned char *) --iob->_IO_read_ptr;
-#else
-               cnt = iob->_cnt++;
-               bp = (unsigned char *) --iob->_ptr;
-#endif
-               c = cnt < i ? cnt : i;
-               while ((ep = locc( c, bp, '\n' ))) {
-                   /*
-                    * if we hit the end of this field, return.
-                    */
-                   if ((j = *++ep) != ' ' && j != '\t') {
-#ifdef LINUX_STDIO
-                       j = ep - (unsigned char *) iob->_IO_read_ptr;
-                       memcpy (cp, iob->_IO_read_ptr, j);
-                       iob->_IO_read_ptr = ep;
-#else
-                       j = ep - (unsigned char *) iob->_ptr;
-                       memcpy (cp, iob->_ptr, j);
-                       iob->_ptr = ep;
-                       iob->_cnt -= j;
-#endif
-                       cp += j;
-                       state = FLD;
-                       goto finish;
-                   }
-                   c -= ep - bp;
-                   bp = ep;
+           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;
                }
-               /*
-                * end of input or dest buffer - copy what we've found.
-                */
-#ifdef LINUX_STDIO
-               c += bp - (unsigned char *) iob->_IO_read_ptr;
-               memcpy( cp, iob->_IO_read_ptr, c);
-#else
-               c += bp - (unsigned char *) iob->_ptr;
-               memcpy( cp, iob->_ptr, c);
-#endif
-               i -= c;
-               cp += c;
-               if (i <= 0) {
-                   /* the dest buffer is full */
-#ifdef LINUX_STDIO
-                   iob->_IO_read_ptr += c;
-#else
-                   iob->_cnt -= c;
-                   iob->_ptr += c;
-#endif
-                   state = FLDPLUS;
-                   break;
-               }
-               /* 
-                * There's one character left in the input buffer.
-                * Copy it & fill the buffer.  If the last char
-                * was a newline and the next char is not whitespace,
-                * this is the end of the field.  Otherwise loop.
-                */
-               --i;
-#ifdef LINUX_STDIO
-               *cp++ = j = *(iob->_IO_read_ptr + c);
-               iob->_IO_read_ptr = iob->_IO_read_end;
-               c = __underflow(iob);
-               iob->_IO_read_ptr++;    /* NOT automatic! */
-#else
-               *cp++ = j = *(iob->_ptr + c);
-               c = _filbuf(iob);
-#endif
-                if (c == EOF || 
-                 ((j == '\0' || j == '\n') && c != ' ' && c != '\t')) {
-                   if (c != EOF) {
-#ifdef LINUX_STDIO
-                       --iob->_IO_read_ptr;
-#else
-                       --iob->_ptr;
-                       ++iob->_cnt;
-#endif
+
+               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;
                    }
-                   state = FLD;
-                   break;
+                   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;
+        }
 
-       case BODY: 
        body:
+       case BODY: {
            /*
             * get the message body up to bufsz characters or the
-            * end of the message.  Sleazy hack: if bufsz is negative
-            * we assume that we were called to copy directly into
-            * the output buffer and we don't add an eos.
+            * end of the message.
             */
-           i = (bufsz < 0) ? -bufsz : bufsz-1;
-#ifdef LINUX_STDIO
-           bp = (unsigned char *) --iob->_IO_read_ptr;
-           cnt = (long) iob->_IO_read_end - (long) iob->_IO_read_ptr;
-#else
-           bp = (unsigned char *) --iob->_ptr;
-           cnt = ++iob->_cnt;
-#endif
-           c = (cnt < i ? cnt : i);
-           if (msg_style != MS_DEFAULT && c > 1) {
+           char *bp;
+
+            name[0] = '\0';
+           max = *bufsz-1;
+           /* Back up and store the current position. */
+           bp = --s->readpos;
+            c = min(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
@@ -447,7 +758,12 @@ m_getfld (int state, unsigned char *name, unsigned char *buf,
                 * algorithms vs. brute force.)  Since I (currently)
                 * run MH on a vax, we use the matchc instruction. --vj
                 */
-               if ((ep = matchc( fdelimlen, fdelim, c, bp )))
+               char *ep;
+
+                if ((ep = memmem(bp, c, s->fdelim, s->fdelimlen)))
+                    /* Plus one to nab the '\n' that starts fdelim as
+                     * that ends the previous line;  it isn't part of
+                     * msg_delim. */
                    c = ep - bp + 1;
                else {
                    /*
@@ -463,70 +779,96 @@ m_getfld (int state, unsigned char *name, unsigned char *buf,
                     * ends with one of the characters in the pattern
                     * (excluding the first and last), we do only one test.
                     */
-                   ep = bp + c - 1;
-                   if ((sp = pat_map[*ep])) {
+                   char *sp;
+
+                    ep = bp + c - 1; /* The last byte. */
+                   if ((sp = s->pat_map[(unsigned char) *ep])) {
                        do {
-                           cp = sp;
-                           while (*--ep == *--cp)
-                           ;
-                           if (cp < fdelim) {
-                               if (ep >= bp)
-                                   /*
-                                    * ep < bp means that all the buffer
-                                    * contains is a prefix of delim.
-                                    * If this prefix is really a delim, the
-                                    * m_eom call at entry should have found
-                                    * it.  Thus it's not a delim and we can
-                                    * take all of it.
+                           /* 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;
-                       }
+                                   break;
+                               }
+                           }
                            /* try matching one less char of delim string */
                            ep = bp + c - 1;
-                       } while (--sp > fdelim);
+                       } while (--sp > s->fdelim);
                    }
                }
            }
            memcpy( buf, bp, c );
-#ifdef LINUX_STDIO
-           iob->_IO_read_ptr += c;
-#else
-           iob->_cnt -= c;
-           iob->_ptr += c;
-#endif
-           if (bufsz < 0) {
-               msg_count = c;
-               return (state);
-           }
+           /* 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", state);
+       default:
+           adios (NULL, "m_getfld() called with bogus state of %d", s->state);
     }
-finish:
+
     *cp = 0;
-    msg_count = cp - buf;
-    return (state);
+    leave_getfld (s);
+
+    return s->state;
 }
 
 
-#ifdef RPATHS
-static char unixbuf[BUFSIZ] = "";
-#endif /* RPATHS */
+/* m_getfld() with the existing iob. */
+int m_getfld2(m_getfld_state_t *gstate, char name[NAMESZ], char *buf, int *bufsz)
+{
+    if (!*gstate)
+       adios(NULL, "m_getfld2 without gstate");
+
+    return m_getfld(gstate, name, buf, bufsz, (*gstate)->iob);
+}
+
 
 void
-m_unknown(FILE *iob)
+m_unknown(m_getfld_state_t *gstate, FILE *iob)
 {
-    register int c;
-    register long pos;
-    char text[10];
-    register char *cp;
-    register char *delimstr;
+    m_getfld_state_t s;
+    int c;
+    char text[MAX_DELIMITER_SIZE];
+    char from[] = "From ";
+    char *cp;
+    char *delimstr;
+    unsigned int i;
+
+    enter_getfld (gstate, iob);
+    s = *gstate;
 
 /*
- * Figure out what the message delimitter string is for this
+ * Figure out what the message delimiter 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
@@ -538,77 +880,76 @@ m_unknown(FILE *iob)
  * specified when nmh was built (or from the mts.conf file).
  */
 
-    msg_style = MS_UNKNOWN;
+    s->msg_style = MS_UNKNOWN;
 
-    pos = ftell (iob);
-    if (fread (text, sizeof(*text), 5, iob) == 5
-           && strncmp (text, "From ", 5) == 0) {
-       msg_style = MS_MBOX;
+    for (i = 0, cp = text; i < sizeof text; ++i, ++cp) {
+       if ((c = Getc (s)) == EOF) {
+           *cp = '\0';
+           break;
+       }
+        *cp = c;
+    }
+
+    if (i == sizeof from-1  &&  strncmp (text, "From ", sizeof from-1) == 0) {
+       s->msg_style = MS_MBOX;
        delimstr = "\nFrom ";
-#ifndef        RPATHS
-       while ((c = getc (iob)) != '\n' && c >= 0)
-           ;
-#else /* RPATHS */
-       cp = unixbuf;
-       while ((c = getc (iob)) != '\n' && cp - unixbuf < BUFSIZ - 1)
-           *cp++ = c;
-       *cp = 0;
-#endif /* RPATHS */
+       while ((c = Getc(s)) != EOF && c != '\n')
+            ;
     } else {
        /* not a Unix style maildrop */
-       fseek (iob, pos, SEEK_SET);
-       if (mmdlm2 == NULL || *mmdlm2 == 0)
-           mmdlm2 = "\001\001\001\001\n";
-       delimstr = mmdlm2;
-       msg_style = MS_MMDF;
+       s->readpos -= s->bytes_read;
+       s->bytes_read = 0;
+       delimstr = MMDF_DELIM;
+       s->msg_style = MS_MMDF;
     }
+
+    /*     "\nFrom \0"   7                  "\001\001\001\001\n\0"  6
+     *       |                                  |
+     *       delimstr   c=6                     delimstr   c=5
+     */
     c = strlen (delimstr);
-    fdelim = (unsigned char *) mh_xmalloc((size_t) (c + 3));
-    *fdelim++ = '\0';
-    *fdelim = '\n';
-    msg_delim = (char *)fdelim+1;
-    edelim = (unsigned char *)msg_delim+1;
-    fdelimlen = c + 1;
-    edelimlen = c - 1;
-    strcpy (msg_delim, delimstr);
-    delimend = (unsigned char *)msg_delim + edelimlen;
-    if (edelimlen <= 1)
+    s->fdelim = mh_xmalloc (c + 3); /* \0, \n, delimstr, \0 */
+    *s->fdelim++ = '\0';
+    *s->fdelim = '\n';
+    s->fdelimlen = c + 1;
+    s->msg_delim = s->fdelim+1;
+    strcpy (s->msg_delim, delimstr);
+    s->edelim = s->msg_delim+1;
+    s->edelimlen = c - 1;
+    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).
      */
-    pat_map = (unsigned char **) calloc (256, sizeof(unsigned char *));
+    s->pat_map = (char **) mh_xcalloc (256, sizeof(char *));
 
-    for (cp = (char *) fdelim + 1; cp < (char *) delimend; cp++ )
-       pat_map[(unsigned char)*cp] = (unsigned char *) cp;
+    for (cp = s->fdelim + 1; cp < s->delimend; cp++ )
+       s->pat_map[(unsigned char)*cp] = cp;
 
-    if (msg_style == MS_MMDF) {
+    if (s->msg_style == MS_MMDF) {
        /* flush extra msg hdrs */
-       while ((c = Getc(iob)) >= 0 && eom (c, iob))
+       while ((c = Getc(s)) != EOF && eom (c, s))
            ;
-       if (c >= 0)
-           ungetc(c, iob);
+       if (c != EOF)
+            Ungetc(s);
     }
+
+    leave_getfld (s);
 }
 
 
-void
-m_eomsbr (int (*action)())
+/* m_unknown() with the existing iob. */
+void m_unknown2(m_getfld_state_t *gstate)
 {
-    if ((eom_action = action)) {
-       msg_style = MS_MSH;
-       *msg_delim = 0;
-       fdelimlen = 1;
-       delimend = fdelim;
-    } else {
-       msg_style = MS_MMDF;
-       msg_delim = (char *)fdelim + 1;
-       fdelimlen = strlen((char *)fdelim);
-       delimend = (unsigned char *)(msg_delim + edelimlen);
-    }
+    if (!*gstate)
+       adios(NULL, "m_unknown2 without gstate");
+
+    m_unknown(gstate, (*gstate)->iob);
 }
 
 
@@ -617,145 +958,53 @@ m_eomsbr (int (*action)())
  */
 
 static int
-m_Eom (int c, FILE *iob)
+m_Eom (m_getfld_state_t s)
 {
-    register long pos = 0L;
-    register int i;
-    char text[10];
-#ifdef RPATHS
-    register char *cp;
-#endif /* RPATHS */
-
-    pos = ftell (iob);
-    if ((i = fread (text, sizeof *text, edelimlen, iob)) != edelimlen
-           || strncmp (text, (char *)edelim, edelimlen)) {
-       if (i == 0 && msg_style == MS_MBOX)
-           /* the final newline in the (brain damaged) unix-format
-            * maildrop is part of the delimitter - delete it.
-            */
-           return 1;
+    int i;
+    char text[MAX_DELIMITER_SIZE];
+    char *cp;
+    int adjust = 1;
 
-#if 0
-       fseek (iob, pos, SEEK_SET);
-#endif
-
-       fseek (iob, (long)(pos-1), SEEK_SET);
-       getc (iob);             /* should be OK */
-       return 0;
-    }
+    for (i = 0, cp = text; i < s->edelimlen; ++i, ++cp) {
+       int c2;
 
-    if (msg_style == MS_MBOX) {
-#ifndef RPATHS
-       while ((c = getc (iob)) != '\n')
-           if (c < 0)
-               break;
-#else /* RPATHS */
-       cp = unixbuf;
-       while ((c = getc (iob)) != '\n' && c >= 0 && cp - unixbuf < BUFSIZ - 1)
-           *cp++ = c;
-       *cp = 0;
-#endif /* RPATHS */
+       if ((c2 = Getc (s)) == EOF) {
+           *cp = '\0';
+           break;
+       }
+       *cp = c2;
     }
 
-    return 1;
-}
-
-
-#ifdef RPATHS
-/*
- * Return the Return-Path and Delivery-Date
- * header information.
- *
- * Currently, I'm assuming that the "From " line
- * takes one of the following forms.
- *
- * From sender date remote from host   (for UUCP delivery)
- * From sender@host  date              (for sendmail delivery)
- */
+    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 delimiter - delete it.
+            */
+           return 1;
+       }
 
-int
-get_returnpath (char *rp, int rplen, char *dd, int ddlen)
-{
-    char *ap, *bp, *cp, *dp;
+       if (i <= 2  &&  s->msg_style == MS_MBOX  &&
+           i != s->edelimlen  &&  ! strncmp(text, s->fdelim, i)) {
+           /* If all or part of fdelim appeared at the end of the file,
+              back up even more so that the bytes are included in the
+              message. */
+           adjust = 2;
+       }
 
-    ap = unixbuf;
-    if (!(bp = cp = strchr(ap, ' ')))
+       /* 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 - adjust;
+       s->bytes_read = adjust;
        return 0;
-
-    /*
-     * Check for "remote from" in envelope to see
-     * if this message uses UUCP style addressing
-     */
-    while ((cp = strchr(++cp, 'r'))) {
-       if (strncmp (cp, "remote from", 11) == 0) {
-           cp = strrchr (cp, ' ');
-           break;
-       }
     }
 
-    /*
-     * Get the Return-Path information from
-     * the "From " envelope.
-     */
-    if (cp) {
-       /* return path for UUCP style addressing */
-       dp = strchr (++cp, '\n');
-       snprintf (rp, rplen, "%.*s!%.*s\n", dp - cp, cp, bp - ap, ap);
-    } else {
-       /* return path for standard domain addressing */
-       snprintf (rp, rplen, "%.*s\n", bp - ap, ap);
+    if (s->msg_style == MS_MBOX) {
+       int c;
+       while ((c = Getc(s)) != EOF && c != '\n')
+            ;
     }
 
-    /*
-     * advance over the spaces to get to
-     * delivery date on envelope
-     */
-    while (*bp == ' ')
-       bp++;
-
-    /* Now get delivery date from envelope */
-    snprintf (dd, ddlen, "%.*s\n", 24, bp);
-
-    unixbuf[0] = 0;
     return 1;
 }
-#endif /* RPATHS */
-
-
-static unsigned 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 ((unsigned char *)--str);
-       }
-}
-
-
-/*
- * Locate character "term" in the next "cnt" characters of "src".
- * If found, return its address, otherwise return 0.
- */
-
-static unsigned char *
-locc(int cnt, unsigned char *src, unsigned char term)
-{
-    while (*src++ != term && --cnt > 0);
-
-    return (cnt > 0 ? --src : (unsigned char *)0);
-}
-