blob: c00605c775c38d67408f393a724d26c9cf25588d [file] [log] [blame]
/*
This file is part of libmicrohttpd
Copyright (C) 2007-2021 Daniel Pittman, Christian Grothoff, and Evgeny Grin
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
* @author Karlson2k (Evgeny Grin)
*/
#include "postprocessor.h"
#include "internal.h"
#include "mhd_str.h"
#include "mhd_compat.h"
#include "mhd_assert.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
_MHD_EXTERN 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 (_ ("libmicrohttpd API violation.\n"));
encoding = 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;
mhd_assert (NULL != encoding);
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 < 2) ||
(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;
}
/**
* Give a (possibly partial) value to the application callback. We have some
* part of the value in the 'pp->xbuf', the rest is between @a value_start and
* @a value_end. If @a last_escape is non-NULL, there may be an incomplete
* escape sequence at at @a value_escape between @a value_start and @a
* value_end which we should preserve in 'pp->xbuf' for the future.
*
* Unescapes the value and calls the iterator together with the key. The key
* must already be in the key buffer allocated and 0-terminated at the end of
* @a pp at the time of the call.
*
* @param[in,out] pp post processor to act upon
* @param value_start where in memory is the value
* @param value_end where does the value end
* @param last_escape last '%'-sign in value range,
* if relevant, or NULL
*/
static void
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));
/* 'value_start' and 'value_end' must be either both non-NULL or both NULL */
mhd_assert ( (NULL == value_start) || (NULL != value_end) );
mhd_assert ( (NULL != value_start) || (NULL == value_end) );
mhd_assert ( (NULL == last_escape) || (NULL != value_start) );
/* move remaining input from previous round into processing buffer */
if (0 != pp->xbuf_pos)
memcpy (xbuf,
pp->xbuf,
pp->xbuf_pos);
xoff = pp->xbuf_pos;
pp->xbuf_pos = 0;
if ( (NULL != last_escape) &&
(((size_t) (value_end - last_escape)) < sizeof (pp->xbuf)) )
{
mhd_assert (value_end >= last_escape);
pp->xbuf_pos = (size_t) (value_end - last_escape);
memcpy (pp->xbuf,
last_escape,
(size_t) (value_end - last_escape));
value_end = last_escape;
}
while ( (value_start != value_end) ||
(pp->must_ikvi) ||
(xoff > 0) )
{
size_t delta = (size_t) (value_end - value_start);
bool cut = false;
size_t clen = 0;
mhd_assert (value_end >= value_start);
if (delta > XBUF_SIZE - xoff)
delta = XBUF_SIZE - xoff;
/* move (additional) input into processing buffer */
if (0 != delta)
{
memcpy (&xbuf[xoff],
value_start,
delta);
xoff += delta;
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 ( (xoff > 0) &&
('%' == xbuf[xoff - 1]) )
{
cut = (xoff != XBUF_SIZE);
xoff--;
if (cut)
{
/* move escape sequence into buffer for next function invocation */
pp->xbuf[0] = '%';
pp->xbuf_pos = 1;
}
else
{
/* just skip escape sequence for next loop iteration */
delta = xoff;
clen = 1;
}
}
else if ( (xoff > 1) &&
('%' == xbuf[xoff - 2]) )
{
cut = (xoff != XBUF_SIZE);
xoff -= 2;
if (cut)
{
/* move escape sequence into buffer for next function invocation */
memcpy (pp->xbuf,
&xbuf[xoff],
2);
pp->xbuf_pos = 2;
}
else
{
/* just skip escape sequence for next loop iteration */
delta = xoff;
clen = 2;
}
}
mhd_assert (xoff < sizeof (xbuf));
/* unescape */
xbuf[xoff] = '\0'; /* 0-terminate in preparation */
if (0 != xoff)
{
MHD_unescape_plus (xbuf);
xoff = MHD_http_unescape (xbuf);
}
/* finally: call application! */
if (pp->must_ikvi || (0 != xoff) )
{
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;
if (cut)
break;
if (0 != clen)
{
xbuf[delta] = '%'; /* undo 0-termination */
memmove (xbuf,
&xbuf[delta],
clen);
}
xoff = clen;
}
}
/**
* 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 enum MHD_Result
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;
mhd_assert (PP_Callback != pp->state);
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; /* Unreachable */
case PP_Init:
/* initial phase */
mhd_assert (NULL == start_key);
mhd_assert (NULL == end_key);
mhd_assert (NULL == start_value);
mhd_assert (NULL == end_value);
switch (post_data[poff])
{
case '=':
/* Case: (no key)'=' */
/* Empty key with value */
pp->state = PP_Error;
continue;
case '&':
/* Case: (no key)'&' */
/* Empty key without value */
poff++;
continue;
case '\n':
case '\r':
/* Case: (no key)'\n' or (no key)'\r' */
pp->state = PP_Done;
poff++;
break;
default:
/* normal character, key start, advance! */
pp->state = PP_ProcessKey;
start_key = &post_data[poff];
pp->must_ikvi = true;
poff++;
continue;
}
break; /* end PP_Init */
case PP_ProcessKey:
/* key phase */
mhd_assert (NULL == start_value);
mhd_assert (NULL == end_value);
mhd_assert (NULL != start_key || 0 == poff);
mhd_assert (0 != poff || NULL == start_key);
mhd_assert (NULL == end_key);
switch (post_data[poff])
{
case '=':
/* Case: 'key=' */
if (0 != poff)
end_key = &post_data[poff];
poff++;
pp->state = PP_ProcessValue;
break;
case '&':
/* Case: 'key&' */
if (0 != poff)
end_key = &post_data[poff];
poff++;
pp->state = PP_Callback;
break;
case '\n':
case '\r':
/* Case: 'key\n' or 'key\r' */
if (0 != poff)
end_key = &post_data[poff];
/* No advance here, 'PP_Done' will be selected by next 'PP_Init' phase */
pp->state = PP_Callback;
break;
default:
/* normal character, advance! */
if (0 == poff)
start_key = post_data;
poff++;
break;
}
mhd_assert (NULL == end_key || NULL != start_key);
break; /* end PP_ProcessKey */
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;
end_value = NULL;
}
continue;
case '\n':
case '\r':
/* Case: 'value\n' or 'value\r' */
end_value = &post_data[poff];
if (pp->must_ikvi ||
(start_value != end_value) )
pp->state = PP_Callback; /* No poff advance here to set PP_Done in the next iteration */
else
{
poff++;
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:
mhd_assert ((NULL != end_key) || (NULL == start_key));
if (1)
{
const size_t key_len = (size_t) (end_key - start_key);
mhd_assert (end_key >= start_key);
if (0 != key_len)
{
if ( (pp->buffer_pos + key_len >= pp->buffer_size) ||
(pp->buffer_pos + key_len < pp->buffer_pos) )
{
/* key too long, cannot parse! */
pp->state = PP_Error;
continue;
}
/* compute key, if we have not already */
memcpy (&kbuf[pp->buffer_pos],
start_key,
key_len);
pp->buffer_pos += key_len;
start_key = NULL;
end_key = NULL;
pp->must_unescape_key = true;
}
}
#ifdef _DEBUG
else
mhd_assert (0 != pp->buffer_pos);
#endif /* _DEBUG */
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);
if (PP_Error == pp->state)
continue;
pp->value_offset = 0;
start_value = NULL;
end_value = NULL;
pp->buffer_pos = 0;
pp->state = PP_Init;
break;
case PP_NextBoundary:
case PP_ProcessEntryHeaders:
case PP_PerformCheckMultipart:
case PP_ProcessValueToBoundary:
case PP_PerformCleanup:
case PP_Nested_Init:
case PP_Nested_PerformMarking:
case PP_Nested_ProcessEntryHeaders:
case PP_Nested_ProcessValueToBoundary:
case PP_Nested_PerformCleanup:
default:
MHD_PANIC (_ ("internal error.\n")); /* should never happen! */
}
mhd_assert ((end_key == NULL) || (start_key != NULL));
mhd_assert ((end_value == NULL) || (start_value != NULL));
}
mhd_assert (PP_Callback != pp->state);
if (PP_Error == pp->state)
{
/* State in error, returning failure */
return MHD_NO;
}
/* save remaining data for next iteration */
if (NULL != start_key)
{
size_t key_len;
mhd_assert ((PP_ProcessKey == pp->state) || (NULL != end_key));
if (NULL == end_key)
end_key = &post_data[poff];
mhd_assert (end_key >= start_key);
key_len = (size_t) (end_key - start_key);
mhd_assert (0 != key_len); /* it must be always non-zero here */
if (pp->buffer_pos + key_len >= pp->buffer_size)
{
pp->state = PP_Error;
return MHD_NO;
}
memcpy (&kbuf[pp->buffer_pos],
start_key,
key_len);
pp->buffer_pos += key_len;
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];
if ( (NULL != last_escape) &&
(2 < (end_value - last_escape)) )
last_escape = NULL;
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;
}
/**
* If the given line matches the prefix, strdup the
* rest of the line into the suffix ptr.
*
* @param prefix prefix to match
* @param prefix_len length of @a prefix
* @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,
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;
}
/**
*
* @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) += (size_t) (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 = (size_t) (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: ",
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;
}
/**
* 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 = (size_t) (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;
}
/**
*
* @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 enum MHD_Result
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;
case PP_ProcessKey:
case PP_ProcessValue:
case PP_Callback:
default:
MHD_PANIC (_ ("internal error.\n")); /* 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;
}
_MHD_EXTERN enum MHD_Result
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;
}
_MHD_EXTERN enum MHD_Result
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;
}
/* end of postprocessor.c */