Blob Blame History Raw
/*
     This file is part of libmicrohttpd
     Copyright (C) 2007-2013 Daniel Pittman and Christian Grothoff

     This library is free software; you can redistribute it and/or
     modify it under the terms of the GNU Lesser General Public
     License as published by the Free Software Foundation; either
     version 2.1 of the License, or (at your option) any later version.

     This library is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     Lesser General Public License for more details.

     You should have received a copy of the GNU Lesser General Public
     License along with this library; if not, write to the Free Software
     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

/**
 * @file postprocessor.c
 * @brief  Methods for parsing POST data
 * @author Christian Grothoff
 */

#include "internal.h"
#include "mhd_str.h"
#include "mhd_compat.h"

/**
 * Size of on-stack buffer that we use for un-escaping of the value.
 * We use a pretty small value to be nice to the stack on embedded
 * systems.
 */
#define XBUF_SIZE 512

/**
 * States in the PP parser's state machine.
 */
enum PP_State
{
  /* general states */
  PP_Error,
  PP_Done,
  PP_Init,
  PP_NextBoundary,

  /* url encoding-states */
  PP_ProcessValue,
  PP_ExpectNewLine,

  /* post encoding-states  */
  PP_ProcessEntryHeaders,
  PP_PerformCheckMultipart,
  PP_ProcessValueToBoundary,
  PP_PerformCleanup,

  /* nested post-encoding states */
  PP_Nested_Init,
  PP_Nested_PerformMarking,
  PP_Nested_ProcessEntryHeaders,
  PP_Nested_ProcessValueToBoundary,
  PP_Nested_PerformCleanup

};


enum RN_State
{
  /**
   * No RN-preprocessing in this state.
   */
  RN_Inactive = 0,

  /**
   * If the next character is CR, skip it.  Otherwise,
   * just go inactive.
   */
  RN_OptN = 1,

  /**
   * Expect LFCR (and only LFCR).  As always, we also
   * expect only LF or only CR.
   */
  RN_Full = 2,

  /**
   * Expect either LFCR or '--'LFCR.  If '--'LFCR, transition into dash-state
   * for the main state machine
   */
  RN_Dash = 3,

  /**
   * Got a single dash, expect second dash.
   */
  RN_Dash2 = 4
};


/**
 * Bits for the globally known fields that
 * should not be deleted when we exit the
 * nested state.
 */
enum NE_State
{
  NE_none = 0,
  NE_content_name = 1,
  NE_content_type = 2,
  NE_content_filename = 4,
  NE_content_transfer_encoding = 8
};


/**
 * Internal state of the post-processor.  Note that the fields
 * are sorted by type to enable optimal packing by the compiler.
 */
struct MHD_PostProcessor
{

  /**
   * The connection for which we are doing
   * POST processing.
   */
  struct MHD_Connection *connection;

  /**
   * Function to call with POST data.
   */
  MHD_PostDataIterator ikvi;

  /**
   * Extra argument to ikvi.
   */
  void *cls;

  /**
   * Encoding as given by the headers of the
   * connection.
   */
  const char *encoding;

  /**
   * Primary boundary (points into encoding string)
   */
  const char *boundary;

  /**
   * Nested boundary (if we have multipart/mixed encoding).
   */
  char *nested_boundary;

  /**
   * Pointer to the name given in disposition.
   */
  char *content_name;

  /**
   * Pointer to the (current) content type.
   */
  char *content_type;

  /**
   * Pointer to the (current) filename.
   */
  char *content_filename;

  /**
   * Pointer to the (current) encoding.
   */
  char *content_transfer_encoding;

  /**
   * Unprocessed value bytes due to escape
   * sequences (URL-encoding only).
   */
  char xbuf[8];

  /**
   * Size of our buffer for the key.
   */
  size_t buffer_size;

  /**
   * Current position in the key buffer.
   */
  size_t buffer_pos;

  /**
   * Current position in @e xbuf.
   */
  size_t xbuf_pos;

  /**
   * Current offset in the value being processed.
   */
  uint64_t value_offset;

  /**
   * strlen(boundary) -- if boundary != NULL.
   */
  size_t blen;

  /**
   * strlen(nested_boundary) -- if nested_boundary != NULL.
   */
  size_t nlen;

  /**
   * Do we have to call the 'ikvi' callback when processing the
   * multipart post body even if the size of the payload is zero?
   * Set to #MHD_YES whenever we parse a new multiparty entry header,
   * and to #MHD_NO the first time we call the 'ikvi' callback.
   * Used to ensure that we do always call 'ikvi' even if the
   * payload is empty (but not more than once).
   */
  int must_ikvi;

