Commit message: fix buffer overflow and add test
CVE: CVE-2021-3466
CWE: CWE-120
Vulnerability description: A flaw was found in libmicrohttpd. A missing bounds check in the post_process_urlencoded function leads to a buffer overflow, allowing a remote attacker to write arbitrary data in an application that uses libmicrohttpd. The highest threat from this vulnerability is to data confidentiality and integrity as well as system availability. Only version 0.9.70 is vulnerable.

Project: libmicrohttpd

post_process_urlencoded (struct MHD_PostProcessor *pp,
                         const char *post_data,
                         size_t post_data_len)
{
  char *kbuf = (char *) &pp[1];
  size_t poff;
  const char *start_key = NULL;
  const char *end_key = NULL;
  const char *start_value = NULL;
  const char *end_value = NULL;
  const char *last_escape = NULL;

  poff = 0;
  while ( ( (poff < post_data_len) ||
            (pp->state == PP_Callback) ) &&
          (pp->state != PP_Error) )
  {
    switch (pp->state)
    {
    case PP_Error:
      /* clearly impossible as per while loop invariant */
      abort ();
      break;
    case PP_Init:
      /* key phase */
      if (NULL == start_key)
        start_key = &post_data[poff];
      pp->must_ikvi = true;
      switch (post_data[poff])
      {
      case '=':
        /* Case: 'key=' */
        end_key = &post_data[poff];
        poff++;
        pp->state = PP_ProcessValue;
        break;
      case '&':
        /* Case: 'key&' */
        end_key = &post_data[poff];
        mhd_assert (NULL == start_value);
        mhd_assert (NULL == end_value);
        poff++;
        pp->state = PP_Callback;
        break;
      case '\n':
      case '\r':
        /* Case: 'key\n' or 'key\r' */
        end_key = &post_data[poff];
        poff++;
        pp->state = PP_Done;
        break;
      default:
        /* normal character, advance! */
        poff++;
        continue;
      }
      break; /* end PP_Init */
    case PP_ProcessValue:
      if (NULL == start_value)
        start_value = &post_data[poff];
      switch (post_data[poff])
      {
      case '=':
        /* case 'key==' */
        pp->state = PP_Error;
        continue;
      case '&':
        /* case 'value&' */
        end_value = &post_data[poff];
        poff++;
        if (pp->must_ikvi ||
            (start_value != end_value) )
        {
          pp->state = PP_Callback;
        }
        else
        {
          pp->buffer_pos = 0;
          pp->value_offset = 0;
          pp->state = PP_Init;
          start_value = NULL;
        }
        continue;
      case '\n':
      case '\r':
        /* Case: 'value\n' or 'value\r' */
        end_value = &post_data[poff];
        poff++;
        if (pp->must_ikvi)
          pp->state = PP_Callback;
        else
          pp->state = PP_Done;
        break;
      case '%':
        last_escape = &post_data[poff];
        poff++;
        break;
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
        /* character, may be part of escaping */
        poff++;
        continue;
      default:
        /* normal character, no more escaping! */
        last_escape = NULL;
        poff++;
        continue;
      }
      break; /* end PP_ProcessValue */
    case PP_Done:
      switch (post_data[poff])
      {
      case '\n':
      case '\r':
        poff++;
        continue;
      }
      /* unexpected data at the end, fail! */
      pp->state = PP_Error;
      break;
    case PP_Callback:
      if ( (pp->buffer_pos + (end_key - start_key) >=
            pp->buffer_size) ||
           (pp->buffer_pos + (end_key - start_key) <
            pp->buffer_pos) )
      {
        /* key too long, cannot parse! */
        pp->state = PP_Error;
        continue;
      }
      /* compute key, if we have not already */
      if (NULL != start_key)
      {
        memcpy (&kbuf[pp->buffer_pos],
                start_key,
                end_key - start_key);
        pp->buffer_pos += end_key - start_key;
        start_key = NULL;
        end_key = NULL;
        pp->must_unescape_key = true;
      }
      if (pp->must_unescape_key)
      {
        kbuf[pp->buffer_pos] = '\0'; /* 0-terminate key */
        MHD_unescape_plus (kbuf);
        MHD_http_unescape (kbuf);
        pp->must_unescape_key = false;
      }
      process_value (pp,
                     start_value,
                     end_value,
                     NULL);
      pp->value_offset = 0;
      start_value = NULL;
      end_value = NULL;
      pp->buffer_pos = 0;
      pp->state = PP_Init;
      break;
    default:
      mhd_panic (mhd_panic_cls,
                 __FILE__,
                 __LINE__,
                 NULL);              /* should never happen! */
    }
  }

  /* save remaining data for next iteration */
  if (NULL != start_key)
  {
    if (NULL == end_key)
      end_key = &post_data[poff];
    if (pp->buffer_pos + (end_key - start_key) >= pp->buffer_size)
    {
      pp->state = PP_Error;
      return MHD_NO;
    }
    memcpy (&kbuf[pp->buffer_pos],
            start_key,
            end_key - start_key);
    pp->buffer_pos += end_key - start_key;
    pp->must_unescape_key = true;
    start_key = NULL;
    end_key = NULL;
  }
  if ( (NULL != start_value) &&
       (PP_ProcessValue == pp->state) )
  {
    /* compute key, if we have not already */
    if (pp->must_unescape_key)
    {
      kbuf[pp->buffer_pos] = '\0'; /* 0-terminate key */
      MHD_unescape_plus (kbuf);
      MHD_http_unescape (kbuf);
      pp->must_unescape_key = false;
    }
    if (NULL == end_value)
      end_value = &post_data[poff];
    process_value (pp,
                   start_value,
                   end_value,
                   last_escape);
    pp->must_ikvi = false;
  }
  if (PP_Error == pp->state)
  {
    /* State in error, returning failure */
    return MHD_NO;
  }
  return MHD_YES;
}

