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