  /**
   * State of the parser.
   */
  enum PP_State state;

  /**
   * Side-state-machine: skip LRCR (or just LF).
   * Set to 0 if we are not in skip mode.  Set to 2
   * if a LFCR is expected, set to 1 if a CR should
   * be skipped if it is the next character.
   */
  enum RN_State skip_rn;

  /**
   * If we are in skip_rn with "dash" mode and
   * do find 2 dashes, what state do we go into?
   */
  enum PP_State dash_state;

  /**
   * Which headers are global? (used to tell which
   * headers were only valid for the nested multipart).
   */
  enum NE_State have;

};


/**
 * Create a `struct MHD_PostProcessor`.
 *
 * A `struct MHD_PostProcessor` can be used to (incrementally) parse
 * the data portion of a POST request.  Note that some buggy browsers
 * fail to set the encoding type.  If you want to support those, you
 * may have to call #MHD_set_connection_value with the proper encoding
 * type before creating a post processor (if no supported encoding
 * type is set, this function will fail).
 *
 * @param connection the connection on which the POST is
 *        happening (used to determine the POST format)
 * @param buffer_size maximum number of bytes to use for
 *        internal buffering (used only for the parsing,
 *        specifically the parsing of the keys).  A
 *        tiny value (256-1024) should be sufficient.
 *        Do NOT use a value smaller than 256.  For good
 *        performance, use 32 or 64k (i.e. 65536).
 * @param iter iterator to be called with the parsed data,
 *        Must NOT be NULL.
 * @param iter_cls first argument to @a iter
 * @return NULL on error (out of memory, unsupported encoding),
 *         otherwise a PP handle
 * @ingroup request
 */
struct MHD_PostProcessor *
MHD_create_post_processor (struct MHD_Connection *connection,
                           size_t buffer_size,
                           MHD_PostDataIterator iter,
                           void *iter_cls)
{
  struct MHD_PostProcessor *ret;
  const char *encoding;
  const char *boundary;
  size_t blen;

  if ( (buffer_size < 256) ||
       (NULL == connection) ||
       (NULL == iter))
    mhd_panic (mhd_panic_cls,
               __FILE__,
               __LINE__,
               NULL);
  encoding = MHD_lookup_connection_value (connection,
                                          MHD_HEADER_KIND,
                                          MHD_HTTP_HEADER_CONTENT_TYPE);
  if (NULL == encoding)
    return NULL;
  boundary = NULL;
  if (! MHD_str_equal_caseless_n_ (MHD_HTTP_POST_ENCODING_FORM_URLENCODED,
                                   encoding,
                                   MHD_STATICSTR_LEN_ (MHD_HTTP_POST_ENCODING_FORM_URLENCODED)))
    {
      if (! MHD_str_equal_caseless_n_ (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA,
                                       encoding,
                                       MHD_STATICSTR_LEN_ (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA)))
        return NULL;
      boundary =
        &encoding[MHD_STATICSTR_LEN_ (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA)];
      /* Q: should this be "strcasestr"? */
      boundary = strstr (boundary, "boundary=");
      if (NULL == boundary)
	return NULL; /* failed to determine boundary */
      boundary += MHD_STATICSTR_LEN_ ("boundary=");
      blen = strlen (boundary);
      if ( (blen == 0) ||
           (blen * 2 + 2 > buffer_size) )
        return NULL;            /* (will be) out of memory or invalid boundary */
      if ( (boundary[0] == '"') &&
           (boundary[blen - 1] == '"') )
	{
	  /* remove enclosing quotes */
	  ++boundary;
	  blen -= 2;
	}
    }
  else
    blen = 0;
  buffer_size += 4; /* round up to get nice block sizes despite boundary search */

  /* add +1 to ensure we ALWAYS have a zero-termination at the end */
  if (NULL == (ret = MHD_calloc_ (1, sizeof (struct MHD_PostProcessor) + buffer_size + 1)))
    return NULL;
  ret->connection = connection;
  ret->ikvi = iter;
  ret->cls = iter_cls;
  ret->encoding = encoding;
  ret->buffer_size = buffer_size;
  ret->state = PP_Init;
  ret->blen = blen;
  ret->boundary = boundary;
  ret->skip_rn = RN_Inactive;
  return ret;
}