Project: libmicrohttpd

value_checker2 (void *cls,
                enum MHD_ValueKind kind,
                const char *key,
                const char *filename,
                const char *content_type,
                const char *transfer_encoding,
                const char *data,
                uint64_t off,
                size_t size)
{
  return MHD_YES;
}

Project: libmicrohttpd

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 ( ( (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 = false;
  pp->value_offset += newline;
  (*ioffptr) += newline;
  return MHD_YES;
}

Project: libmicrohttpd

value_checker (void *cls,
               enum MHD_ValueKind kind,
               const char *key,
               const char *filename,
               const char *content_type,
               const char *transfer_encoding,
               const char *data,
               uint64_t off,
               size_t size)
{
  int *want_off = cls;
  int idx = *want_off;
  (void) kind;  /* Unused. Silent compiler warning. */

#if 0
  fprintf (stderr,
           "VC: `%s' `%s' `%s' `%s' `%.*s' (%d)\n",
           key, filename, content_type, transfer_encoding,
           (int) size,
           data,
           (int) size);
#endif
  if ( (0 != off) && (0 == size) )
  {
    if (NULL == want[idx + 4])
      *want_off = idx + 5;
    return MHD_YES;
  }
  if ((idx < 0) ||
      (want[idx] == NULL) ||
      (0 != strcmp (key, want[idx])) ||
      (mismatch (filename, want[idx + 1])) ||
      (mismatch (content_type, want[idx + 2])) ||
      (mismatch (transfer_encoding, want[idx + 3])) ||
      (0 != memcmp (data,
                    &want[idx + 4][off],
                    size)))
  {
    *want_off = -1;
    fprintf (stderr,
             "Failed with: `%s' `%s' `%s' `%s' `%.*s'\n",
             key, filename, content_type, transfer_encoding,
             (int) size,
             data);
    fprintf (stderr,
             "Wanted: `%s' `%s' `%s' `%s' `%s'\n",
             want[idx],
             want[idx + 1],
             want[idx + 2],
             want[idx + 3],
             want[idx + 4]);
    fprintf (stderr,
             "Unexpected result: %d/%d/%d/%d/%d/%d/%d\n",
             (idx < 0),
             (want[idx] == NULL),
             (NULL != want[idx]) && (0 != strcmp (key, want[idx])),
             (mismatch (filename, want[idx + 1])),
             (mismatch (content_type, want[idx + 2])),
             (mismatch (transfer_encoding, want[idx + 3])),
             (0 != memcmp (data, &want[idx + 4][off], size)));
    return MHD_NO;
  }
  if ( ( (NULL == want[idx + 4]) &&
         (0 == off + size) ) ||
       (off + size == strlen (want[idx + 4])) )
    *want_off = idx + 5;
  return MHD_YES;
}

Project: libmicrohttpd

test_multipart_garbage (void)
{
  struct MHD_Connection connection;
  struct MHD_HTTP_Header header;
  struct MHD_PostProcessor *pp;
  unsigned int want_off;
  size_t size = strlen (FORM_DATA);
  size_t splitpoint;
  char xdata[size + 3];

  /* fill in evil garbage at the beginning */
  xdata[0] = '-';
  xdata[1] = 'x';
  xdata[2] = '\r';
  memcpy (&xdata[3], FORM_DATA, size);
  size += 3;
  for (splitpoint = 1; splitpoint < size; splitpoint++)
  {
    want_off = FORM_START;
    memset (&connection, 0, sizeof (struct MHD_Connection));
    memset (&header, 0, sizeof (struct MHD_HTTP_Header));
    connection.headers_received = &header;
    header.header = MHD_HTTP_HEADER_CONTENT_TYPE;
    header.value =
      MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA ", boundary=AaB03x";
    header.header_size = MHD_STATICSTR_LEN_ (MHD_HTTP_HEADER_CONTENT_TYPE);
    header.value_size = MHD_STATICSTR_LEN_ (
      MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA ", boundary=AaB03x");
    header.kind = MHD_HEADER_KIND;
    pp = MHD_create_post_processor (&connection,
                                    1024, &value_checker, &want_off);
    MHD_post_process (pp, xdata, splitpoint);
    MHD_post_process (pp, &xdata[splitpoint], size - splitpoint);
    MHD_destroy_post_processor (pp);
    if (want_off != FORM_END)
    {
      fprintf (stderr,
               "Test failed in line %u at point %d\n",
               (unsigned int) __LINE__,
               (int) splitpoint);
      return (int) splitpoint;
    }
  }
  return 0;
}

Project: libmicrohttpd

test_empty_value (void)
{
  struct MHD_Connection connection;
  struct MHD_HTTP_Header header;
  struct MHD_PostProcessor *pp;
  unsigned int want_off = URL_EMPTY_VALUE_START;
  size_t i;
  size_t delta;
  size_t size;

  memset (&connection, 0, sizeof (struct MHD_Connection));
  memset (&header, 0, sizeof (struct MHD_HTTP_Header));
  connection.headers_received = &header;
  header.header = MHD_HTTP_HEADER_CONTENT_TYPE;
  header.value = MHD_HTTP_POST_ENCODING_FORM_URLENCODED;
  header.header_size = strlen (header.header);
  header.value_size = strlen (header.value);
  header.kind = MHD_HEADER_KIND;
  pp = MHD_create_post_processor (&connection,
                                  1024, &value_checker, &want_off);
  i = 0;
  size = strlen (URL_EMPTY_VALUE_DATA);
  while (i < size)
  {
    delta = 1 + MHD_random_ () % (size - i);
    MHD_post_process (pp, &URL_EMPTY_VALUE_DATA[i], delta);
    i += delta;
  }
  MHD_destroy_post_processor (pp);
  if (want_off != URL_EMPTY_VALUE_END)
  {
    fprintf (stderr,
             "Test failed in line %u at offset %d\n",
             (unsigned int) __LINE__,
             (int) want_off);
    return 8;
  }
  return 0;
}

Project: libmicrohttpd

test_urlencoding_case (unsigned int want_start,
                       unsigned int want_end,
                       const char *url_data)
{
  struct MHD_Connection connection;
  struct MHD_HTTP_Header header;
  struct MHD_PostProcessor *pp;
  unsigned int want_off = want_start;
  size_t i;
  size_t delta;
  size_t size;

  memset (&connection, 0, sizeof (struct MHD_Connection));
  memset (&header, 0, sizeof (struct MHD_HTTP_Header));
  connection.headers_received = &header;
  header.header = MHD_HTTP_HEADER_CONTENT_TYPE;
  header.value = MHD_HTTP_POST_ENCODING_FORM_URLENCODED;
  header.header_size = MHD_STATICSTR_LEN_ (MHD_HTTP_HEADER_CONTENT_TYPE);
  header.value_size = MHD_STATICSTR_LEN_ (
    MHD_HTTP_POST_ENCODING_FORM_URLENCODED);
  header.kind = MHD_HEADER_KIND;
  pp = MHD_create_post_processor (&connection,
                                  1024,
                                  &value_checker,
                                  &want_off);
  i = 0;
  size = strlen (url_data);
  while (i < size)
  {
    delta = 1 + MHD_random_ () % (size - i);
    MHD_post_process (pp,
                      &url_data[i],
                      delta);
    i += delta;
  }
  MHD_destroy_post_processor (pp);
  if (want_off != want_end)
  {
    fprintf (stderr,
             "Test failed in line %u: %u != %u\n",
             (unsigned int) __LINE__,
             want_off,
             want_end);
    return 1;
  }
  return 0;
}

Project: libmicrohttpd

mismatch (const char *a, const char *b)
{
  if (a == b)
    return 0;
  if ((a == NULL) || (b == NULL))
    return 1;
  return 0 != strcmp (a, b);
}

Project: libmicrohttpd

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;
  }
}

