mirror of
https://git.gnunet.org/libmicrohttpd.git
synced 2026-09-27 04:09:31 +03:00
1328 lines
37 KiB
C
1328 lines
37 KiB
C
/*
|
|
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 == 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;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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 */
|