/**
 * Process url-encoded POST data.
 *
 * @param pp post processor context
 * @param post_data upload data
 * @param post_data_len number of bytes in @a post_data
 * @return #MHD_YES on success, #MHD_NO if there was an error processing the data
 */
static int
post_process_urlencoded (struct MHD_PostProcessor *pp,
                         const char *post_data,
			 size_t post_data_len)
{
  size_t equals;
  size_t amper;
  size_t poff;
  size_t xoff;
  size_t delta;
  int end_of_value_found;
  char *buf;
  char xbuf[XBUF_SIZE + 1];

  buf = (char *) &pp[1];
  poff = 0;
  while (poff < post_data_len)
    {
      switch (pp->state)
        {
        case PP_Error:
          return MHD_NO;
        case PP_Done:
          /* did not expect to receive more data */
          pp->state = PP_Error;
          return MHD_NO;
        case PP_Init:
          equals = 0;
          while ((equals + poff < post_data_len) &&
                 (post_data[equals + poff] != '='))
            equals++;
          if (equals + pp->buffer_pos > pp->buffer_size)
            {
              pp->state = PP_Error;     /* out of memory */
              return MHD_NO;
            }
          memcpy (&buf[pp->buffer_pos], &post_data[poff], equals);
          pp->buffer_pos += equals;
          if (equals + poff == post_data_len)
            return MHD_YES;     /* no '=' yet */
          buf[pp->buffer_pos] = '\0';   /* 0-terminate key */
          pp->buffer_pos = 0;   /* reset for next key */
	  MHD_unescape_plus (buf);
          MHD_http_unescape (buf);
          poff += equals + 1;
          pp->state = PP_ProcessValue;
          pp->value_offset = 0;
          break;
        case PP_ProcessValue:
          /* obtain rest of value from previous iteration */
          memcpy (xbuf, pp->xbuf, pp->xbuf_pos);
          xoff = pp->xbuf_pos;
          pp->xbuf_pos = 0;

          /* find last position in input buffer that is part of the value */
          amper = 0;
          while ((amper + poff < post_data_len) &&
                 (amper < XBUF_SIZE) &&
                 (post_data[amper + poff] != '&') &&
                 (post_data[amper + poff] != '\n') &&
                 (post_data[amper + poff] != '\r'))
            amper++;
          end_of_value_found = ((amper + poff < post_data_len) &&
                                ((post_data[amper + poff] == '&') ||
                                 (post_data[amper + poff] == '\n') ||
                                 (post_data[amper + poff] == '\r')));
          /* compute delta, the maximum number of bytes that we will be able to
             process right now (either amper-limited of xbuf-size limited) */
          delta = amper;
          if (delta > XBUF_SIZE - xoff)
            delta = XBUF_SIZE - xoff;

          /* move input into processing buffer */
          memcpy (&xbuf[xoff], &post_data[poff], delta);
          xoff += delta;
          poff += delta;

          /* find if escape sequence is at the end of the processing buffer;
             if so, exclude those from processing (reduce delta to point at
             end of processed region) */
          delta = xoff;
          if ((delta > 0) &&
              ('%' == xbuf[delta - 1]))
            delta--;
          else if ((delta > 1) &&
                   ('%' == xbuf[delta - 2]))
            delta -= 2;

          /* if we have an incomplete escape sequence, save it to
             pp->xbuf for later */
          if (delta < xoff)
            {
              memcpy (pp->xbuf,
                      &xbuf[delta],
                      xoff - delta);
              pp->xbuf_pos = xoff - delta;
              xoff = delta;
            }

          /* If we have nothing to do (delta == 0) and
             not just because the value is empty (are
             waiting for more data), go for next iteration */
          if ( (0 == xoff) &&
               (poff == post_data_len))
            continue;

          /* unescape */
          xbuf[xoff] = '\0';    /* 0-terminate in preparation */
	  MHD_unescape_plus (xbuf);
          xoff = MHD_http_unescape (xbuf);
          /* finally: call application! */
	  pp->must_ikvi = MHD_NO;
          if (MHD_NO == pp->ikvi (pp->cls,
                                  MHD_POSTDATA_KIND,
                                  (const char *) &pp[1],    /* key */
                                  NULL,
                                  NULL,
                                  NULL,
                                  xbuf,
                                  pp->value_offset,
                                  xoff))
            {
              pp->state = PP_Error;
              return MHD_NO;
            }
          pp->value_offset += xoff;

          /* are we done with the value? */
          if (end_of_value_found)
            {
              /* we found the end of the value! */
              if ( ('\n' == post_data[poff]) ||
                   ('\r' == post_data[poff]) )
                {
                  pp->state = PP_ExpectNewLine;
                }
              else if ('&' == post_data[poff])
                {
                  poff++;       /* skip '&' */
                  pp->state = PP_Init;
                }
            }
          break;
        case PP_ExpectNewLine:
          if ( ('\n' == post_data[poff]) ||
               ('\r' == post_data[poff]) )
            {
              poff++;
              /* we are done, report error if we receive any more... */
              pp->state = PP_Done;
              return MHD_YES;
            }
          return MHD_NO;
        default:
          mhd_panic (mhd_panic_cls,
                     __FILE__,
                     __LINE__,
                     NULL);          /* should never happen! */
        }
    }
  return MHD_YES;
}