Project: libmicrohttpd

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 */
  }
}

Project: libmicrohttpd

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;
}

Project: libmicrohttpd

test_multipart_splits (void)
{
  struct MHD_Connection connection;
  struct MHD_HTTP_Header header;
  struct MHD_PostProcessor *pp;
  unsigned int want_off;
  size_t size;
  size_t splitpoint;

  size = strlen (FORM_DATA);
  for (splitpoint = 1; splitpoint < size; splitpoint++)
  {
    want_off = FORM_START;
    memset (&connection, 0, sizeof (struct MHD_Connection));
    memset (&header, 0, sizeof (struct MHD_HTTP_Header));
    connection.headers_received = &header;
    header.header = MHD_HTTP_HEADER_CONTENT_TYPE;
    header.value =
      MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA ", boundary=AaB03x";
    header.header_size = strlen (header.header);
    header.value_size = strlen (header.value);
    header.kind = MHD_HEADER_KIND;
    pp = MHD_create_post_processor (&connection,
                                    1024, &value_checker, &want_off);
    MHD_post_process (pp, FORM_DATA, splitpoint);
    MHD_post_process (pp, &FORM_DATA[splitpoint], size - splitpoint);
    MHD_destroy_post_processor (pp);
    if (want_off != FORM_END)
    {
      fprintf (stderr,
               "Test failed in line %u at point %d\n",
               (unsigned int) __LINE__,
               (int) splitpoint);
      return (int) splitpoint;
    }
  }
  return 0;
}

