/** * 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; }
/** * Decode multipart POST data. * * @param pp post processor context */ 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) && (0 == strncasecmp (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; }
/** * Process url-encoded POST data. * * @param pp post processor context * @param post_data upload data * @param post_data_len number of bytes in upload_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_http_unescape (NULL, NULL, 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 */ xoff = MHD_http_unescape (NULL, NULL, 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; }