/**
 * If the given line matches the prefix, strdup the
 * rest of the line into the suffix ptr.
 *
 * @param prefix prefix to match
 * @param line line to match prefix in
 * @param suffix set to a copy of the rest of the line, starting at the end of the match
 * @return #MHD_YES if there was a match, #MHD_NO if not
 */
static int
try_match_header (const char *prefix,
                  char *line,
                  char **suffix)
{
  if (NULL != *suffix)
    return MHD_NO;
  while (0 != *line)
    {
      if (MHD_str_equal_caseless_n_ (prefix,
                                     line,
                                     strlen (prefix)))
        {
          *suffix = strdup (&line[strlen (prefix)]);
          return MHD_YES;
        }
      ++line;
    }
  return MHD_NO;
}


/**
 *
 * @param pp post processor context
 * @param boundary boundary to look for
 * @param blen number of bytes in boundary
 * @param ioffptr set to the end of the boundary if found,
 *                otherwise incremented by one (FIXME: quirky API!)
 * @param next_state state to which we should advance the post processor
 *                   if the boundary is found
 * @param next_dash_state dash_state to which we should advance the
 *                   post processor if the boundary is found
 * @return #MHD_NO if the boundary is not found, #MHD_YES if we did find it
 */
static int
find_boundary (struct MHD_PostProcessor *pp,
               const char *boundary,
               size_t blen,
               size_t *ioffptr,
               enum PP_State next_state,
               enum PP_State next_dash_state)
{
  char *buf = (char *) &pp[1];
  const char *dash;

  if (pp->buffer_pos < 2 + blen)
    {
      if (pp->buffer_pos == pp->buffer_size)
        pp->state = PP_Error;   /* out of memory */
      /* ++(*ioffptr); */
      return MHD_NO;            /* not enough data */
    }
  if ( (0 != memcmp ("--",
                     buf,
                     2)) ||
       (0 != memcmp (&buf[2],
                     boundary,
                     blen)))
    {
      if (pp->state != PP_Init)
        {
          /* garbage not allowed */
          pp->state = PP_Error;
        }
      else
        {
          /* skip over garbage (RFC 2046, 5.1.1) */
          dash = memchr (buf,
                         '-',
                         pp->buffer_pos);
          if (NULL == dash)
            (*ioffptr) += pp->buffer_pos; /* skip entire buffer */
          else
            if (dash == buf)
              (*ioffptr)++; /* at least skip one byte */
            else
              (*ioffptr) += dash - buf; /* skip to first possible boundary */
        }
      return MHD_NO;            /* expected boundary */
    }
  /* remove boundary from buffer */
  (*ioffptr) += 2 + blen;
  /* next: start with headers */
  pp->skip_rn = RN_Dash;
  pp->state = next_state;
  pp->dash_state = next_dash_state;
  return MHD_YES;
}


/**
 * In buf, there maybe an expression '$key="$value"'.  If that is the
 * case, copy a copy of $value to destination.
 *
 * If destination is already non-NULL, do nothing.
 */
static void
try_get_value (const char *buf,
	       const char *key,
	       char **destination)
{
  const char *spos;
  const char *bpos;
  const char *endv;
  size_t klen;
  size_t vlen;

  if (NULL != *destination)
    return;
  bpos = buf;
  klen = strlen (key);
  while (NULL != (spos = strstr (bpos, key)))
    {
      if ( (spos[klen] != '=') ||
           ( (spos != buf) &&
             (spos[-1] != ' ') ) )
        {
          /* no match */
          bpos = spos + 1;
          continue;
        }
      if (spos[klen + 1] != '"')
        return;                 /* not quoted */
      if (NULL == (endv = strchr (&spos[klen + 2],
                                  '\"')))
        return;                 /* no end-quote */
      vlen = endv - spos - klen - 1;
      *destination = malloc (vlen);
      if (NULL == *destination)
        return;                 /* out of memory */
      (*destination)[vlen - 1] = '\0';
      memcpy (*destination,
              &spos[klen + 2],
              vlen - 1);
      return;                   /* success */
    }
}