Project: libmicrohttpd

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;
}

Project: libmicrohttpd

test_multipart (void)
{
  struct MHD_Connection connection;
  struct MHD_HTTP_Header header;
  struct MHD_PostProcessor *pp;
  unsigned int want_off = FORM_START;
  size_t i;
  size_t delta;
  size_t size;

  memset (&connection, 0, sizeof (struct MHD_Connection));
  memset (&header, 0, sizeof (struct MHD_HTTP_Header));
  connection.headers_received = &header;
  header.header = MHD_HTTP_HEADER_CONTENT_TYPE;
  header.value =
    MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA ", boundary=AaB03x";
  header.kind = MHD_HEADER_KIND;
  header.header_size = strlen (header.header);
  header.value_size = strlen (header.value);
  pp = MHD_create_post_processor (&connection,
                                  1024, &value_checker, &want_off);
  i = 0;
  size = strlen (FORM_DATA);
  while (i < size)
  {
    delta = 1 + MHD_random_ () % (size - i);
    MHD_post_process (pp, &FORM_DATA[i], delta);
    i += delta;
  }
  MHD_destroy_post_processor (pp);
  if (want_off != FORM_END)
  {
    fprintf (stderr,
             "Test failed in line %u\n",
             (unsigned int) __LINE__);
    return 2;
  }
  return 0;
}

