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