/**
 * Go over the headers of the part and update
 * the fields in "pp" according to what we find.
 * If we are at the end of the headers (as indicated
 * by an empty line), transition into next_state.
 *
 * @param pp post processor context
 * @param ioffptr set to how many bytes have been
 *                processed
 * @param next_state state to which the post processor should
 *                be advanced if we find the end of the headers
 * @return #MHD_YES if we can continue processing,
 *         #MHD_NO on error or if we do not have
 *                enough data yet
 */
static int
process_multipart_headers (struct MHD_PostProcessor *pp,
                           size_t *ioffptr,
                           enum PP_State next_state)
{
  char *buf = (char *) &pp[1];
  size_t newline;

  newline = 0;
  while ( (newline < pp->buffer_pos) &&
          (buf[newline] != '\r') &&
          (buf[newline] != '\n') )
    newline++;
  if (newline == pp->buffer_size)
    {
      pp->state = PP_Error;
      return MHD_NO;            /* out of memory */
    }
  if (newline == pp->buffer_pos)
    return MHD_NO;              /* will need more data */
  if (0 == newline)
    {
      /* empty line - end of headers */
      pp->skip_rn = RN_Full;
      pp->state = next_state;
      return MHD_YES;
    }
  /* got an actual header */
  if (buf[newline] == '\r')
    pp->skip_rn = RN_OptN;
  buf[newline] = '\0';
  if (MHD_str_equal_caseless_n_ ("Content-disposition: ",
                                 buf,
                                 MHD_STATICSTR_LEN_ ("Content-disposition: ")))
    {
      try_get_value (&buf[MHD_STATICSTR_LEN_ ("Content-disposition: ")],
                     "name",
                     &pp->content_name);
      try_get_value (&buf[MHD_STATICSTR_LEN_ ("Content-disposition: ")],
                     "filename",
                     &pp->content_filename);
    }
  else
    {
      try_match_header ("Content-type: ",
                        buf,
                        &pp->content_type);
      try_match_header ("Content-Transfer-Encoding: ",
                        buf,
                        &pp->content_transfer_encoding);
    }
  (*ioffptr) += newline + 1;
  return MHD_YES;
}


/**
 * We have the value until we hit the given boundary;
 * process accordingly.
 *
 * @param pp post processor context
 * @param ioffptr incremented based on the number of bytes processed
 * @param boundary the boundary to look for
 * @param blen strlen(boundary)
 * @param next_state what state to go into after the
 *        boundary was found
 * @param next_dash_state state to go into if the next
 *        boundary ends with "--"
 * @return #MHD_YES if we can continue processing,
 *         #MHD_NO on error or if we do not have
 *                enough data yet
 */
static int
process_value_to_boundary (struct MHD_PostProcessor *pp,
                           size_t *ioffptr,
                           const char *boundary,
                           size_t blen,
                           enum PP_State next_state,
                           enum PP_State next_dash_state)
{
  char *buf = (char *) &pp[1];
  size_t newline;
  const char *r;

  /* all data in buf until the boundary
     (\r\n--+boundary) is part of the value */
  newline = 0;
  while (1)
    {
      while (newline + 4 < pp->buffer_pos)
        {
          r = memchr (&buf[newline],
                      '\r',
                      pp->buffer_pos - newline - 4);
          if (NULL == r)
          {
            newline = pp->buffer_pos - 4;
            break;
          }
          newline = r - buf;
          if (0 == memcmp ("\r\n--",
                           &buf[newline],
                           4))
            break;
          newline++;
        }
      if (newline + blen + 4 <= pp->buffer_pos)
        {
          /* can check boundary */
          if (0 != memcmp (&buf[newline + 4],
                           boundary,
                           blen))
            {
              /* no boundary, "\r\n--" is part of content, skip */
              newline += 4;
              continue;
            }
          else
            {
              /* boundary found, process until newline then
                 skip boundary and go back to init */
              pp->skip_rn = RN_Dash;
              pp->state = next_state;
              pp->dash_state = next_dash_state;
              (*ioffptr) += blen + 4;       /* skip boundary as well */
              buf[newline] = '\0';
              break;
            }
        }
      else
        {
          /* cannot check for boundary, process content that
             we have and check again later; except, if we have
             no content, abort (out of memory) */
          if ( (0 == newline) &&
               (pp->buffer_pos == pp->buffer_size) )
            {
              pp->state = PP_Error;
              return MHD_NO;
            }
          break;
        }
    }
  /* newline is either at beginning of boundary or
     at least at the last character that we are sure
     is not part of the boundary */
  if ( ( (MHD_YES == pp->must_ikvi) ||
	 (0 != newline) ) &&
       (MHD_NO == pp->ikvi (pp->cls,
			    MHD_POSTDATA_KIND,
			    pp->content_name,
			    pp->content_filename,
			    pp->content_type,
			    pp->content_transfer_encoding,
			    buf,
                            pp->value_offset,
                            newline)) )
    {
      pp->state = PP_Error;
      return MHD_NO;
    }
  pp->must_ikvi = MHD_NO;
  pp->value_offset += newline;
  (*ioffptr) += newline;
  return MHD_YES;
}