Project: libmicrohttpd

test_nested_multipart (void)
{
  struct MHD_Connection connection;
  struct MHD_HTTP_Header header;
  struct MHD_PostProcessor *pp;
  unsigned int want_off = FORM_NESTED_START;
  size_t i;
  size_t delta;
  size_t size;

  memset (&connection, 0, sizeof (struct MHD_Connection));
  memset (&header, 0, sizeof (struct MHD_HTTP_Header));
  connection.headers_received = &header;
  header.header = MHD_HTTP_HEADER_CONTENT_TYPE;
  header.value =
    MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA ", boundary=AaB03x";
  header.kind = MHD_HEADER_KIND;
  header.header_size = strlen (header.header);
  header.value_size = strlen (header.value);
  pp = MHD_create_post_processor (&connection,
                                  1024, &value_checker, &want_off);
  i = 0;
  size = strlen (FORM_NESTED_DATA);
  while (i < size)
  {
    delta = 1 + MHD_random_ () % (size - i);
    MHD_post_process (pp, &FORM_NESTED_DATA[i], delta);
    i += delta;
  }
  MHD_destroy_post_processor (pp);
  if (want_off != FORM_NESTED_END)
  {
    fprintf (stderr,
             "Test failed in line %u\n",
             (unsigned int) __LINE__);
    return 4;
  }
  return 0;
}

Project: libmicrohttpd

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);
  if (MHD_NO == MHD_lookup_connection_value_n (connection,
                                               MHD_HEADER_KIND,
                                               MHD_HTTP_HEADER_CONTENT_TYPE,
                                               MHD_STATICSTR_LEN_ (
                                                 MHD_HTTP_HEADER_CONTENT_TYPE),
                                               &encoding,
                                               NULL))
    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;
}

Project: libmicrohttpd

try_match_header (const char *prefix,
                  size_t prefix_len,
                  char *line,
                  char **suffix)
{
  if (NULL != *suffix)
    return MHD_NO;
  while (0 != *line)
  {
    if (MHD_str_equal_caseless_n_ (prefix,
                                   line,
                                   prefix_len))
    {
      *suffix = strdup (&line[prefix_len]);
      return MHD_YES;
    }
    ++line;
  }
  return MHD_NO;
}

Project: libmicrohttpd

test_urlencoding (void)
{
  unsigned int errorCount = 0;

  errorCount += test_urlencoding_case (URL_START,
                                       URL_END,
                                       URL_DATA);
  errorCount += test_urlencoding_case (URL_NOVALUE1_START,
                                       URL_NOVALUE1_END,
                                       URL_NOVALUE1_DATA);
  errorCount += test_urlencoding_case (URL_NOVALUE2_START,
                                       URL_NOVALUE2_END,
                                       URL_NOVALUE2_DATA);
  if (0 != errorCount)
    fprintf (stderr,
             "Test failed in line %u with %u errors\n",
             (unsigned int) __LINE__,
             errorCount);
  return errorCount;
}

Project: libmicrohttpd

MHD_destroy_post_processor (struct MHD_PostProcessor *pp)
{
  enum MHD_Result 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_Init) ) )
    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;
}

Project: libmicrohttpd

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 = true;
      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;
}

Project: libmicrohttpd

