diff options
Diffstat (limited to 'src/microhttpd/postprocessor.c')
-rw-r--r-- | src/microhttpd/postprocessor.c | 1191 |
1 files changed, 1191 insertions, 0 deletions
diff --git a/src/microhttpd/postprocessor.c b/src/microhttpd/postprocessor.c new file mode 100644 index 00000000..d371f3d0 --- /dev/null +++ b/src/microhttpd/postprocessor.c @@ -0,0 +1,1191 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2007-2013 Daniel Pittman and Christian Grothoff + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/** + * @file postprocessor.c + * @brief Methods for parsing POST data + * @author Christian Grothoff + */ + +#include "internal.h" + +/** + * Size of on-stack buffer that we use for un-escaping of the value. + * We use a pretty small value to be nice to the stack on embedded + * systems. + */ +#define XBUF_SIZE 512 + +/** + * States in the PP parser's state machine. + */ +enum PP_State +{ + /* general states */ + PP_Error, + PP_Done, + PP_Init, + PP_NextBoundary, + + /* url encoding-states */ + PP_ProcessValue, + PP_ExpectNewLine, + + /* post encoding-states */ + PP_ProcessEntryHeaders, + PP_PerformCheckMultipart, + PP_ProcessValueToBoundary, + PP_PerformCleanup, + + /* nested post-encoding states */ + PP_Nested_Init, + PP_Nested_PerformMarking, + PP_Nested_ProcessEntryHeaders, + PP_Nested_ProcessValueToBoundary, + PP_Nested_PerformCleanup + +}; + + +enum RN_State +{ + /** + * No RN-preprocessing in this state. + */ + RN_Inactive = 0, + + /** + * If the next character is CR, skip it. Otherwise, + * just go inactive. + */ + RN_OptN = 1, + + /** + * Expect LFCR (and only LFCR). As always, we also + * expect only LF or only CR. + */ + RN_Full = 2, + + /** + * Expect either LFCR or '--'LFCR. If '--'LFCR, transition into dash-state + * for the main state machine + */ + RN_Dash = 3, + + /** + * Got a single dash, expect second dash. + */ + RN_Dash2 = 4 +}; + + +/** + * Bits for the globally known fields that + * should not be deleted when we exit the + * nested state. + */ +enum NE_State +{ + NE_none = 0, + NE_content_name = 1, + NE_content_type = 2, + NE_content_filename = 4, + NE_content_transfer_encoding = 8 +}; + + +/** + * Internal state of the post-processor. Note that the fields + * are sorted by type to enable optimal packing by the compiler. + */ +struct MHD_PostProcessor +{ + + /** + * The connection for which we are doing + * POST processing. + */ + struct MHD_Connection *connection; + + /** + * Function to call with POST data. + */ + MHD_PostDataIterator ikvi; + + /** + * Extra argument to ikvi. + */ + void *cls; + + /** + * Encoding as given by the headers of the + * connection. + */ + const char *encoding; + + /** + * Primary boundary (points into encoding string) + */ + const char *boundary; + + /** + * Nested boundary (if we have multipart/mixed encoding). + */ + char *nested_boundary; + + /** + * Pointer to the name given in disposition. + */ + char *content_name; + + /** + * Pointer to the (current) content type. + */ + char *content_type; + + /** + * Pointer to the (current) filename. + */ + char *content_filename; + + /** + * Pointer to the (current) encoding. + */ + char *content_transfer_encoding; + + /** + * Unprocessed value bytes due to escape + * sequences (URL-encoding only). + */ + char xbuf[8]; + + /** + * Size of our buffer for the key. + */ + size_t buffer_size; + + /** + * Current position in the key buffer. + */ + size_t buffer_pos; + + /** + * Current position in xbuf. + */ + size_t xbuf_pos; + + /** + * Current offset in the value being processed. + */ + uint64_t value_offset; + + /** + * strlen(boundary) -- if boundary != NULL. + */ + size_t blen; + + /** + * strlen(nested_boundary) -- if nested_boundary != NULL. + */ + size_t nlen; + + /** + * Do we have to call the 'ikvi' callback when processing the + * multipart post body even if the size of the payload is zero? + * Set to #MHD_YES whenever we parse a new multiparty entry header, + * and to #MHD_NO the first time we call the 'ikvi' callback. + * Used to ensure that we do always call 'ikvi' even if the + * payload is empty (but not more than once). + */ + int must_ikvi; + + /** + * State of the parser. + */ + enum PP_State state; + + /** + * Side-state-machine: skip LRCR (or just LF). + * Set to 0 if we are not in skip mode. Set to 2 + * if a LFCR is expected, set to 1 if a CR should + * be skipped if it is the next character. + */ + enum RN_State skip_rn; + + /** + * If we are in skip_rn with "dash" mode and + * do find 2 dashes, what state do we go into? + */ + enum PP_State dash_state; + + /** + * Which headers are global? (used to tell which + * headers were only valid for the nested multipart). + */ + enum NE_State have; + +}; + + +/** + * Create a `struct MHD_PostProcessor`. + * + * A `struct MHD_PostProcessor` can be used to (incrementally) parse + * the data portion of a POST request. Note that some buggy browsers + * fail to set the encoding type. If you want to support those, you + * may have to call #MHD_set_connection_value with the proper encoding + * type before creating a post processor (if no supported encoding + * type is set, this function will fail). + * + * @param connection the connection on which the POST is + * happening (used to determine the POST format) + * @param buffer_size maximum number of bytes to use for + * internal buffering (used only for the parsing, + * specifically the parsing of the keys). A + * tiny value (256-1024) should be sufficient. + * Do NOT use a value smaller than 256. For good + * performance, use 32 or 64k (i.e. 65536). + * @param iter iterator to be called with the parsed data, + * Must NOT be NULL. + * @param iter_cls first argument to @a iter + * @return NULL on error (out of memory, unsupported encoding), + * otherwise a PP handle + * @ingroup request + */ +struct MHD_PostProcessor * +MHD_create_post_processor (struct MHD_Connection *connection, + size_t buffer_size, + MHD_PostDataIterator iter, void *iter_cls) +{ + struct MHD_PostProcessor *ret; + const char *encoding; + const char *boundary; + size_t blen; + + if ((buffer_size < 256) || (connection == NULL) || (iter == NULL)) + mhd_panic (mhd_panic_cls, __FILE__, __LINE__, NULL); + encoding = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_CONTENT_TYPE); + if (encoding == NULL) + return NULL; + boundary = NULL; + if (!MHD_str_equal_caseless_n_ (MHD_HTTP_POST_ENCODING_FORM_URLENCODED, encoding, + strlen (MHD_HTTP_POST_ENCODING_FORM_URLENCODED))) + { + if (!MHD_str_equal_caseless_n_ (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA, encoding, + strlen (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA))) + return NULL; + boundary = + &encoding[strlen (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 += strlen ("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 = malloc (sizeof (struct MHD_PostProcessor) + buffer_size + 1))) + return NULL; + memset (ret, 0, sizeof (struct MHD_PostProcessor) + buffer_size + 1); + ret->connection = connection; + ret->ikvi = iter; + ret->cls = iter_cls; + ret->encoding = encoding; + ret->buffer_size = buffer_size; + ret->state = PP_Init; + ret->blen = blen; + ret->boundary = boundary; + ret->skip_rn = RN_Inactive; + return ret; +} + + +/** + * Process url-encoded POST data. + * + * @param pp post processor context + * @param post_data upload data + * @param post_data_len number of bytes in @a post_data + * @return #MHD_YES on success, #MHD_NO if there was an error processing the data + */ +static int +post_process_urlencoded (struct MHD_PostProcessor *pp, + const char *post_data, + size_t post_data_len) +{ + size_t equals; + size_t amper; + size_t poff; + size_t xoff; + size_t delta; + int end_of_value_found; + char *buf; + char xbuf[XBUF_SIZE + 1]; + + buf = (char *) &pp[1]; + poff = 0; + while (poff < post_data_len) + { + switch (pp->state) + { + case PP_Error: + return MHD_NO; + case PP_Done: + /* did not expect to receive more data */ + pp->state = PP_Error; + return MHD_NO; + case PP_Init: + equals = 0; + while ((equals + poff < post_data_len) && + (post_data[equals + poff] != '=')) + equals++; + if (equals + pp->buffer_pos > pp->buffer_size) + { + pp->state = PP_Error; /* out of memory */ + return MHD_NO; + } + memcpy (&buf[pp->buffer_pos], &post_data[poff], equals); + pp->buffer_pos += equals; + if (equals + poff == post_data_len) + return MHD_YES; /* no '=' yet */ + buf[pp->buffer_pos] = '\0'; /* 0-terminate key */ + pp->buffer_pos = 0; /* reset for next key */ + MHD_unescape_plus (buf); + MHD_http_unescape (buf); + poff += equals + 1; + pp->state = PP_ProcessValue; + pp->value_offset = 0; + break; + case PP_ProcessValue: + /* obtain rest of value from previous iteration */ + memcpy (xbuf, pp->xbuf, pp->xbuf_pos); + xoff = pp->xbuf_pos; + pp->xbuf_pos = 0; + + /* find last position in input buffer that is part of the value */ + amper = 0; + while ((amper + poff < post_data_len) && + (amper < XBUF_SIZE) && + (post_data[amper + poff] != '&') && + (post_data[amper + poff] != '\n') && + (post_data[amper + poff] != '\r')) + amper++; + end_of_value_found = ((amper + poff < post_data_len) && + ((post_data[amper + poff] == '&') || + (post_data[amper + poff] == '\n') || + (post_data[amper + poff] == '\r'))); + /* compute delta, the maximum number of bytes that we will be able to + process right now (either amper-limited of xbuf-size limited) */ + delta = amper; + if (delta > XBUF_SIZE - xoff) + delta = XBUF_SIZE - xoff; + + /* move input into processing buffer */ + memcpy (&xbuf[xoff], &post_data[poff], delta); + xoff += delta; + poff += delta; + + /* find if escape sequence is at the end of the processing buffer; + if so, exclude those from processing (reduce delta to point at + end of processed region) */ + delta = xoff; + if ((delta > 0) && (xbuf[delta - 1] == '%')) + delta--; + else if ((delta > 1) && (xbuf[delta - 2] == '%')) + delta -= 2; + + /* if we have an incomplete escape sequence, save it to + pp->xbuf for later */ + if (delta < xoff) + { + memcpy (pp->xbuf, &xbuf[delta], xoff - delta); + pp->xbuf_pos = xoff - delta; + xoff = delta; + } + + /* If we have nothing to do (delta == 0) and + not just because the value is empty (are + waiting for more data), go for next iteration */ + if ((xoff == 0) && (poff == post_data_len)) + continue; + + /* unescape */ + xbuf[xoff] = '\0'; /* 0-terminate in preparation */ + MHD_unescape_plus (xbuf); + xoff = MHD_http_unescape (xbuf); + /* finally: call application! */ + pp->must_ikvi = MHD_NO; + if (MHD_NO == pp->ikvi (pp->cls, MHD_POSTDATA_KIND, (const char *) &pp[1], /* key */ + NULL, NULL, NULL, xbuf, pp->value_offset, + xoff)) + { + pp->state = PP_Error; + return MHD_NO; + } + pp->value_offset += xoff; + + /* are we done with the value? */ + if (end_of_value_found) + { + /* we found the end of the value! */ + if ((post_data[poff] == '\n') || (post_data[poff] == '\r')) + { + pp->state = PP_ExpectNewLine; + } + else if (post_data[poff] == '&') + { + poff++; /* skip '&' */ + pp->state = PP_Init; + } + } + break; + case PP_ExpectNewLine: + if ((post_data[poff] == '\n') || (post_data[poff] == '\r')) + { + poff++; + /* we are done, report error if we receive any more... */ + pp->state = PP_Done; + return MHD_YES; + } + return MHD_NO; + default: + mhd_panic (mhd_panic_cls, __FILE__, __LINE__, NULL); /* should never happen! */ + } + } + return MHD_YES; +} + + +/** + * If the given line matches the prefix, strdup the + * rest of the line into the suffix ptr. + * + * @param prefix prefix to match + * @param line line to match prefix in + * @param suffix set to a copy of the rest of the line, starting at the end of the match + * @return #MHD_YES if there was a match, #MHD_NO if not + */ +static int +try_match_header (const char *prefix, char *line, char **suffix) +{ + if (NULL != *suffix) + return MHD_NO; + while (*line != 0) + { + if (MHD_str_equal_caseless_n_ (prefix, line, strlen (prefix))) + { + *suffix = strdup (&line[strlen (prefix)]); + return MHD_YES; + } + ++line; + } + return MHD_NO; +} + + +/** + * + * @param pp post processor context + * @param boundary boundary to look for + * @param blen number of bytes in boundary + * @param ioffptr set to the end of the boundary if found, + * otherwise incremented by one (FIXME: quirky API!) + * @param next_state state to which we should advance the post processor + * if the boundary is found + * @param next_dash_state dash_state to which we should advance the + * post processor if the boundary is found + * @return #MHD_NO if the boundary is not found, #MHD_YES if we did find it + */ +static int +find_boundary (struct MHD_PostProcessor *pp, + const char *boundary, + size_t blen, + size_t *ioffptr, + enum PP_State next_state, enum PP_State next_dash_state) +{ + char *buf = (char *) &pp[1]; + const char *dash; + + if (pp->buffer_pos < 2 + blen) + { + if (pp->buffer_pos == pp->buffer_size) + pp->state = PP_Error; /* out of memory */ + // ++(*ioffptr); + return MHD_NO; /* not enough data */ + } + if ((0 != memcmp ("--", buf, 2)) || (0 != memcmp (&buf[2], boundary, blen))) + { + if (pp->state != PP_Init) + { + /* garbage not allowed */ + pp->state = PP_Error; + } + else + { + /* skip over garbage (RFC 2046, 5.1.1) */ + dash = memchr (buf, '-', pp->buffer_pos); + if (NULL == dash) + (*ioffptr) += pp->buffer_pos; /* skip entire buffer */ + else + if (dash == buf) + (*ioffptr)++; /* at least skip one byte */ + else + (*ioffptr) += dash - buf; /* skip to first possible boundary */ + } + return MHD_NO; /* expected boundary */ + } + /* remove boundary from buffer */ + (*ioffptr) += 2 + blen; + /* next: start with headers */ + pp->skip_rn = RN_Dash; + pp->state = next_state; + pp->dash_state = next_dash_state; + return MHD_YES; +} + + +/** + * In buf, there maybe an expression '$key="$value"'. If that is the + * case, copy a copy of $value to destination. + * + * If destination is already non-NULL, do nothing. + */ +static void +try_get_value (const char *buf, + const char *key, + char **destination) +{ + const char *spos; + const char *bpos; + const char *endv; + size_t klen; + size_t vlen; + + if (NULL != *destination) + return; + bpos = buf; + klen = strlen (key); + while (NULL != (spos = strstr (bpos, key))) + { + if ((spos[klen] != '=') || ((spos != buf) && (spos[-1] != ' '))) + { + /* no match */ + bpos = spos + 1; + continue; + } + if (spos[klen + 1] != '"') + return; /* not quoted */ + if (NULL == (endv = strchr (&spos[klen + 2], '\"'))) + return; /* no end-quote */ + vlen = endv - spos - klen - 1; + *destination = malloc (vlen); + if (NULL == *destination) + return; /* out of memory */ + (*destination)[vlen - 1] = '\0'; + memcpy (*destination, &spos[klen + 2], vlen - 1); + return; /* success */ + } +} + + +/** + * Go over the headers of the part and update + * the fields in "pp" according to what we find. + * If we are at the end of the headers (as indicated + * by an empty line), transition into next_state. + * + * @param pp post processor context + * @param ioffptr set to how many bytes have been + * processed + * @param next_state state to which the post processor should + * be advanced if we find the end of the headers + * @return #MHD_YES if we can continue processing, + * #MHD_NO on error or if we do not have + * enough data yet + */ +static int +process_multipart_headers (struct MHD_PostProcessor *pp, + size_t *ioffptr, enum PP_State next_state) +{ + char *buf = (char *) &pp[1]; + size_t newline; + + newline = 0; + while ((newline < pp->buffer_pos) && + (buf[newline] != '\r') && (buf[newline] != '\n')) + newline++; + if (newline == pp->buffer_size) + { + pp->state = PP_Error; + return MHD_NO; /* out of memory */ + } + if (newline == pp->buffer_pos) + return MHD_NO; /* will need more data */ + if (0 == newline) + { + /* empty line - end of headers */ + pp->skip_rn = RN_Full; + pp->state = next_state; + return MHD_YES; + } + /* got an actual header */ + if (buf[newline] == '\r') + pp->skip_rn = RN_OptN; + buf[newline] = '\0'; + if (MHD_str_equal_caseless_n_ ("Content-disposition: ", + buf, strlen ("Content-disposition: "))) + { + try_get_value (&buf[strlen ("Content-disposition: ")], + "name", &pp->content_name); + try_get_value (&buf[strlen ("Content-disposition: ")], + "filename", &pp->content_filename); + } + else + { + try_match_header ("Content-type: ", buf, &pp->content_type); + try_match_header ("Content-Transfer-Encoding: ", + buf, &pp->content_transfer_encoding); + } + (*ioffptr) += newline + 1; + return MHD_YES; +} + + +/** + * We have the value until we hit the given boundary; + * process accordingly. + * + * @param pp post processor context + * @param ioffptr incremented based on the number of bytes processed + * @param boundary the boundary to look for + * @param blen strlen(boundary) + * @param next_state what state to go into after the + * boundary was found + * @param next_dash_state state to go into if the next + * boundary ends with "--" + * @return #MHD_YES if we can continue processing, + * #MHD_NO on error or if we do not have + * enough data yet + */ +static int +process_value_to_boundary (struct MHD_PostProcessor *pp, + size_t *ioffptr, + const char *boundary, + size_t blen, + enum PP_State next_state, + enum PP_State next_dash_state) +{ + char *buf = (char *) &pp[1]; + size_t newline; + const char *r; + + /* all data in buf until the boundary + (\r\n--+boundary) is part of the value */ + newline = 0; + while (1) + { + while (newline + 4 < pp->buffer_pos) + { + r = memchr (&buf[newline], '\r', pp->buffer_pos - newline - 4); + if (NULL == r) + { + newline = pp->buffer_pos - 4; + break; + } + newline = r - buf; + if (0 == memcmp ("\r\n--", &buf[newline], 4)) + break; + newline++; + } + if (newline + pp->blen + 4 <= pp->buffer_pos) + { + /* can check boundary */ + if (0 != memcmp (&buf[newline + 4], boundary, pp->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) += pp->blen + 4; /* skip boundary as well */ + buf[newline] = '\0'; + break; + } + } + else + { + /* cannot check for boundary, process content that + we have and check again later; except, if we have + no content, abort (out of memory) */ + if ((0 == newline) && (pp->buffer_pos == pp->buffer_size)) + { + pp->state = PP_Error; + return MHD_NO; + } + break; + } + } + /* newline is either at beginning of boundary or + at least at the last character that we are sure + is not part of the boundary */ + if ( ( (MHD_YES == pp->must_ikvi) || + (0 != newline) ) && + (MHD_NO == pp->ikvi (pp->cls, + MHD_POSTDATA_KIND, + pp->content_name, + pp->content_filename, + pp->content_type, + pp->content_transfer_encoding, + buf, pp->value_offset, newline)) ) + { + pp->state = PP_Error; + return MHD_NO; + } + pp->must_ikvi = MHD_NO; + pp->value_offset += newline; + (*ioffptr) += newline; + return MHD_YES; +} + + +/** + * + * @param pp post processor context + */ +static void +free_unmarked (struct MHD_PostProcessor *pp) +{ + if ((NULL != pp->content_name) && (0 == (pp->have & NE_content_name))) + { + free (pp->content_name); + pp->content_name = NULL; + } + if ((NULL != pp->content_type) && (0 == (pp->have & NE_content_type))) + { + free (pp->content_type); + pp->content_type = NULL; + } + if ((NULL != pp->content_filename) && + (0 == (pp->have & NE_content_filename))) + { + free (pp->content_filename); + pp->content_filename = NULL; + } + if ((NULL != pp->content_transfer_encoding) && + (0 == (pp->have & NE_content_transfer_encoding))) + { + free (pp->content_transfer_encoding); + pp->content_transfer_encoding = NULL; + } +} + + +/** + * Decode multipart POST data. + * + * @param pp post processor context + * @param post_data data to decode + * @param post_data_len number of bytes in @a post_data + * @return #MHD_NO on error, + */ +static int +post_process_multipart (struct MHD_PostProcessor *pp, + const char *post_data, + size_t post_data_len) +{ + char *buf; + size_t max; + size_t ioff; + size_t poff; + int state_changed; + + buf = (char *) &pp[1]; + ioff = 0; + poff = 0; + state_changed = 1; + while ((poff < post_data_len) || + ((pp->buffer_pos > 0) && (state_changed != 0))) + { + /* 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 ((max == 0) && (state_changed == 0) && (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) && (buf[1] == '\n')) + { + pp->skip_rn = RN_Inactive; + ioff += 2; + } + else + { + pp->skip_rn = RN_OptN; + ioff++; + } + goto AGAIN; + } + if (buf[0] == '\n') + { + ioff++; + pp->skip_rn = RN_Inactive; + goto AGAIN; + } + pp->skip_rn = RN_Inactive; + pp->state = PP_Error; + return MHD_NO; /* no '\r\n' */ + case RN_Dash2: + if (buf[0] == '-') + { + ioff++; + pp->skip_rn = RN_Full; + pp->state = pp->dash_state; + goto AGAIN; + } + pp->state = PP_Error; + break; + } + + /* main state engine */ + switch (pp->state) + { + case PP_Error: + return MHD_NO; + case PP_Done: + /* did not expect to receive more data */ + pp->state = PP_Error; + return MHD_NO; + case PP_Init: + /** + * Per RFC2046 5.1.1 NOTE TO IMPLEMENTORS, consume anything + * prior to the first multipart boundary: + * + * > There appears to be room for additional information prior + * > to the first boundary delimiter line and following the + * > final boundary delimiter line. These areas should + * > generally be left blank, and implementations must ignore + * > anything that appears before the first boundary delimiter + * > line or after the last one. + */ + (void) find_boundary (pp, + pp->boundary, + pp->blen, + &ioff, + PP_ProcessEntryHeaders, PP_Done); + break; + case PP_NextBoundary: + if (MHD_NO == find_boundary (pp, + pp->boundary, + pp->blen, + &ioff, + PP_ProcessEntryHeaders, PP_Done)) + { + if (pp->state == PP_Error) + return MHD_NO; + goto END; + } + break; + case PP_ProcessEntryHeaders: + pp->must_ikvi = MHD_YES; + if (MHD_NO == + process_multipart_headers (pp, &ioff, PP_PerformCheckMultipart)) + { + if (pp->state == PP_Error) + return MHD_NO; + else + goto END; + } + state_changed = 1; + break; + case PP_PerformCheckMultipart: + if ((pp->content_type != NULL) && + (MHD_str_equal_caseless_n_ (pp->content_type, + "multipart/mixed", + strlen ("multipart/mixed")))) + { + pp->nested_boundary = strstr (pp->content_type, "boundary="); + if (pp->nested_boundary == NULL) + { + pp->state = PP_Error; + return MHD_NO; + } + pp->nested_boundary = + strdup (&pp->nested_boundary[strlen ("boundary=")]); + if (pp->nested_boundary == NULL) + { + /* 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 (pp->nested_boundary != NULL) + { + free (pp->nested_boundary); + pp->nested_boundary = NULL; + } + pp->state = PP_ProcessEntryHeaders; + state_changed = 1; + break; + case PP_Nested_Init: + if (pp->nested_boundary == NULL) + { + 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 (pp->content_name != NULL) + pp->have |= NE_content_name; + if (pp->content_type != NULL) + pp->have |= NE_content_type; + if (pp->content_filename != NULL) + pp->have |= NE_content_filename; + if (pp->content_transfer_encoding != NULL) + 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 (ioff != 0) + { + memmove (buf, &buf[ioff], pp->buffer_pos - ioff); + pp->buffer_pos -= ioff; + } + if (poff < post_data_len) + { + pp->state = PP_Error; + return MHD_NO; /* serious error */ + } + return MHD_YES; +} + + +/** + * Parse and process POST data. Call this function when POST data is + * available (usually during an #MHD_AccessHandlerCallback) with the + * "upload_data" and "upload_data_size". Whenever possible, this will + * then cause calls to the #MHD_PostDataIterator. + * + * @param pp the post processor + * @param post_data @a post_data_len bytes of POST data + * @param post_data_len length of @a post_data + * @return #MHD_YES on success, #MHD_NO on error + * (out-of-memory, iterator aborted, parse error) + * @ingroup request + */ +int +MHD_post_process (struct MHD_PostProcessor *pp, + const char *post_data, size_t post_data_len) +{ + if (0 == post_data_len) + return MHD_YES; + if (NULL == pp) + return MHD_NO; + if (MHD_str_equal_caseless_n_ (MHD_HTTP_POST_ENCODING_FORM_URLENCODED, pp->encoding, + strlen(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, + strlen (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA))) + return post_process_multipart (pp, post_data, post_data_len); + /* this should never be reached */ + return MHD_NO; +} + + +/** + * Release PostProcessor resources. + * + * @param pp post processor context to destroy + * @return #MHD_YES if processing completed nicely, + * #MHD_NO if there were spurious characters / formatting + * problems; it is common to ignore the return + * value of this function + * @ingroup request + */ +int +MHD_destroy_post_processor (struct MHD_PostProcessor *pp) +{ + int ret; + + if (NULL == pp) + return MHD_YES; + if (PP_ProcessValue == pp->state) + { + /* key without terminated value left at the end of the + buffer; fake receiving a termination character to + ensure it is also processed */ + post_process_urlencoded (pp, "\n", 1); + } + /* These internal strings need cleaning up since + the post-processing may have been interrupted + at any stage */ + if ((pp->xbuf_pos > 0) || + ( (pp->state != PP_Done) && + (pp->state != PP_ExpectNewLine))) + ret = MHD_NO; + else + ret = MHD_YES; + pp->have = NE_none; + free_unmarked (pp); + if (pp->nested_boundary != NULL) + free (pp->nested_boundary); + free (pp); + return ret; +} + +/* end of postprocessor.c */ |