/**
 *
 * @param pp post processor context
 */
static void
free_unmarked (struct MHD_PostProcessor *pp)
{
  if ( (NULL != pp->content_name) &&
       (0 == (pp->have & NE_content_name)) )
    {
      free (pp->content_name);
      pp->content_name = NULL;
    }
  if ( (NULL != pp->content_type) &&
       (0 == (pp->have & NE_content_type)) )
    {
      free (pp->content_type);
      pp->content_type = NULL;
    }
  if ( (NULL != pp->content_filename) &&
       (0 == (pp->have & NE_content_filename)) )
    {
      free (pp->content_filename);
      pp->content_filename = NULL;
    }
  if ( (NULL != pp->content_transfer_encoding) &&
       (0 == (pp->have & NE_content_transfer_encoding)) )
    {
      free (pp->content_transfer_encoding);
      pp->content_transfer_encoding = NULL;
    }
}


/**
 * Decode multipart POST data.
 *
 * @param pp post processor context
 * @param post_data data to decode
 * @param post_data_len number of bytes in @a post_data
 * @return #MHD_NO on error,
 */
static int
post_process_multipart (struct MHD_PostProcessor *pp,
                        const char *post_data,
			size_t post_data_len)
{
  char *buf;
  size_t max;
  size_t ioff;
  size_t poff;
  int state_changed;

  buf = (char *) &pp[1];
  ioff = 0;
  poff = 0;
  state_changed = 1;
  while ( (poff < post_data_len) ||
          ( (pp->buffer_pos > 0) &&
            (0 != state_changed) ) )
    {
      /* first, move as much input data
         as possible to our internal buffer */
      max = pp->buffer_size - pp->buffer_pos;
      if (max > post_data_len - poff)
        max = post_data_len - poff;
      memcpy (&buf[pp->buffer_pos],
              &post_data[poff],
              max);
      poff += max;
      pp->buffer_pos += max;
      if ( (0 == max) &&
           (0 == state_changed) &&
           (poff < post_data_len) )
        {
          pp->state = PP_Error;
          return MHD_NO;        /* out of memory */
        }
      state_changed = 0;

      /* first state machine for '\r'-'\n' and '--' handling */
      switch (pp->skip_rn)
        {
        case RN_Inactive:
          break;
        case RN_OptN:
          if (buf[0] == '\n')
            {
              ioff++;
              pp->skip_rn = RN_Inactive;
              goto AGAIN;
            }
          /* fall-through! */
        case RN_Dash:
          if (buf[0] == '-')
            {
              ioff++;
              pp->skip_rn = RN_Dash2;
              goto AGAIN;
            }
          pp->skip_rn = RN_Full;
          /* fall-through! */
        case RN_Full:
          if (buf[0] == '\r')
            {
              if ( (pp->buffer_pos > 1) &&
                   ('\n' == buf[1]) )
                {
                  pp->skip_rn = RN_Inactive;
                  ioff += 2;
                }
              else
                {
                  pp->skip_rn = RN_OptN;
                  ioff++;
                }
              goto AGAIN;
            }
          if (buf[0] == '\n')
            {
              ioff++;
              pp->skip_rn = RN_Inactive;
              goto AGAIN;
            }
          pp->skip_rn = RN_Inactive;
          pp->state = PP_Error;
          return MHD_NO;        /* no '\r\n' */
        case RN_Dash2:
          if (buf[0] == '-')
            {
              ioff++;
              pp->skip_rn = RN_Full;
              pp->state = pp->dash_state;
              goto AGAIN;
            }
          pp->state = PP_Error;
          break;
        }

      /* main state engine */
      switch (pp->state)
        {
        case PP_Error:
          return MHD_NO;
        case PP_Done:
          /* did not expect to receive more data */
          pp->state = PP_Error;
          return MHD_NO;
        case PP_Init:
          /**
           * Per RFC2046 5.1.1 NOTE TO IMPLEMENTORS, consume anything
           * prior to the first multipart boundary:
           *
           * > There appears to be room for additional information prior
           * > to the first boundary delimiter line and following the
           * > final boundary delimiter line.  These areas should
           * > generally be left blank, and implementations must ignore
           * > anything that appears before the first boundary delimiter
           * > line or after the last one.
           */
          (void) find_boundary (pp,
				pp->boundary,
				pp->blen,
				&ioff,
				PP_ProcessEntryHeaders,
                                PP_Done);
          break;
        case PP_NextBoundary:
          if (MHD_NO == find_boundary (pp,
                                       pp->boundary,
                                       pp->blen,
                                       &ioff,
                                       PP_ProcessEntryHeaders,
                                       PP_Done))
            {
              if (pp->state == PP_Error)
                return MHD_NO;
              goto END;
            }
          break;
        case PP_ProcessEntryHeaders:
	  pp->must_ikvi = MHD_YES;
          if (MHD_NO ==
              process_multipart_headers (pp,
                                         &ioff,
                                         PP_PerformCheckMultipart))
            {
              if (pp->state == PP_Error)
                return MHD_NO;
              else
                goto END;
            }
          state_changed = 1;
          break;
        case PP_PerformCheckMultipart:
          if ( (NULL != pp->content_type) &&
               (MHD_str_equal_caseless_n_ (pp->content_type,
                                           "multipart/mixed",
                                           MHD_STATICSTR_LEN_ ("multipart/mixed"))))
            {
              pp->nested_boundary = strstr (pp->content_type,
                                            "boundary=");
              if (NULL == pp->nested_boundary)
                {
                  pp->state = PP_Error;
                  return MHD_NO;
                }
              pp->nested_boundary =
                strdup (&pp->nested_boundary[MHD_STATICSTR_LEN_ ("boundary=")]);
              if (NULL == pp->nested_boundary)
                {
                  /* out of memory */
                  pp->state = PP_Error;
                  return MHD_NO;
                }
              /* free old content type, we will need that field
                 for the content type of the nested elements */
              free (pp->content_type);
              pp->content_type = NULL;
              pp->nlen = strlen (pp->nested_boundary);
              pp->state = PP_Nested_Init;
              state_changed = 1;
              break;
            }
          pp->state = PP_ProcessValueToBoundary;
          pp->value_offset = 0;
          state_changed = 1;
          break;
        case PP_ProcessValueToBoundary:
          if (MHD_NO == process_value_to_boundary (pp,
                                                   &ioff,
                                                   pp->boundary,
                                                   pp->blen,
                                                   PP_PerformCleanup,
                                                   PP_Done))
            {
              if (pp->state == PP_Error)
                return MHD_NO;
              break;
            }
          break;
        case PP_PerformCleanup:
          /* clean up state of one multipart form-data element! */
          pp->have = NE_none;
          free_unmarked (pp);
          if (NULL != pp->nested_boundary)
            {
              free (pp->nested_boundary);
              pp->nested_boundary = NULL;
            }
          pp->state = PP_ProcessEntryHeaders;
          state_changed = 1;
          break;
        case PP_Nested_Init:
          if (NULL == pp->nested_boundary)
            {
              pp->state = PP_Error;
              return MHD_NO;
            }
          if (MHD_NO == find_boundary (pp,
                                       pp->nested_boundary,
                                       pp->nlen,
                                       &ioff,
                                       PP_Nested_PerformMarking,
                                       PP_NextBoundary /* or PP_Error? */ ))
            {
              if (pp->state == PP_Error)
                return MHD_NO;
              goto END;
            }
          break;
        case PP_Nested_PerformMarking:
          /* remember what headers were given
             globally */
          pp->have = NE_none;
          if (NULL != pp->content_name)
            pp->have |= NE_content_name;
          if (NULL != pp->content_type)
            pp->have |= NE_content_type;
          if (NULL != pp->content_filename)
            pp->have |= NE_content_filename;
          if (NULL != pp->content_transfer_encoding)
            pp->have |= NE_content_transfer_encoding;
          pp->state = PP_Nested_ProcessEntryHeaders;
          state_changed = 1;
          break;
        case PP_Nested_ProcessEntryHeaders:
          pp->value_offset = 0;
          if (MHD_NO ==
              process_multipart_headers (pp,
                                         &ioff,
                                         PP_Nested_ProcessValueToBoundary))
            {
              if (pp->state == PP_Error)
                return MHD_NO;
              else
                goto END;
            }
          state_changed = 1;
          break;
        case PP_Nested_ProcessValueToBoundary:
          if (MHD_NO == process_value_to_boundary (pp,
                                                   &ioff,
                                                   pp->nested_boundary,
                                                   pp->nlen,
                                                   PP_Nested_PerformCleanup,
                                                   PP_NextBoundary))
            {
              if (pp->state == PP_Error)
                return MHD_NO;
              break;
            }
          break;
        case PP_Nested_PerformCleanup:
          free_unmarked (pp);
          pp->state = PP_Nested_ProcessEntryHeaders;
          state_changed = 1;
          break;
        default:
          mhd_panic (mhd_panic_cls,
                     __FILE__,
                     __LINE__,
                     NULL);          /* should never happen! */
        }
    AGAIN:
      if (ioff > 0)
        {
          memmove (buf,
                   &buf[ioff],
                   pp->buffer_pos - ioff);
          pp->buffer_pos -= ioff;
          ioff = 0;
          state_changed = 1;
        }
    }
END:
  if (0 != ioff)
    {
      memmove (buf,
               &buf[ioff],
               pp->buffer_pos - ioff);
      pp->buffer_pos -= ioff;
    }
  if (poff < post_data_len)
    {
      pp->state = PP_Error;
      return MHD_NO;            /* serious error */
    }
  return MHD_YES;
}