process_value (struct MHD_PostProcessor *pp,
               const char *value_start,
               const char *value_end,
               const char *last_escape)
{
  char xbuf[XBUF_SIZE + 1];
  size_t xoff;

  mhd_assert (pp->xbuf_pos < sizeof (xbuf));
  memcpy (xbuf,
          pp->xbuf,
          pp->xbuf_pos);
  xoff = pp->xbuf_pos;
  pp->xbuf_pos = 0;
  if (NULL != last_escape)
  {
    if (((size_t) (value_end - last_escape)) < sizeof (pp->xbuf))
    {
      pp->xbuf_pos = value_end - last_escape;
      memcpy (pp->xbuf,
              last_escape,
              value_end - last_escape);
      value_end = last_escape;
    }
  }
  while ( (value_start != value_end) ||
          (pp->must_ikvi) ||
          (xoff > 0) )
  {
    size_t delta = value_end - value_start;

    if (delta > XBUF_SIZE - xoff)
      delta = XBUF_SIZE - xoff;
    /* move input into processing buffer */
    memcpy (&xbuf[xoff],
            value_start,
            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) */
    if (delta >= XBUF_SIZE - 2)
    {
      if ((xoff + delta > 0) &&
          ('%' == xbuf[xoff + delta - 1]))
        delta--;
      else if ((xoff + delta > 1) &&
               ('%' == xbuf[xoff + delta - 2]))
        delta -= 2;
    }
    xoff += delta;
    value_start += delta;
    mhd_assert (xoff < sizeof (xbuf));
    /* unescape */
    xbuf[xoff] = '\0';        /* 0-terminate in preparation */
    MHD_unescape_plus (xbuf);
    xoff = MHD_http_unescape (xbuf);
    /* finally: call application! */
    pp->must_ikvi = false;
    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;
    }
    pp->value_offset += xoff;
    xoff = 0;
  }
}

Project: libmicrohttpd

test_overflow ()
{
  struct MHD_Connection connection;
  struct MHD_HTTP_Header header;
  struct MHD_PostProcessor *pp;
  size_t i;
  size_t j;
  size_t delta;
  char *buf;

  memset (&connection, 0, sizeof (struct MHD_Connection));
  memset (&header, 0, sizeof (struct MHD_HTTP_Header));
  connection.headers_received = &header;
  header.header = MHD_HTTP_HEADER_CONTENT_TYPE;
  header.value = MHD_HTTP_POST_ENCODING_FORM_URLENCODED;
  header.header_size = strlen (header.header);
  header.value_size = strlen (header.value);
  header.kind = MHD_HEADER_KIND;
  for (i = 128; i < 1024 * 1024; i += 1024)
  {
    pp = MHD_create_post_processor (&connection,
                                    1024,
                                    &value_checker2,
                                    NULL);
    buf = malloc (i);
    if (NULL == buf)
      return 1;
    memset (buf, 'A', i);
    buf[i / 2] = '=';
    delta = 1 + (MHD_random_ () % (i - 1));
    j = 0;
    while (j < i)
    {
      if (j + delta > i)
        delta = i - j;
      if (MHD_NO ==
          MHD_post_process (pp,
                            &buf[j],
                            delta))
        break;
      j += delta;
    }
    free (buf);
    MHD_destroy_post_processor (pp);
  }
  return 0;
}

Project: libmicrohttpd

main (int argc, char *const *argv)
{
  unsigned int errorCount = 0;
  (void) argc; (void) argv;  /* Unused. Silent compiler warning. */

  errorCount += test_multipart_splits ();
  errorCount += test_multipart_garbage ();
  errorCount += test_urlencoding ();
  errorCount += test_multipart ();
  errorCount += test_nested_multipart ();
  errorCount += test_empty_value ();
  errorCount += test_overflow ();
  if (errorCount != 0)
    fprintf (stderr, "Error (code: %u)\n", errorCount);
  return errorCount != 0;       /* 0 == pass */
}

Project: libmicrohttpd

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: ",
                      MHD_STATICSTR_LEN_ ("Content-type: "),
                      buf,
                      &pp->content_type);
    try_match_header ("Content-Transfer-Encoding: ",
                      MHD_STATICSTR_LEN_ ("Content-Transfer-Encoding: "),
                      buf,
                      &pp->content_transfer_encoding);
  }
  (*ioffptr) += newline + 1;
  return MHD_YES;
}