/**
 * Parse and process POST data.  Call this function when POST data is
 * available (usually during an #MHD_AccessHandlerCallback) with the
 * "upload_data" and "upload_data_size".  Whenever possible, this will
 * then cause calls to the #MHD_PostDataIterator.
 *
 * @param pp the post processor
 * @param post_data @a post_data_len bytes of POST data
 * @param post_data_len length of @a post_data
 * @return #MHD_YES on success, #MHD_NO on error
 *         (out-of-memory, iterator aborted, parse error)
 * @ingroup request
 */
int
MHD_post_process (struct MHD_PostProcessor *pp,
                  const char *post_data,
                  size_t post_data_len)
{
  if (0 == post_data_len)
    return MHD_YES;
  if (NULL == pp)
    return MHD_NO;
  if (MHD_str_equal_caseless_n_ (MHD_HTTP_POST_ENCODING_FORM_URLENCODED,
                                 pp->encoding,
                                 MHD_STATICSTR_LEN_(MHD_HTTP_POST_ENCODING_FORM_URLENCODED)))
    return post_process_urlencoded (pp,
                                    post_data,
                                    post_data_len);
  if (MHD_str_equal_caseless_n_ (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA,
                                 pp->encoding,
                                 MHD_STATICSTR_LEN_ (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA)))
    return post_process_multipart (pp,
                                   post_data,
                                   post_data_len);
  /* this should never be reached */
  return MHD_NO;
}


/**
 * Release PostProcessor resources.
 *
 * @param pp post processor context to destroy
 * @return #MHD_YES if processing completed nicely,
 *         #MHD_NO if there were spurious characters / formatting
 *                problems; it is common to ignore the return
 *                value of this function
 * @ingroup request
 */
int
MHD_destroy_post_processor (struct MHD_PostProcessor *pp)
{
  int ret;

  if (NULL == pp)
    return MHD_YES;
  if (PP_ProcessValue == pp->state)
  {
    /* key without terminated value left at the end of the
       buffer; fake receiving a termination character to
       ensure it is also processed */
    post_process_urlencoded (pp,
                             "\n",
                             1);
  }
  /* These internal strings need cleaning up since
     the post-processing may have been interrupted
     at any stage */
  if ( (pp->xbuf_pos > 0) ||
       ( (pp->state != PP_Done) &&
         (pp->state != PP_ExpectNewLine) ) )
    ret = MHD_NO;
  else
    ret = MHD_YES;
  pp->have = NE_none;
  free_unmarked (pp);
  if (NULL != pp->nested_boundary)
    free (pp->nested_boundary);
  free (pp);
  return ret;
}

/* end of postprocessor.c */