diff --git a/src/include/microhttpd2.h b/src/include/microhttpd2.h index 0d8b349c..9ab26bb6 100644 --- a/src/include/microhttpd2.h +++ b/src/include/microhttpd2.h @@ -147,6 +147,8 @@ */ #define MHD_VERSION 0x01990001 +#ifndef MHD_BOOL_DEFINED + /** * Representation of 'bool' in the public API as stdbool.h may not * always be available and presence of 'bool' keyword may depend on @@ -171,6 +173,10 @@ enum MHD_Bool MHD_YES = 1 }; + +#define MHD_BOOL_DEFINED 1 +#endif /* ! MHD_BOOL_DEFINED */ + #ifndef MHD_STRINGS_DEFINED @@ -638,16 +644,46 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode */ MHD_SC_REQ_COOKIE_INVALID = 40163 , + /** + * Parsing of the POST data is incomplete because client used incorrect + * format of POST encoding. + * Some POST data is available or has been provided via callback. + */ + MHD_SC_REQ_POST_PARSE_PARTIAL_INVALID_POST_FORMAT = 40202 + , /** * The request does not have "Content-Type:" header and POST data cannot * be parsed */ - MHD_SC_REQ_POST_PARSE_FAILED_NO_CNTN_TYPE = 40180 + MHD_SC_REQ_POST_PARSE_FAILED_NO_CNTN_TYPE = 40280 + , + /** + * The request has unknown POST encoding specified by "Content-Type:" header + */ + MHD_SC_REQ_POST_PARSE_FAILED_UNKNOWN_CNTN_TYPE = 40281 + , + /** + * The request has "Content-Type: multipart/form-data" header without + * "boundary" parameter + */ + MHD_SC_REQ_POST_PARSE_FAILED_HEADER_NO_BOUNDARY = 40282 + , + /** + * The request has "Content-Type: multipart/form-data" header with misformed + * data + */ + MHD_SC_REQ_POST_PARSE_FAILED_HEADER_MISFORMED = 40283 + , + /** + * The POST data cannot be parsed because client used incorrect format + * of POST encoding. + */ + MHD_SC_REQ_POST_PARSE_FAILED_INVALID_POST_FORMAT = 40290 , /** * The request cannot be processed. Sending error reply. */ - MHD_SC_REQ_PROCCESSING_ERR_REPLY = 40200 + MHD_SC_REQ_PROCCESSING_ERR_REPLY = 41000 , /* 50000-level errors are because of an error internal @@ -1109,6 +1145,26 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode */ MHD_SC_ERR_RESPONSE_ALLOCATION_FAILURE = 50250 , + /** + * The request POST data cannot be parsed because stream has not enough + * pool memory free. + */ + MHD_SC_REQ_POST_PARSE_FAILED_NO_POOL_MEM = 50260 + , + /** + * The POST data cannot be parsed completely because no "large shared buffer" + * space is available. + * Some POST data may be parsed. + */ + MHD_SC_REQ_POST_PARSE_FAILED_NO_LARGE_BUF_MEM = 50261 + , + /** + * The application set POST encoding to "multipart/form-data", but the request + * has no "Content-Type: multipart/form-data" header which is required + * to find "boundary" used in this encoding + */ + MHD_SC_REQ_POST_PARSE_FAILED_HEADER_NOT_MPART = 50284 + , /** * The feature is not supported by this MHD build (either * disabled by configure parameters or build platform @@ -4912,7 +4968,8 @@ struct MHD_NameValueKind * is queued. If the data is needed beyond this point, it should be copied. * * @param cls closure - * @param nvt the name, the value and the kind of the element + * @param nv the name and the value of the element + * @param kind the type (kind) of the element * @return #MHD_YES to continue iterating, * #MHD_NO to abort the iteration * @ingroup request @@ -4920,7 +4977,8 @@ struct MHD_NameValueKind typedef enum MHD_Bool (MHD_FN_PAR_NONNULL_ (2) *MHD_NameValueIterator)(void *cls, - const struct MHD_NameValueKind *nvt); + enum MHD_ValueKind kind, + const struct MHD_NameAndValue *nv); /** @@ -6173,6 +6231,81 @@ MHD_FN_PAR_NONNULL_ (1); #define MHD_action_process_upload_inc(request,uc,uc_cls) \ MHD_action_process_upload (request, 0, NULL, NULL, uc, uc_cls) +#ifndef MHD_POST_PARSE_RESULT_DEFINED + +enum MHD_FIXED_ENUM_MHD_SET_ MHD_PostParseResult +{ + /** + * The POST data parse successfully and completely. + */ + MHD_POST_PARSE_RES_OK = 0 + , + /** + * The POST request has no content or zero-length content. + */ + MHD_POST_PARSE_RES_REQUEST_EMPTY = 1 + , + /** + * Parsing of the POST data is incomplete because client used incorrect + * format of POST encoding. + * Some POST data is available or has been provided via callback. + */ + MHD_POST_PARSE_RES_PARTIAL_INVALID_POST_FORMAT = 2 + , + /** + * The POST data cannot be parsed completely because the stream has + * no free pool memory. + * Some POST data may be parsed. + */ + MHD_POST_PARSE_RES_FAILED_NO_POOL_MEM = 60 + , + /** + * The POST data cannot be parsed completely because no "large shared buffer" + * space is available. + * Some POST data may be parsed. + */ + MHD_POST_PARSE_RES_FAILED_NO_LARGE_BUF_MEM = 61 + , + /** + * The POST data cannot be parsed because 'Content-Type:' is unknown. + */ + MHD_POST_PARSE_RES_FAILED_UNKNOWN_CNTN_TYPE = 80 + , + /** + * The POST data cannot be parsed because 'Content-Type:' header is not set. + */ + MHD_POST_PARSE_RES_FAILED_NO_CNTN_TYPE = 81 + , + /** + * The POST data cannot be parsed because "Content-Type:" request header has + * no "boundary" parameter for "multipart/form-data" + */ + MHD_POST_PARSE_RES_FAILED_HEADER_NO_BOUNDARY = 82 + , + /** + * The POST data cannot be parsed because "Content-Type: multipart/form-data" + * request header is misformed + */ + MHD_POST_PARSE_RES_FAILED_HEADER_MISFORMED = 83 + , + /** + * The application set POST encoding to "multipart/form-data", but the request + * has no "Content-Type: multipart/form-data" header which is required + * to find "boundary" used in this encoding + */ + MHD_POST_PARSE_RES_FAILED_HEADER_NOT_MPART = 84 + , + /** + * The POST data cannot be parsed because client used incorrect format + * of POST encoding. + */ + MHD_POST_PARSE_RES_FAILED_INVALID_POST_FORMAT = 90 + +}; + +#define MHD_POST_PARSE_RESULT_DEFINED 1 +#endif /* ! MHD_POST_PARSE_RESULT_DEFINED */ + #ifndef MHD_POST_DATA_READER_DEFINED /** @@ -6182,15 +6315,24 @@ MHD_FN_PAR_NONNULL_ (1); * * @param req the request * @param cls user-specified closure - * @param name 0-terminated key for the value - * @param filename name of the uploaded file, NULL if not known - * @param content_type mime-type of the data, NULL if not known - * @param encoding the encoding of the data - * @param data pointer to @a size bytes of data at the - * specified @a off offset, - * NOT zero-terminated - * @param off offset of data in the overall value - * @param size number of bytes in @a data available + * @param name the name of the POST field + * @param filename the name of the uploaded file, @a cstr member is NULL if not + * known / not provided + * @param content_type the mime-type of the data, cstr member is NULL if not + * known / not provided + * @param encoding the encoding of the data, cstr member is NULL if not known / + * not provided + * @param size the number of bytes in @a data available, may be zero if + * the @a final_data is #MHD_YES + * @param data the pointer to @a size bytes of data at the specified + * @a off offset, NOT zero-terminated + * @param off the offset of @a data in the overall value, always equal to + * the sum of sizes of previous calls for the same field / file; + * client may provide more than one field with the same name and + * the same filename, the new filed (or file) is indicated by zero + * value of @a off (and the end is indicated by @a final_data) + * @param final_data if set to #MHD_YES then full field data is provided, + * if set to #MHD_NO then more field data may be provided * @return action specifying how to proceed: * #MHD_upload_action_continue() if all is well, * #MHD_upload_action_suspend() to stop reading the upload until @@ -6209,9 +6351,10 @@ typedef const struct MHD_UploadAction * const struct MHD_StringNullable *filename, const struct MHD_StringNullable *content_type, const struct MHD_StringNullable *encoding, + size_t size, const void *data, uint_fast64_t off, - size_t size); + enum MHD_Bool final_data); /** @@ -6219,15 +6362,14 @@ typedef const struct MHD_UploadAction * * of the postprocessor upload data. * @param req the request * @param cls the closure - * @param parsing_result the result of POST data parsing: MHD_SC_OK, - MHD_SC_REQ_POST_PARSE_FAILED_NO_CNTN_TYPE or ... + * @param parsing_result the result of POST data parsing * @return the action to proceed */ typedef const struct MHD_UploadAction * (MHD_FN_PAR_NONNULL_ (1) *MHD_PostDataFinished) (struct MHD_Request *req, void *cls, - enum MHD_StatusCode parsing_result); + enum MHD_PostParseResult parsing_result); #define MHD_POST_DATA_READER_DEFINED 1 #endif /* ! MHD_POST_DATA_READER_DEFINED */ @@ -6250,8 +6392,12 @@ typedef const struct MHD_UploadAction * * request POST data. Within the set limit the buffer is * allocated automatically from the "large" shared memory * pool if necessary. - * @param auto_stream_size the size above which values are not buffered and - * processed by the @a steam_reader automatically, + * @param auto_stream_size the size of the field (in encoded form) above which + * values are not buffered and processed by + * the @a steam_reader automatically; + * useful to have large data (like file uploads) + * processed incrementally, while keeping buffer space + * for small fields only; * ignored if @a stream_reader is NULL * @param enc the data encoding to use, * use #MHD_HTTP_POST_ENCODING_OTHER to detect automatically @@ -6283,44 +6429,54 @@ MHD_action_parse_post (struct MHD_Request *request, MHD_FN_PAR_NONNULL_ (1); +#ifndef MHD_POSTFILED_DEFINED + /** * Post data element. * If any member is not provided/set then pointer to C string is NULL. * If any member is set to empty string then pointer to C string not NULL, * but the length is zero. */ -struct MHD_PostData +struct MHD_PostField { /** * The name of the field */ struct MHD_String name; + /** + * The field data + * If not set or defined then to C string is NULL. + * If set to empty string then pointer to C string not NULL, + * but the length is zero. + */ + struct MHD_StringNullable value; /** * The filename if provided (only for "multipart/form-data") * If not set or defined then to C string is NULL. * If set to empty string then pointer to C string not NULL, + * but the length is zero. */ struct MHD_StringNullable filename; /** * The Content-Type if provided (only for "multipart/form-data") * If not set or defined then to C string is NULL. * If set to empty string then pointer to C string not NULL, + * but the length is zero. */ struct MHD_StringNullable content_type; /** * The Transfer-Encoding if provided (only for "multipart/form-data") * If not set or defined then to C string is NULL. * If set to empty string then pointer to C string not NULL, + * but the length is zero. */ struct MHD_StringNullable transfer_encoding; - /** - * The field data - * If not set or defined then to C string is NULL. - * If set to empty string then pointer to C string not NULL, - */ - struct MHD_StringNullable value; }; +#define MHD_POSTFILED_DEFINED 1 +#endif /* ! MHD_POSTFILED_DEFINED */ + + /** * Iterator over POST data. * @@ -6336,7 +6492,7 @@ struct MHD_PostData typedef enum MHD_Bool (MHD_FN_PAR_NONNULL_ (2) *MHD_PostDataIterator)(void *cls, - const struct MHD_PostData *data); + const struct MHD_PostField *data); /** * Get all of the post data from the request via request. @@ -6372,7 +6528,7 @@ MHD_EXTERN_ size_t MHD_request_get_post_data_list ( struct MHD_Request *request, size_t num_elements, - struct MHD_PostData elements[MHD_FN_PAR_DYN_ARR_SIZE_ (num_elements)]) + struct MHD_PostField elements[MHD_FN_PAR_DYN_ARR_SIZE_ (num_elements)]) MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (3) MHD_FN_PAR_OUT_SIZE_ (3,2); diff --git a/src/include/microhttpd2_main.h.in b/src/include/microhttpd2_main.h.in index 3ae20eb9..bed9c9fb 100644 --- a/src/include/microhttpd2_main.h.in +++ b/src/include/microhttpd2_main.h.in @@ -611,7 +611,8 @@ struct MHD_NameValueKind * is queued. If the data is needed beyond this point, it should be copied. * * @param cls closure - * @param nvt the name, the value and the kind of the element + * @param nv the name and the value of the element + * @param kind the type (kind) of the element * @return #MHD_YES to continue iterating, * #MHD_NO to abort the iteration * @ingroup request @@ -619,7 +620,8 @@ struct MHD_NameValueKind typedef enum MHD_Bool (MHD_FN_PAR_NONNULL_ (2) *MHD_NameValueIterator)(void *cls, - const struct MHD_NameValueKind *nvt); + enum MHD_ValueKind kind, + const struct MHD_NameAndValue *nv); /** @@ -1872,6 +1874,81 @@ MHD_FN_PAR_NONNULL_ (1); #define MHD_action_process_upload_inc(request,uc,uc_cls) \ MHD_action_process_upload (request, 0, NULL, NULL, uc, uc_cls) +#ifndef MHD_POST_PARSE_RESULT_DEFINED + +enum MHD_FIXED_ENUM_MHD_SET_ MHD_PostParseResult +{ + /** + * The POST data parse successfully and completely. + */ + MHD_POST_PARSE_RES_OK = 0 + , + /** + * The POST request has no content or zero-length content. + */ + MHD_POST_PARSE_RES_REQUEST_EMPTY = 1 + , + /** + * Parsing of the POST data is incomplete because client used incorrect + * format of POST encoding. + * Some POST data is available or has been provided via callback. + */ + MHD_POST_PARSE_RES_PARTIAL_INVALID_POST_FORMAT = 2 + , + /** + * The POST data cannot be parsed completely because the stream has + * no free pool memory. + * Some POST data may be parsed. + */ + MHD_POST_PARSE_RES_FAILED_NO_POOL_MEM = 60 + , + /** + * The POST data cannot be parsed completely because no "large shared buffer" + * space is available. + * Some POST data may be parsed. + */ + MHD_POST_PARSE_RES_FAILED_NO_LARGE_BUF_MEM = 61 + , + /** + * The POST data cannot be parsed because 'Content-Type:' is unknown. + */ + MHD_POST_PARSE_RES_FAILED_UNKNOWN_CNTN_TYPE = 80 + , + /** + * The POST data cannot be parsed because 'Content-Type:' header is not set. + */ + MHD_POST_PARSE_RES_FAILED_NO_CNTN_TYPE = 81 + , + /** + * The POST data cannot be parsed because "Content-Type:" request header has + * no "boundary" parameter for "multipart/form-data" + */ + MHD_POST_PARSE_RES_FAILED_HEADER_NO_BOUNDARY = 82 + , + /** + * The POST data cannot be parsed because "Content-Type: multipart/form-data" + * request header is misformed + */ + MHD_POST_PARSE_RES_FAILED_HEADER_MISFORMED = 83 + , + /** + * The application set POST encoding to "multipart/form-data", but the request + * has no "Content-Type: multipart/form-data" header which is required + * to find "boundary" used in this encoding + */ + MHD_POST_PARSE_RES_FAILED_HEADER_NOT_MPART = 84 + , + /** + * The POST data cannot be parsed because client used incorrect format + * of POST encoding. + */ + MHD_POST_PARSE_RES_FAILED_INVALID_POST_FORMAT = 90 + +}; + +#define MHD_POST_PARSE_RESULT_DEFINED 1 +#endif /* ! MHD_POST_PARSE_RESULT_DEFINED */ + #ifndef MHD_POST_DATA_READER_DEFINED /** @@ -1881,15 +1958,24 @@ MHD_FN_PAR_NONNULL_ (1); * * @param req the request * @param cls user-specified closure - * @param name 0-terminated key for the value - * @param filename name of the uploaded file, NULL if not known - * @param content_type mime-type of the data, NULL if not known - * @param encoding the encoding of the data - * @param data pointer to @a size bytes of data at the - * specified @a off offset, - * NOT zero-terminated - * @param off offset of data in the overall value - * @param size number of bytes in @a data available + * @param name the name of the POST field + * @param filename the name of the uploaded file, @a cstr member is NULL if not + * known / not provided + * @param content_type the mime-type of the data, cstr member is NULL if not + * known / not provided + * @param encoding the encoding of the data, cstr member is NULL if not known / + * not provided + * @param size the number of bytes in @a data available, may be zero if + * the @a final_data is #MHD_YES + * @param data the pointer to @a size bytes of data at the specified + * @a off offset, NOT zero-terminated + * @param off the offset of @a data in the overall value, always equal to + * the sum of sizes of previous calls for the same field / file; + * client may provide more than one field with the same name and + * the same filename, the new filed (or file) is indicated by zero + * value of @a off (and the end is indicated by @a final_data) + * @param final_data if set to #MHD_YES then full field data is provided, + * if set to #MHD_NO then more field data may be provided * @return action specifying how to proceed: * #MHD_upload_action_continue() if all is well, * #MHD_upload_action_suspend() to stop reading the upload until @@ -1908,9 +1994,10 @@ typedef const struct MHD_UploadAction * const struct MHD_StringNullable *filename, const struct MHD_StringNullable *content_type, const struct MHD_StringNullable *encoding, + size_t size, const void *data, uint_fast64_t off, - size_t size); + enum MHD_Bool final_data); /** @@ -1918,15 +2005,14 @@ typedef const struct MHD_UploadAction * * of the postprocessor upload data. * @param req the request * @param cls the closure - * @param parsing_result the result of POST data parsing: MHD_SC_OK, - MHD_SC_REQ_POST_PARSE_FAILED_NO_CNTN_TYPE or ... + * @param parsing_result the result of POST data parsing * @return the action to proceed */ typedef const struct MHD_UploadAction * (MHD_FN_PAR_NONNULL_ (1) *MHD_PostDataFinished) (struct MHD_Request *req, void *cls, - enum MHD_StatusCode parsing_result); + enum MHD_PostParseResult parsing_result); #define MHD_POST_DATA_READER_DEFINED 1 #endif /* ! MHD_POST_DATA_READER_DEFINED */ @@ -1949,8 +2035,12 @@ typedef const struct MHD_UploadAction * * request POST data. Within the set limit the buffer is * allocated automatically from the "large" shared memory * pool if necessary. - * @param auto_stream_size the size above which values are not buffered and - * processed by the @a steam_reader automatically, + * @param auto_stream_size the size of the field (in encoded form) above which + * values are not buffered and processed by + * the @a steam_reader automatically; + * useful to have large data (like file uploads) + * processed incrementally, while keeping buffer space + * for small fields only; * ignored if @a stream_reader is NULL * @param enc the data encoding to use, * use #MHD_HTTP_POST_ENCODING_OTHER to detect automatically @@ -1982,44 +2072,54 @@ MHD_action_parse_post (struct MHD_Request *request, MHD_FN_PAR_NONNULL_ (1); +#ifndef MHD_POSTFILED_DEFINED + /** * Post data element. * If any member is not provided/set then pointer to C string is NULL. * If any member is set to empty string then pointer to C string not NULL, * but the length is zero. */ -struct MHD_PostData +struct MHD_PostField { /** * The name of the field */ struct MHD_String name; + /** + * The field data + * If not set or defined then to C string is NULL. + * If set to empty string then pointer to C string not NULL, + * but the length is zero. + */ + struct MHD_StringNullable value; /** * The filename if provided (only for "multipart/form-data") * If not set or defined then to C string is NULL. * If set to empty string then pointer to C string not NULL, + * but the length is zero. */ struct MHD_StringNullable filename; /** * The Content-Type if provided (only for "multipart/form-data") * If not set or defined then to C string is NULL. * If set to empty string then pointer to C string not NULL, + * but the length is zero. */ struct MHD_StringNullable content_type; /** * The Transfer-Encoding if provided (only for "multipart/form-data") * If not set or defined then to C string is NULL. * If set to empty string then pointer to C string not NULL, + * but the length is zero. */ struct MHD_StringNullable transfer_encoding; - /** - * The field data - * If not set or defined then to C string is NULL. - * If set to empty string then pointer to C string not NULL, - */ - struct MHD_StringNullable value; }; +#define MHD_POSTFILED_DEFINED 1 +#endif /* ! MHD_POSTFILED_DEFINED */ + + /** * Iterator over POST data. * @@ -2035,7 +2135,7 @@ struct MHD_PostData typedef enum MHD_Bool (MHD_FN_PAR_NONNULL_ (2) *MHD_PostDataIterator)(void *cls, - const struct MHD_PostData *data); + const struct MHD_PostField *data); /** * Get all of the post data from the request via request. @@ -2071,7 +2171,7 @@ MHD_EXTERN_ size_t MHD_request_get_post_data_list ( struct MHD_Request *request, size_t num_elements, - struct MHD_PostData elements[MHD_FN_PAR_DYN_ARR_SIZE_ (num_elements)]) + struct MHD_PostField elements[MHD_FN_PAR_DYN_ARR_SIZE_ (num_elements)]) MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (3) MHD_FN_PAR_OUT_SIZE_ (3,2); diff --git a/src/include/microhttpd2_preamble.h.in b/src/include/microhttpd2_preamble.h.in index 08727134..d83739d0 100644 --- a/src/include/microhttpd2_preamble.h.in +++ b/src/include/microhttpd2_preamble.h.in @@ -147,6 +147,8 @@ */ #define MHD_VERSION 0x01990001 +#ifndef MHD_BOOL_DEFINED + /** * Representation of 'bool' in the public API as stdbool.h may not * always be available and presence of 'bool' keyword may depend on @@ -171,6 +173,10 @@ enum MHD_Bool MHD_YES = 1 }; + +#define MHD_BOOL_DEFINED 1 +#endif /* ! MHD_BOOL_DEFINED */ + #ifndef MHD_STRINGS_DEFINED @@ -638,16 +644,46 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode */ MHD_SC_REQ_COOKIE_INVALID = 40163 , + /** + * Parsing of the POST data is incomplete because client used incorrect + * format of POST encoding. + * Some POST data is available or has been provided via callback. + */ + MHD_SC_REQ_POST_PARSE_PARTIAL_INVALID_POST_FORMAT = 40202 + , /** * The request does not have "Content-Type:" header and POST data cannot * be parsed */ - MHD_SC_REQ_POST_PARSE_FAILED_NO_CNTN_TYPE = 40180 + MHD_SC_REQ_POST_PARSE_FAILED_NO_CNTN_TYPE = 40280 + , + /** + * The request has unknown POST encoding specified by "Content-Type:" header + */ + MHD_SC_REQ_POST_PARSE_FAILED_UNKNOWN_CNTN_TYPE = 40281 + , + /** + * The request has "Content-Type: multipart/form-data" header without + * "boundary" parameter + */ + MHD_SC_REQ_POST_PARSE_FAILED_HEADER_NO_BOUNDARY = 40282 + , + /** + * The request has "Content-Type: multipart/form-data" header with misformed + * data + */ + MHD_SC_REQ_POST_PARSE_FAILED_HEADER_MISFORMED = 40283 + , + /** + * The POST data cannot be parsed because client used incorrect format + * of POST encoding. + */ + MHD_SC_REQ_POST_PARSE_FAILED_INVALID_POST_FORMAT = 40290 , /** * The request cannot be processed. Sending error reply. */ - MHD_SC_REQ_PROCCESSING_ERR_REPLY = 40200 + MHD_SC_REQ_PROCCESSING_ERR_REPLY = 41000 , /* 50000-level errors are because of an error internal @@ -1109,6 +1145,26 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode */ MHD_SC_ERR_RESPONSE_ALLOCATION_FAILURE = 50250 , + /** + * The request POST data cannot be parsed because stream has not enough + * pool memory free. + */ + MHD_SC_REQ_POST_PARSE_FAILED_NO_POOL_MEM = 50260 + , + /** + * The POST data cannot be parsed completely because no "large shared buffer" + * space is available. + * Some POST data may be parsed. + */ + MHD_SC_REQ_POST_PARSE_FAILED_NO_LARGE_BUF_MEM = 50261 + , + /** + * The application set POST encoding to "multipart/form-data", but the request + * has no "Content-Type: multipart/form-data" header which is required + * to find "boundary" used in this encoding + */ + MHD_SC_REQ_POST_PARSE_FAILED_HEADER_NOT_MPART = 50284 + , /** * The feature is not supported by this MHD build (either * disabled by configure parameters or build platform diff --git a/src/mhd2/Makefile.am b/src/mhd2/Makefile.am index cc6ed3c3..339ed14f 100644 --- a/src/mhd2/Makefile.am +++ b/src/mhd2/Makefile.am @@ -36,6 +36,7 @@ libmicrohttpd2_la_SOURCES = \ mhd_sockets_funcs.c mhd_sockets_funcs.h \ mhd_socket_error.c mhd_socket_error.h \ mhd_atomic_counter.c mhd_atomic_counter.h \ + mhd_bool.h \ mhd_str.c mhd_str.h \ mhd_str_macros.h mhd_str_types.h \ mhd_buffer.h \ @@ -90,6 +91,15 @@ libmicrohttpd2_la_SOURCES += \ compat_calloc.c endif +post_parser_files = \ + http_post_enc.h \ + mhd_post_parser.h mhd_post_result.h mhd_postfield.h \ + post_parser_funcs.c post_parser_funcs.h + +if HAVE_POST_PARSER + libmicrohttpd2_la_SOURCES += $(post_parser_files) +endif + libmicrohttpd2_la_CPPFLAGS = \ $(AM_CPPFLAGS) $(MHD_LIB_CPPFLAGS) $(MHD_TLS_LIB_CPPFLAGS) \ diff --git a/src/mhd2/daemon_funcs.c b/src/mhd2/daemon_funcs.c index 772aa6ca..523a6884 100644 --- a/src/mhd2/daemon_funcs.c +++ b/src/mhd2/daemon_funcs.c @@ -120,11 +120,11 @@ mhd_daemon_get_lbuf (struct MHD_Daemon *restrict d, if (! mhd_daemon_claim_lbuf (d, requested_size)) { buf->size = 0; - buf->buf = NULL; + buf->data = NULL; return false; } - buf->buf = (char *) malloc (requested_size); - if (NULL == buf->buf) + buf->data = (char *) malloc (requested_size); + if (NULL == buf->data) { buf->size = 0; mhd_daemon_reclaim_lbuf (d, requested_size); @@ -142,23 +142,23 @@ mhd_daemon_grow_lbuf (struct MHD_Daemon *restrict d, struct mhd_Buffer *restrict buf) { void *new_alloc; - mhd_assert (NULL != buf->buf || 0 == buf->size); - mhd_assert (0 != buf->size || NULL == buf->buf); + mhd_assert (NULL != buf->data || 0 == buf->size); + mhd_assert (0 != buf->size || NULL == buf->data); if (! mhd_daemon_claim_lbuf (d, grow_size)) return false; - if (NULL == buf->buf) + if (NULL == buf->data) new_alloc = malloc (grow_size); else - new_alloc = realloc (buf->buf, buf->size + grow_size); + new_alloc = realloc (buf->data, buf->size + grow_size); if (NULL == new_alloc) { mhd_daemon_reclaim_lbuf (d, grow_size); return false; } - buf->buf = (char *) new_alloc; + buf->data = (char *) new_alloc; buf->size += grow_size; return true; @@ -172,11 +172,11 @@ mhd_daemon_free_lbuf (struct MHD_Daemon *restrict d, { if (0 == buf->size) { - mhd_assert (NULL == buf->buf); + mhd_assert (NULL == buf->data); return; } - free (buf->buf); - buf->buf = NULL; + free (buf->data); + buf->data = NULL; mhd_daemon_reclaim_lbuf (d, buf->size); buf->size = 0; } diff --git a/src/mhd2/events_process.c b/src/mhd2/events_process.c index 3b18b6f8..6e2fddcf 100644 --- a/src/mhd2/events_process.c +++ b/src/mhd2/events_process.c @@ -264,6 +264,7 @@ close_all_daemon_conns (struct MHD_Daemon *d) c = mhd_DLINKEDL_GET_LAST (&(d->conns),all_conn)) { mhd_conn_pre_close_d_shutdown (c); + mhd_conn_pre_clean (c); mhd_conn_close_final (c); } } diff --git a/src/mhd2/http_post_enc.h b/src/mhd2/http_post_enc.h new file mode 100644 index 00000000..dbd24678 --- /dev/null +++ b/src/mhd2/http_post_enc.h @@ -0,0 +1,81 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd 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. + + GNU libmicrohttpd 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 src/mhd2/http_post_enc.h + * @brief The definition of the enum for POST encoding types + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_HTTP_POST_ENC_H +#define MHD_HTTP_POST_ENC_H 1 + +#include "mhd_sys_options.h" + + +#ifndef MHD_HTTP_POSTENCODING_DEFINED + +/** + * @brief Possible encodings for HTML forms submitted as HTTP POST requests + * + * @defgroup postenc HTTP POST encodings + * See also: https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-2 + * @{ + */ +enum MHD_FIXED_ENUM_MHD_APP_SET_ MHD_HTTP_PostEncoding +{ + /** + * No post encoding / broken data / unknown encoding + */ + MHD_HTTP_POST_ENCODING_OTHER = 0 + , + /** + * "application/x-www-form-urlencoded" + * See https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#url-encoded-form-data + * See https://url.spec.whatwg.org/#application/x-www-form-urlencoded + * See https://datatracker.ietf.org/doc/html/rfc3986#section-2 + */ + MHD_HTTP_POST_ENCODING_FORM_URLENCODED = 1 + , + /** + * "multipart/form-data" + * See https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart-form-data + * See https://www.rfc-editor.org/rfc/rfc7578.html + */ + MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA = 2 + , + /** + * "text/plain" + * Introduced by HTML5 + * See https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#plain-text-form-data + * @warning Format is ambiguous. Do not use unless there is a very strong reason. + */ + MHD_HTTP_POST_ENCODING_TEXT_PLAIN = 3 +}; + + +/** @} */ /* end of group postenc */ + +# define MHD_HTTP_POSTENCODING_DEFINED 1 +#endif /* ! MHD_HTTP_POSTENCODING_DEFINED */ + + + +#endif /* ! MHD_HTTP_POST_ENC_H */ diff --git a/src/mhd2/mhd_action.h b/src/mhd2/mhd_action.h index 2bde0bd2..48c273d9 100644 --- a/src/mhd2/mhd_action.h +++ b/src/mhd2/mhd_action.h @@ -36,7 +36,8 @@ #include "http_post_enc.h" #ifdef HAVE_POST_PARSER -# include "mhd_public_api.h" +# include "mhd_bool.h" +# include "mhd_post_result.h" #endif @@ -146,16 +147,16 @@ typedef const struct MHD_UploadAction * const struct MHD_StringNullable *filename, const struct MHD_StringNullable *content_type, const struct MHD_StringNullable *encoding, + size_t size, const void *data, uint_fast64_t off, - size_t size); - + enum MHD_Bool final_data); typedef const struct MHD_UploadAction * (MHD_FN_PAR_NONNULL_ (1) *MHD_PostDataFinished) (struct MHD_Request *req, void *cls, - enum MHD_StatusCode parsing_result); + enum MHD_PostParseResult parsing_result); #define MHD_POST_DATA_READER_DEFINED 1 #endif /* ! MHD_POST_DATA_READER_DEFINED */ diff --git a/src/mhd2/mhd_bool.h b/src/mhd2/mhd_bool.h new file mode 100644 index 00000000..0b580a0f --- /dev/null +++ b/src/mhd2/mhd_bool.h @@ -0,0 +1,54 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Christian Grothoff + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd 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. + + GNU libmicrohttpd 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 src/mhd2/mhd_bool.h + * @brief The definition of the enum MHD_Bool, which is used in public API + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_BOOL_H +#define MHD_BOOL_H 1 + +#include "mhd_sys_options.h" + +#ifndef MHD_BOOL_DEFINED + +enum MHD_Bool +{ + /** + * MHD-internal return code for "NO". + */ + MHD_NO = 0 + , + /** + * MHD-internal return code for "YES". All non-zero values + * will be interpreted as "YES", but MHD will only ever + * return #MHD_YES or #MHD_NO. + */ + MHD_YES = 1 +}; + + +#define MHD_BOOL_DEFINED 1 +#endif /* ! MHD_BOOL_DEFINED */ + +#endif /* ! MHD_BOOL_H */ diff --git a/src/mhd2/mhd_buffer.h b/src/mhd2/mhd_buffer.h index 1e282b2c..e52d8264 100644 --- a/src/mhd2/mhd_buffer.h +++ b/src/mhd2/mhd_buffer.h @@ -31,19 +31,35 @@ #include "sys_base_types.h" /** - * The buffer with size + * The buffer with the size */ struct mhd_Buffer { /** - * The size of the allocated @a buf buffer + * The size of the data or the allocation pointed by @a buf buffer */ size_t size; /** - * The pointer to the allocation + * The pointer to the data or the allocation */ - char *buf; + char *data; +}; + +/** + * The data with the size + */ +struct mhd_BufferConst +{ + /** + * The size of the data pointed by @a buf buffer + */ + size_t size; + + /** + * The pointer to the data + */ + const char *data; }; #endif /* ! MHD_BUFFER_H */ diff --git a/src/mhd2/mhd_connection.h b/src/mhd2/mhd_connection.h index 663af748..e0049a96 100644 --- a/src/mhd2/mhd_connection.h +++ b/src/mhd2/mhd_connection.h @@ -201,133 +201,139 @@ enum MHD_FIXED_ENUM_ MHD_CONNECTION_STATE * Connection just started (no headers received). * Waiting for the line with the request type, URL and version. */ - MHD_CONNECTION_INIT = 0, - + MHD_CONNECTION_INIT = 0 + , /** * Part of the request line was received. * Wait for complete line. */ - MHD_CONNECTION_REQ_LINE_RECEIVING, - + MHD_CONNECTION_REQ_LINE_RECEIVING + , /** * We got the URL (and request type and version). Wait for a header line. * * A milestone state. No received data is processed in this state. */ - MHD_CONNECTION_REQ_LINE_RECEIVED, - + MHD_CONNECTION_REQ_LINE_RECEIVED + , /** * Receiving request headers. Wait for the rest of the headers. */ - MHD_CONNECTION_REQ_HEADERS_RECEIVING, - + MHD_CONNECTION_REQ_HEADERS_RECEIVING + , /** * We got the request headers. Process them. */ - MHD_CONNECTION_HEADERS_RECEIVED, - + MHD_CONNECTION_HEADERS_RECEIVED + , /** * We have processed the request headers. Call application callback. */ - MHD_CONNECTION_HEADERS_PROCESSED, - + MHD_CONNECTION_HEADERS_PROCESSED + , /** * We have processed the headers and need to send 100 CONTINUE. */ - MHD_CONNECTION_CONTINUE_SENDING, - + MHD_CONNECTION_CONTINUE_SENDING + , /** * We have sent 100 CONTINUE (or do not need to). Read the message body. */ - MHD_CONNECTION_BODY_RECEIVING, - + MHD_CONNECTION_BODY_RECEIVING + , /** * We got the request body. * * A milestone state. No received data is processed in this state. */ - MHD_CONNECTION_BODY_RECEIVED, - + MHD_CONNECTION_BODY_RECEIVED + , /** * We are reading the request footers. */ - MHD_CONNECTION_FOOTERS_RECEIVING, - + MHD_CONNECTION_FOOTERS_RECEIVING + , /** * We received the entire footer. * * A milestone state. No data is receiving in this state. */ - MHD_CONNECTION_FOOTERS_RECEIVED, - + MHD_CONNECTION_FOOTERS_RECEIVED + , /** * We received the entire request. * * A milestone state. No data is receiving in this state. */ - MHD_CONNECTION_FULL_REQ_RECEIVED, - + MHD_CONNECTION_FULL_REQ_RECEIVED + , /** * Finished receiving request data: either complete request received or * MHD is going to send reply early, without getting full request. */ - MHD_CONNECTION_REQ_RECV_FINISHED, - + MHD_CONNECTION_REQ_RECV_FINISHED + , /** * Finished reading of the request and the response is ready. * Switch internal logic from receiving to sending, prepare connection * sending the reply and build the reply header. */ - MHD_CONNECTION_START_REPLY, - + MHD_CONNECTION_START_REPLY + , /** * We have prepared the response headers in the write buffer. * Send the response headers. */ - MHD_CONNECTION_HEADERS_SENDING, - + MHD_CONNECTION_HEADERS_SENDING + , /** * We have sent the response headers. Get ready to send the body. */ - MHD_CONNECTION_HEADERS_SENT, - + MHD_CONNECTION_HEADERS_SENT + , /** * We are waiting for the client to provide more * data of a non-chunked body. */ - MHD_CONNECTION_UNCHUNKED_BODY_UNREADY, - + MHD_CONNECTION_UNCHUNKED_BODY_UNREADY + , /** * We are ready to send a part of a non-chunked body. Send it. */ - MHD_CONNECTION_UNCHUNKED_BODY_READY, - + MHD_CONNECTION_UNCHUNKED_BODY_READY + , /** * We are waiting for the client to provide a chunk of the body. */ - MHD_CONNECTION_CHUNKED_BODY_UNREADY, - + MHD_CONNECTION_CHUNKED_BODY_UNREADY + , /** * We are ready to send a chunk. */ - MHD_CONNECTION_CHUNKED_BODY_READY, - + MHD_CONNECTION_CHUNKED_BODY_READY + , /** * We have sent the chunked response body. Prepare the footers. */ - MHD_CONNECTION_CHUNKED_BODY_SENT, - + MHD_CONNECTION_CHUNKED_BODY_SENT + , /** * We have prepared the response footer. Send it. */ - MHD_CONNECTION_FOOTERS_SENDING, - + MHD_CONNECTION_FOOTERS_SENDING + , /** * We have sent the entire reply. * Shutdown connection or restart processing to get a new request. */ - MHD_CONNECTION_FULL_REPLY_SENT, - + MHD_CONNECTION_FULL_REPLY_SENT + , + /** + * Finished regular connection processing. + * Initial buffers cleanup and freeing. + */ + MHD_CONNECTION_PRE_CLOSING + , /** * This connection is to be closed. */ diff --git a/src/mhd2/mhd_daemon.h b/src/mhd2/mhd_daemon.h index 5ab886cd..0f825442 100644 --- a/src/mhd2/mhd_daemon.h +++ b/src/mhd2/mhd_daemon.h @@ -831,6 +831,15 @@ struct mhd_DaemonRequestProcessingSettings }; +/** + * Get whether bare LF in HTTP header and other protocol elements + * should be treated as the line termination depending on the configured + * strictness level. + * RFC 9112, section 2.2 + */ +#define mhd_ALLOW_BARE_LF_AS_CRLF(discp_lvl) (0 >= discp_lvl) + + #ifndef NDEBUG /** * Various debugging data diff --git a/src/mhd2/mhd_post_parser.h b/src/mhd2/mhd_post_parser.h new file mode 100644 index 00000000..3c45e584 --- /dev/null +++ b/src/mhd2/mhd_post_parser.h @@ -0,0 +1,281 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd 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. + + GNU libmicrohttpd 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 src/mhd2/mhd_post_parser.h + * @brief The definition of the post parsers data structures + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_POST_PARSER_H +#define MHD_POST_PARSER_H 1 + +#include "mhd_sys_options.h" + +#include "sys_base_types.h" +#include "sys_bool_type.h" + +#include "http_post_enc.h" +#include "mhd_post_result.h" +#include "mhd_buffer.h" + +/** + * The "application/x-www-form-urlencoded" parsing data + */ +struct mhd_PostParserUrlEncData +{ + +}; + +/** + * The "multipart/form-data" parsing data + */ +struct mhd_PostParserMPartFormData +{ + /** + * The boundary marker. + * Allocated in the stream pool + */ + struct mhd_BufferConst bound; +}; + + +// TODO: describe +enum MHD_FIXED_ENUM_ mhd_PostTextState +{ + /** + * The line processing has not been started yet + */ + mhd_POST_TEXT_ST_NOT_STARTED = 0 + , + /** + * Processing name of the field + */ + mhd_POST_TEXT_ST_NAME + , + /** + * At the '=' character after the name. + * This is an intermediate state, should be processed and switched to the next + * state immediately. + * Should not be used outside processing loop. + */ + mhd_POST_TEXT_ST_AT_EQ + , + /** + * The '=' character after the name has been found. + * Looking for the first value character. + */ + mhd_POST_TEXT_ST_EQ_FOUND + , + /** + * Processing the value of the field. + */ + mhd_POST_TEXT_ST_VALUE + , + /** + * At the CR character. + * This is an intermediate state, should be processed and switched to the next + * state immediately. + * Should not be used outside processing loop. + */ + mhd_POST_TEXT_ST_AT_CR + , + /** + * Looking for LF character after CR character. + */ + mhd_POST_TEXT_ST_CR_FOUND + , + /** + * At the LF character without preceding CR character. + * This is an intermediate state, should be processed and switched to the next + * state immediately. + * Should not be used outside processing loop. + */ + mhd_POST_TEXT_ST_AT_LF_BARE + , + /** + * End of the line found. + * This is an intermediate state, should be processed and switched to the next + * state immediately. + * Should not be used outside processing loop. + */ + mhd_POST_TEXT_ST_FULL_LINE_FOUND +}; + +/** + * The "text/plain" parsing data + */ +struct mhd_PostParserTextData +{ + /** + * The parsing state + */ + enum mhd_PostTextState st; + + /** + * The index of the start of the name. + */ + size_t name_idx; + + /** + * The length of the name of the current field, not including + * the terminating zero. + * Zero until the length is found. + */ + size_t name_len; + + /** + * The index of the start of the value. + * Zero until the value is found. + * Cannot be zero if any (including zero-length) value available. + */ + size_t value_idx; + + /** + * The length of the value of the current field, not including + * the terminating zero. + * Zero until the length is found. + */ + size_t value_len; +}; + + +/** + * The encoding-specific parsing data + */ +union mhd_PostParserDetailedData +{ + /** + * The "application/x-www-form-urlencoded" parsing data + */ + struct mhd_PostParserUrlEncData u_enc; + + /** + * The "multipart/form-data" parsing data + */ + struct mhd_PostParserMPartFormData m_form; + + /** + * The "text/plain" parsing data + */ + struct mhd_PostParserTextData text; +}; + +// TODO: remove? +/** + * The type of partially processed data in the buffer + */ +enum MHD_FIXED_ENUM_ mhd_PostParserPartProcType +{ + /** + * No data in the buffer + */ + mhd_POST_PARSER_PART_PROC_TYPE_NONE = 0 + , + /** + * The data is partially processed name + */ + mhd_POST_PARSER_PART_PROC_TYPE_NAME + , + /** + * The data is partially processed value + */ + mhd_POST_PARSER_PART_PROC_TYPE_VALUE +}; + +// TODO: remove? +/** + * Buffered partially processed data + */ +struct mhd_PostParserPartProcessedData +{ + /** + * Partially processed data, left from previous upload data portion + */ + struct mhd_Buffer data; + + /** + * The type of partially processed data in the @a data buffer + */ + enum mhd_PostParserPartProcType d_type; +}; + +/** + * The POST parsing data + */ +struct mhd_PostParserData +{ + /** + * The result of parsing POST data + */ + enum MHD_PostParseResult parse_result; + + /** + * The type of POSE encoding is used. + * Active member of @a e_d depends on this type. + */ + enum MHD_HTTP_PostEncoding enc; + + /** + * The encoding-specific parsing data + */ + union mhd_PostParserDetailedData e_d; + + /** + * The "large" buffer for POST parsing and POST upload values + */ + struct mhd_Buffer lbuf; + + /** + * The size of the data currently in the @a lbuf + */ + size_t lbuf_used; + + /** + * The remaining size of "large shared buffer" allowed to allocate for this + * POST parsing + */ + size_t lbuf_left; // TODO: Remove? Rename to 'lbuf_limit'? + + /** + * True if any POST data was parsed successfully. + */ + bool some_data_provided; + + /** + * The start index of the current field. + * If the field is processed by incremental callback, buffer can be freed or + * reused up to this position (inclusive). + */ + size_t field_start; + + /** + * The offset in the value data. + * Used when value is processed incrementally otherwise it is zero. + */ + size_t value_off; + + /** + * The position of the next character to be parsed + */ + size_t next_parse_pos; +}; + +#endif /* ! MHD_POST_PARSER_H */ diff --git a/src/mhd2/mhd_post_result.h b/src/mhd2/mhd_post_result.h new file mode 100644 index 00000000..bec6bd4f --- /dev/null +++ b/src/mhd2/mhd_post_result.h @@ -0,0 +1,110 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd 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. + + GNU libmicrohttpd 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 src/mhd2/mhd_post_result.h + * @brief The definition of enum for POST parsing result + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_POST_RESULT_H +#define MHD_POST_RESULT_H 1 + +#include "mhd_sys_options.h" + +// TODO: describe and copy to the main header + +#ifndef MHD_POST_PARSE_RESULT_DEFINED + +enum MHD_FIXED_ENUM_MHD_SET_ MHD_PostParseResult +{ + /** + * The POST data parse successfully and completely. + */ + MHD_POST_PARSE_RES_OK = 0 + , + /** + * The POST request has no content or zero-length content. + */ + MHD_POST_PARSE_RES_REQUEST_EMPTY = 1 + , + /** + * Parsing of the POST data is incomplete because client used incorrect + * format of POST encoding. + * Some POST data is available or has been provided via callback. + */ + MHD_POST_PARSE_RES_PARTIAL_INVALID_POST_FORMAT = 2 + , + /** + * The POST data cannot be parsed completely because the stream has + * no free pool memory. + * Some POST data may be parsed. + */ + MHD_POST_PARSE_RES_FAILED_NO_POOL_MEM = 60 + , + /** + * The POST data cannot be parsed completely because no "large shared buffer" + * space is available. + * Some POST data may be parsed. + */ + MHD_POST_PARSE_RES_FAILED_NO_LARGE_BUF_MEM = 61 + , + /** + * The POST data cannot be parsed because 'Content-Type:' is unknown. + */ + MHD_POST_PARSE_RES_FAILED_UNKNOWN_CNTN_TYPE = 80 + , + /** + * The POST data cannot be parsed because 'Content-Type:' header is not set. + */ + MHD_POST_PARSE_RES_FAILED_NO_CNTN_TYPE = 81 + , + /** + * The POST data cannot be parsed because "Content-Type:" request header has + * no "boundary" parameter for "multipart/form-data" + */ + MHD_POST_PARSE_RES_FAILED_HEADER_NO_BOUNDARY = 82 + , + /** + * The POST data cannot be parsed because "Content-Type: multipart/form-data" + * request header is misformed + */ + MHD_POST_PARSE_RES_FAILED_HEADER_MISFORMED = 83 + , + /** + * The application set POST encoding to "multipart/form-data", but the request + * has no "Content-Type: multipart/form-data" header which is required + * to find "boundary" used in this encoding + */ + MHD_POST_PARSE_RES_FAILED_HEADER_NOT_MPART = 84 + , + /** + * The POST data cannot be parsed because client used incorrect format + * of POST encoding. + */ + MHD_POST_PARSE_RES_FAILED_INVALID_POST_FORMAT = 90 + +}; + +#define MHD_POST_PARSE_RESULT_DEFINED 1 +#endif /* ! MHD_POST_PARSE_RESULT_DEFINED */ + + +#endif /* ! MHD_POST_RESULT_H */ diff --git a/src/mhd2/mhd_postfield.h b/src/mhd2/mhd_postfield.h new file mode 100644 index 00000000..ba1facfa --- /dev/null +++ b/src/mhd2/mhd_postfield.h @@ -0,0 +1,73 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd 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. + + GNU libmicrohttpd 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 src/mhd2/mhd_postfield.h + * @brief The definition of the struct MHD_PostField + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_POSTFIELD_H +#define MHD_POSTFIELD_H 1 + +#include "mhd_sys_options.h" + +#ifndef MHD_POSTFILED_DEFINED + +struct MHD_PostField +{ + /** + * The name of the field + */ + struct MHD_String name; + /** + * The field data + * If not set or defined then to C string is NULL. + * If set to empty string then pointer to C string not NULL, + * but the length is zero. + */ + struct MHD_StringNullable value; + /** + * The filename if provided (only for "multipart/form-data") + * If not set or defined then to C string is NULL. + * If set to empty string then pointer to C string not NULL, + * but the length is zero. + */ + struct MHD_StringNullable filename; + /** + * The Content-Type if provided (only for "multipart/form-data") + * If not set or defined then to C string is NULL. + * If set to empty string then pointer to C string not NULL, + * but the length is zero. + */ + struct MHD_StringNullable content_type; + /** + * The Transfer-Encoding if provided (only for "multipart/form-data") + * If not set or defined then to C string is NULL. + * If set to empty string then pointer to C string not NULL, + * but the length is zero. + */ + struct MHD_StringNullable transfer_encoding; +}; + +#define MHD_POSTFILED_DEFINED 1 +#endif /* ! MHD_POSTFILED_DEFINED */ + +#endif /* ! MHD_POSTFIELD_H */ diff --git a/src/mhd2/mhd_request.h b/src/mhd2/mhd_request.h index c6aa7f34..cb16559f 100644 --- a/src/mhd2/mhd_request.h +++ b/src/mhd2/mhd_request.h @@ -41,6 +41,11 @@ #include "mhd_action.h" #include "mhd_buffer.h" +#ifdef HAVE_POST_PARSER +# include "mhd_postfield.h" +# include "mhd_post_parser.h" +#endif + /** * The action set by the application @@ -200,6 +205,33 @@ struct mhd_RequestField mhd_DLINKEDL_LIST_DEF (mhd_RequestField); +#ifdef HAVE_POST_PARSER + +struct mhd_RequestPostField; /* forward declarations */ + +mhd_DLINKEDL_LINKS_DEF (mhd_RequestPostField); + +/** + * The data for POST request fields + */ +struct mhd_RequestPostField +{ + /** + * The field data + */ + struct MHD_PostField field; + + /** + * Headers are kept in a double-linked list. + */ + mhd_DLNKDL_LINKS (mhd_RequestPostField,post_fields); +}; + +mhd_DLINKEDL_LIST_DEF (mhd_RequestPostField); + + +#endif /* HAVE_POST_PARSER */ + /** * The request content data @@ -219,17 +251,30 @@ struct mhd_ReqContentData uint_fast64_t cntn_size; /** - * The size of the received content + * The size of the received content. + * Excluding chunked encoding framing. */ uint_fast64_t recv_size; /** - * The size of the processed content + * The size of the processed content. + * Excluding chunked encoding framing. */ uint_fast64_t proc_size; }; +union mhd_ReqContentParsingData +{ +#ifdef HAVE_POST_PARSER + /** + * The POST parsing data + */ + struct mhd_PostParserData post; +#endif /* HAVE_POST_PARSER */ + // TODO: move "raw" upload processing data here +}; + /** * Request-specific values. * @@ -242,6 +287,13 @@ struct MHD_Request */ mhd_DLNKDL_LIST (mhd_RequestField,fields); +#ifdef HAVE_POST_PARSER + /** + * Linked list of parsed POST fields. + */ + mhd_DLNKDL_LIST (mhd_RequestPostField,post_fields); +#endif /* HAVE_POST_PARSER */ + /** * The action set by the application */ @@ -257,6 +309,11 @@ struct MHD_Request */ bool too_large; + /** + * Upload processing data + */ + union mhd_ReqContentParsingData u_proc; + /** * HTTP version string (i.e. http/1.1). Allocated * in pool. diff --git a/src/mhd2/mhd_str.c b/src/mhd2/mhd_str.c index 05eaebc7..fdf6de0d 100644 --- a/src/mhd2/mhd_str.c +++ b/src/mhd2/mhd_str.c @@ -2003,6 +2003,10 @@ mhd_str_equal_caseless_quoted_bin_n (const char *quoted, } +#endif /* DAUTH_SUPPORT */ + +#if defined(DAUTH_SUPPORT) || defined(HAVE_POST_PARSER) + MHD_INTERNAL size_t mhd_str_unquote (const char *quoted, size_t quoted_len, @@ -2027,8 +2031,7 @@ mhd_str_unquote (const char *quoted, return w; } - -#endif /* DAUTH_SUPPORT */ +#endif /* DAUTH_SUPPORT HAVE_POST_PARSER */ #if defined(DAUTH_SUPPORT) || defined(BAUTH_SUPPORT) @@ -2326,3 +2329,214 @@ MHD_DATA_TRUNCATION_RUNTIME_CHECK_RESTORE_ #undef mhd_base64_map_type #endif /* BAUTH_SUPPORT */ + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_str_starts_with_token_opt_param (const struct MHD_String *restrict str, + const struct MHD_String *restrict token) +{ + size_t i; + + mhd_assert (0 != token->len); + mhd_assert (NULL == memchr(token->cstr, '=', token->len)); + mhd_assert (NULL == memchr(token->cstr, ' ', token->len)); + mhd_assert (NULL == memchr(token->cstr, '\t', token->len)); + + if (str->len < token->len) + return false; /* The string is too short to match */ + + if (! mhd_str_equal_caseless_bin_n (str->cstr, + token->cstr, + token->len)) + return false; /* The string does not start with the token */ + + for (i = token->len; i < str->len; ++i) + { + const char c = str->cstr[i]; + if ((' ' == c) || ('\t' == c)) + continue; + if (';' == c) + return true; /* Found the start of the token parameters */ + return false; /* The initial part of the string does not fully match the token */ + } + mhd_assert (0 && "The string should not have whitespace at the end"); + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_(4) +MHD_FN_PAR_OUT_(5) enum mhd_StingStartsWithTokenResult +mhd_str_starts_with_token_req_param ( + const struct MHD_String *restrict str, + const struct MHD_String *restrict token, + const struct MHD_String *restrict par, + struct mhd_BufferConst *restrict par_value, + bool *restrict par_value_needs_unquote) +{ + size_t i; + const char *const restrict cstr = str->cstr; + bool token_found; + bool param_found; + + mhd_assert (0 != token->len); + mhd_assert (NULL == memchr(token->cstr, '=', token->len)); + mhd_assert (NULL == memchr(token->cstr, ' ', token->len)); + mhd_assert (NULL == memchr(token->cstr, '\t', token->len)); + mhd_assert (NULL == memchr(par->cstr, '=', par->len)); + mhd_assert (NULL == memchr(par->cstr, ' ', par->len)); + mhd_assert (NULL == memchr(par->cstr, '\t', par->len)); + + par_value->data = NULL; + par_value->size = 0; + + if (str->len < token->len) + return false; /* The string is too short to match */ + + if (! mhd_str_equal_caseless_bin_n (cstr, + token->cstr, + token->len)) + return mhd_STR_STARTS_W_TOKEN_NO_TOKEN; /* The string does not start with the token */ + token_found = false; + param_found = false; + + i = token->len; + do + { + /* Find start of the next parameter */ + for ((void) 0; i < str->len; ++i) + { + const char c = cstr[i]; + if ((' ' == c) || ('\t' == c)) + continue; + if (';' == c) + { + /* Found the start of the next token parameter */ + if (param_found) + return mhd_STR_STARTS_W_TOKEN_HAS_TOKEN; + break; + } + if (',' == c) + return mhd_STR_STARTS_W_TOKEN_HAS_TOKEN; /* Found the start of the next token */ + + if (! token_found) + { + if (i == token->len) + { + /* The initial part of the string does not fully match the token or + formatting is not correct */ + return mhd_STR_STARTS_W_TOKEN_NO_TOKEN; + } + /* The string has garbage after the token and whitespace */ + return mhd_STR_STARTS_W_TOKEN_HAS_TOKEN_BAD_FORMAT; + } + /* The garbage after the parameter */ + return mhd_STR_STARTS_W_TOKEN_HAS_TOKEN_BAD_FORMAT; + } + token_found = true; + + if (i == str->len) + return mhd_STR_STARTS_W_TOKEN_HAS_TOKEN; + + /* 'i' is at the start of the parameter name */ + + if (par->len > str->len - i - 1) + return mhd_STR_STARTS_W_TOKEN_HAS_TOKEN; /* the token is found, but the parameter is not */ + else + { /* Check the parameter */ + bool val_needs_unquote; + size_t j; + const char *const prm_str = cstr + i; + + for (j = 0; j < par->len; ++j) + if (! charsequalcaseless (prm_str[j], + par->cstr[j])) + break; + i += j; + mhd_assert (str->len > i); + if ((j == par->len) && + ('=' == cstr[i])) + { + /* The parameter name matches required parameter */ + param_found = true; + par_value->data = cstr + i + 1; + } + else + { + /* i points to the char in the parameter name */ + while ('=' != cstr[i]) + { + if ((';' == cstr[i]) /* end of the parameter */ + || (',' == cstr[i]) /* end of the token */ + || (str->len == ++i)) /* end of the field string */ + { + /* parameter without the value */ + return mhd_STR_STARTS_W_TOKEN_HAS_TOKEN_BAD_FORMAT; + } + } + } + mhd_assert (str->len > i); + mhd_assert ('=' == cstr[i]); + + /* 'i' points to '=' between parameter name and parameter value */ + + ++i; /* Advance to the first char in the parameter value */ + if (str->len == i) + return mhd_STR_STARTS_W_TOKEN_HAS_TOKEN; /* Zero-length parameter value */ + + val_needs_unquote = false; + + /* 'i' points to the char after '=' */ + + if ('"' == cstr[i]) + { + /* The value is quoted */ + ++(par_value->data); /* Point to the first quoted char */ + do + { + ++i; /* Advance to the next char */ + if (str->len == i) + return mhd_STR_STARTS_W_TOKEN_HAS_TOKEN_BAD_FORMAT; /* No closing quote */ + if ('\\' == cstr[i]) + { + val_needs_unquote = true; + ++i; /* Skip quoted char */ + if (str->len == i) + return mhd_STR_STARTS_W_TOKEN_HAS_TOKEN_BAD_FORMAT; /* No closing quote */ + } + } while ('"' != cstr[i]); + if (param_found) + { + par_value->size = (size_t) ((cstr + i) - par_value->data); + *par_value_needs_unquote = val_needs_unquote; + } + /* Complete value found */ + /* Check for the garbage data at the end */ + ++i; /* Advance to the next char */ + } + else + { + /* The value is not quoted */ + while ((' ' != cstr[i]) && + ('\t' != cstr[i])) + { + if ((';' == cstr[i]) /* end of the parameter */ + || (',' == cstr[i]) /* end of the token */ + || (str->len == ++i)) /* end of the field string */ + break; + } + /* The end parameter value */ + if (param_found) + { + par_value->size = (size_t) ((cstr + i) - par_value->data); + *par_value_needs_unquote = false; + } + /* Check for the garbage data at the end */ + } + + /* 'i' points to the next char after end of the parameter value */ + } + } while (i < str->len); + + mhd_assert (token_found); + return mhd_STR_STARTS_W_TOKEN_HAS_TOKEN; +} diff --git a/src/mhd2/mhd_str.h b/src/mhd2/mhd_str.h index a1980f6b..b7c07821 100644 --- a/src/mhd2/mhd_str.h +++ b/src/mhd2/mhd_str.h @@ -27,12 +27,19 @@ #define MHD_STR_H 1 #include "mhd_sys_options.h" + #include "sys_base_types.h" #include "sys_bool_type.h" + +#include "mhd_str_types.h" +#include "mhd_buffer.h" + #include "mhd_str_macros.h" + #ifdef MHD_FAVOR_SMALL_CODE # include "mhd_limits.h" #endif + /* * Block of functions/macros that use US-ASCII charset as required by HTTP * standards. Not affected by current locale settings. @@ -682,6 +689,10 @@ mhd_str_equal_caseless_quoted_bin_n (const char *quoted, #define mhd_str_equal_caseless_quoted_s_bin_n(q,l,u) \ mhd_str_equal_caseless_quoted_bin_n (q,l,u,mhd_SSTR_LEN (u)) +#endif /* DAUTH_SUPPORT */ + +#if defined(DAUTH_SUPPORT) || defined(HAVE_POST_PARSER) + /** * Convert string from quoted to unquoted form as specified by * RFC7230#section-3.2.6 and RFC7694#quoted.strings. @@ -702,7 +713,7 @@ mhd_str_unquote (const char *quoted, size_t quoted_len, char *result); -#endif /* DAUTH_SUPPORT */ +#endif /* DAUTH_SUPPORT HAVE_POST_PARSER */ #if defined(DAUTH_SUPPORT) || defined(BAUTH_SUPPORT) @@ -766,4 +777,78 @@ mhd_base64_to_bin_n (const char *base64, #endif /* BAUTH_SUPPORT */ + +/** + * Check whether the given field value string starts with given token. + * Token matched case-insensitive. + * + * Matched considered successful if string has given token at the first + * position. The token may be followed by optional whitespaces and the semicolon + * symbol. The string is not checked after the semicolon (if any). + * + * @param str the string to check + * @param token the token to find, must not be empty + * @return 'true' if match is successful, + * 'false' otherwise + */ +MHD_INTERNAL bool +mhd_str_starts_with_token_opt_param (const struct MHD_String *restrict str, + const struct MHD_String *restrict token) +MHD_FN_PAR_NONNULL_ALL_; + + +/** + * The result of check for token with parameter + */ +enum MHD_FIXED_ENUM_ mhd_StingStartsWithTokenResult +{ + /** + * The string does not start with the specified token + */ + mhd_STR_STARTS_W_TOKEN_NO_TOKEN = 0 + , + /** + * The string has specified token at the initial position + * @note While formatting problems are not detected, it does not guarantee + * that string has a perfect format. + */ + mhd_STR_STARTS_W_TOKEN_HAS_TOKEN = 1 + , + /** + * The string has specified token at the initial position, but broken + * formatting is detected. + */ + mhd_STR_STARTS_W_TOKEN_HAS_TOKEN_BAD_FORMAT = -1 +}; + +/** + * Check whether the given field value string starts with given token, find + * required parameter. + * Token and parameter matched case-insensitive. + * + * Matched considered successful if string has given token at the first + * position. The token should be followed by optional whitespaces and + * one or more parameters, delimited by the semicolon symbol. + * + * @param str the string to check + * @param token the token to find, must not be empty + * @param par the name of the parameter, must not be empty + * @param[out] par_value set to the found parameter value or @a data member + * set to NULL if parameter is not found + * @param par_value_needs_unquote set to 'true' if @a par_value + * needs to be "unquoted" + * @return result of token detection and string parsing; if token is found + * the presence of the required parameter is indicated only + * by @a par_value + * + */ +MHD_INTERNAL enum mhd_StingStartsWithTokenResult +mhd_str_starts_with_token_req_param ( + const struct MHD_String *restrict str, + const struct MHD_String *restrict token, + const struct MHD_String *restrict par, + struct mhd_BufferConst *restrict par_value, + bool *restrict par_value_needs_unquote) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_(4) MHD_FN_PAR_OUT_(5); + #endif /* MHD_STR_H */ diff --git a/src/mhd2/post_parser_funcs.c b/src/mhd2/post_parser_funcs.c new file mode 100644 index 00000000..3e4b5d37 --- /dev/null +++ b/src/mhd2/post_parser_funcs.c @@ -0,0 +1,1165 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd 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. + + GNU libmicrohttpd 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 src/mhd2/post_parser_funcs.c + * @brief The implementation of internal POST parser functions + * @author Karlson2k (Evgeny Grin) + */ + + +#include "mhd_sys_options.h" + +#include "post_parser_funcs.h" + +#include "mhd_post_parser.h" + +#include + +#include "mhd_action.h" +#include "mhd_request.h" +#include "mhd_connection.h" + +#include "mhd_str_macros.h" + +#include "mhd_str.h" +#include "daemon_logger.h" +#include "stream_funcs.h" +#include "stream_process_request.h" + +#include "daemon_funcs.h" +#include "request_get_value.h" + +/** + * The result of 'multipart/form-data' processing + */ +enum MHD_FIXED_ENUM_ mhd_ProcessMPartResult +{ + /** + * Sting processed successfully, boundary detected + */ + mhd_PROC_MPART_OK = 0 + , + /** + * Error processing string, the error result is set + */ + mhd_PROC_MPART_ERROR_SET + , + /** + * The string is not 'multipart/form-data' header + */ + mhd_PROC_MPART_NO_MPART +}; + + +/** + * Process 'Content-Type:' header value as 'multipart/form-data' data to + * prepare POST parsing data, including setting correct 'boundary' value + * @param c the stream to use + * @param h_cnt_tp the 'Content-Type:' header value string + * @return 'mhd_PROC_MPART_OK' if processed successfully and boundary has been + * detected and set, + * 'mhd_PROC_MPART_ERROR_SET' is has some error in processing which + * resulted in specific error set in + * the stream, + * 'mhd_PROC_MPART_NO_MPART' is string is not 'multipart/form-data' data + */ +static MHD_FN_PAR_NONNULL_ALL_ enum mhd_MPartDetectResult +process_mpart_header (struct MHD_Connection *restrict c, + const struct MHD_String *restrict h_cnt_tp) +{ + static const struct MHD_String mpart_token = + mhd_MSTR_INIT ("multipart/form-data"); + static const struct MHD_String mpart_bound_par = + mhd_MSTR_INIT ("boundary"); + struct mhd_BufferConst mpart_bound; + bool mpart_bound_quoted; + enum mhd_StingStartsWithTokenResult res; + + mhd_assert (NULL != h_cnt_tp->cstr); + + res = mhd_str_starts_with_token_req_param (h_cnt_tp, + &mpart_token, + &mpart_bound_par, + &mpart_bound, + &mpart_bound_quoted); + + if (mhd_STR_STARTS_W_TOKEN_NO_TOKEN == res) + return mhd_PROC_MPART_NO_MPART; + + if (mhd_STR_STARTS_W_TOKEN_HAS_TOKEN_BAD_FORMAT == res) + { + mhd_LOG_PRINT (c->daemon, \ + MHD_SC_REQ_POST_PARSE_FAILED_HEADER_MISFORMED, \ + mhd_LOG_FMT ("The request POST data cannot be parsed " \ + "because 'Content-Type: " \ + "multipart/form-data' header is " \ + "misformed: %.*s%s"), \ + (int) ((h_cnt_tp->len <= 127) ? h_cnt_tp->len : 127), + h_cnt_tp->cstr, + (h_cnt_tp->len <= 127) ? "" : "..."); + c->rq.u_proc.post.parse_result = + MHD_POST_PARSE_RES_FAILED_HEADER_MISFORMED; + return mhd_PROC_MPART_ERROR_SET; + } + + mhd_assert (mhd_STR_STARTS_W_TOKEN_HAS_TOKEN == res); + + if (0 != mpart_bound.size) + { + mhd_LOG_MSG (c->daemon, \ + MHD_SC_REQ_POST_PARSE_FAILED_HEADER_NO_BOUNDARY, \ + "The request POST data cannot be parsed because " \ + "'Content-Type: multipart/form-data' header has " \ + "no 'boundary' parameter value."); + c->rq.u_proc.post.parse_result = + MHD_POST_PARSE_RES_FAILED_HEADER_NO_BOUNDARY; + return mhd_PROC_MPART_ERROR_SET; + } + + mhd_assert (NULL != mpart_bound.data); + + if (! mpart_bound_quoted) + { + c->rq.u_proc.post.enc = MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA; + c->rq.u_proc.post.e_d.m_form.bound = mpart_bound; + } + else + { + char *buf; + + mhd_assert (2 <= mpart_bound.size); /* At least one char and at least one '\' */ + + buf = mhd_stream_alloc_memory (c, mpart_bound.size); + if (NULL == buf) + { + /* It is very low probability that pool would not have memory just + * to held the small boundary string. While it could be possible + * to allocate memory from "large buffer", it would over-complicate + * code here and at freeing part. */ + mhd_LOG_MSG (c->daemon, MHD_SC_REQ_POST_PARSE_FAILED_NO_POOL_MEM, \ + "The request POST data cannot be parsed because " \ + "there is not enough pool memory."); + c->rq.u_proc.post.parse_result = MHD_POST_PARSE_RES_FAILED_NO_POOL_MEM; + return mhd_PROC_MPART_ERROR_SET; + } + c->rq.u_proc.post.enc = MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA; + c->rq.u_proc.post.e_d.m_form.bound.size = + mhd_str_unquote (mpart_bound.data, + mpart_bound.size, + buf); + mhd_assert (0 != c->rq.u_proc.post.e_d.m_form.bound.size); + } + return mhd_PROC_MPART_OK; +} + + +/** + * Detect used POST encoding and 'boundary' for 'multipart/form-data'. + * @param c the stream to use + * @return 'true' if detected successfully, + * 'false' if POST encoding cannot be detected + */ +static MHD_FN_PAR_NONNULL_(1) bool +detect_post_enc (struct MHD_Connection *restrict c) +{ + const struct MHD_StringNullable *h_cnt_tp; + + mhd_assert (MHD_CONNECTION_BODY_RECEIVING > c->state); + + h_cnt_tp = mhd_request_get_value_st (&(c->rq), + MHD_VK_HEADER, + MHD_HTTP_HEADER_CONTENT_TYPE); + if (NULL == h_cnt_tp) + { + mhd_LOG_MSG (c->daemon, MHD_SC_REQ_POST_PARSE_FAILED_NO_CNTN_TYPE, \ + "The request POST data cannot be parsed because " \ + "the request has no 'Content-Type:' header and no " \ + "explicit POST encoding is set."); + c->rq.u_proc.post.parse_result = MHD_POST_PARSE_RES_FAILED_NO_CNTN_TYPE; + return false; /* The "Content-Type:" is not defined by the client */ + } + + mhd_assert (NULL != h_cnt_tp->cstr); + + if (mhd_str_equal_caseless_n_st ("application/x-www-form-urlencoded", + h_cnt_tp->cstr, + h_cnt_tp->len)) + { + c->rq.u_proc.post.enc = MHD_HTTP_POST_ENCODING_FORM_URLENCODED; + return true; + } + + if (1) + { + enum mhd_MPartDetectResult res; + + res = process_mpart_header (c, + (const struct MHD_String *) (const void *) + h_cnt_tp); + + if (mhd_PROC_MPART_OK == res) + return true; + + if (mhd_PROC_MPART_ERROR_SET == res) + return false; + + mhd_assert (mhd_PROC_MPART_NO_MPART == res); + } + + if (1) + { + static const struct MHD_String txt_tkn = mhd_MSTR_INIT("text/plain"); + + if (mhd_str_starts_with_token_opt_param (h_cnt_tp, + &txt_tkn)) + { + c->rq.u_proc.post.enc = MHD_HTTP_POST_ENCODING_TEXT_PLAIN; + return true; + } + } + mhd_LOG_MSG (c->daemon, \ + MHD_SC_REQ_POST_PARSE_FAILED_UNKNOWN_CNTN_TYPE, \ + "The request POST data cannot be parsed because " \ + "'Content-Type' header value is unknown or unsupported."); + c->rq.u_proc.post.parse_result = + MHD_POST_PARSE_RES_FAILED_UNKNOWN_CNTN_TYPE; + return false; +} + +/** + * Detect 'boundary' for 'multipart/form-data' POST encoding. + * @param c the stream to use + * @return 'true' if succeed, + * 'false' if failed and error result is set + */ +static MHD_FN_PAR_NONNULL_ALL_ bool +detect_mpart_boundary_from_the_header (struct MHD_Connection *restrict c) +{ + const struct MHD_StringNullable *h_cnt_tp; + + mhd_assert (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA == \ + c->rq.app_act.head_act.data.post_parse.enc); + + h_cnt_tp = mhd_request_get_value_st (&(c->rq), + MHD_VK_HEADER, + MHD_HTTP_HEADER_CONTENT_TYPE); + if (NULL == h_cnt_tp) + { + mhd_LOG_MSG (c->daemon, MHD_SC_REQ_POST_PARSE_FAILED_NO_CNTN_TYPE, \ + "The request POST data cannot be parsed because " \ + "'multipart/form-data' requires 'boundary' parameter, but " \ + "the request has no 'Content-Type:' header."); + c->rq.u_proc.post.parse_result = MHD_POST_PARSE_RES_FAILED_NO_CNTN_TYPE; + return false; + } + + mhd_assert (NULL != h_cnt_tp->cstr); + + enum mhd_MPartDetectResult res; + + res = process_mpart_header (c, + (const struct MHD_String *) (const void *) + h_cnt_tp); + + if (mhd_PROC_MPART_OK == res) + return true; + + if (mhd_PROC_MPART_NO_MPART == res) + { + mhd_LOG_MSG (c->daemon, MHD_SC_REQ_POST_PARSE_FAILED_HEADER_NOT_MPART, \ + "The request POST data cannot be parsed because " \ + "'multipart/form-data' requires 'boundary' parameter, but " \ + "the request has no 'Content-Type: multipart/form-data' " \ + "header."); + c->rq.u_proc.post.parse_result = MHD_POST_PARSE_RES_FAILED_HEADER_NOT_MPART; + return false; + } + + mhd_assert (mhd_PROC_MPART_ERROR_SET == res); + return false; +} + +static MHD_FN_PAR_NONNULL_(1) void +reset_text_parse_field_data (struct mhd_PostParserData *pdata) +{ + mhd_assert (MHD_HTTP_POST_ENCODING_TEXT_PLAIN == pdata->enc); + memset (&(pdata->e_d.text), 0, sizeof(pdata->e_d.text)); +#ifndef HAVE_NULL_PTR_ALL_ZEROS + pdata->e_d.text.name_ptr = NULL; + pdata->e_d.text.value_ptr = NULL; +#endif + pdata->field_start = 0; +} + +/** + * Finish initialisation of data for POST parsing + * @param c the stream to use + */ +static MHD_FN_PAR_NONNULL_(1) void +init_post_parse_data (struct MHD_Connection *restrict c) +{ + struct mhd_PostParserData *const pdata = + &(c->rq.u_proc.post); + + mhd_assert (mhd_ACTION_POST_PARSE == c->rq.app_act.head_act.act); + mhd_assert (MHD_HTTP_POST_ENCODING_OTHER != \ + c->rq.u_proc.post.enc); + + pdata->lbuf_left = c->rq.app_act.head_act.data.post_parse.buffer_size; + + switch (pdata->enc) + { + case MHD_HTTP_POST_ENCODING_FORM_URLENCODED: + // TODO: + break; + case MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA: + // TODO + break; + case MHD_HTTP_POST_ENCODING_TEXT_PLAIN: + reset_text_parse_field_data (pdata); + break; + case MHD_HTTP_POST_ENCODING_OTHER: + default: + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + } +} + +MHD_INTERNAL MHD_FN_PAR_NONNULL_(1) bool +mhd_stream_prepare_for_post_parse (struct MHD_Connection *restrict c) +{ + mhd_assert (mhd_ACTION_POST_PARSE == c->rq.app_act.head_act.act); + if (MHD_HTTP_POST_ENCODING_OTHER == + c->rq.app_act.head_act.data.post_parse.enc) + { + if (! detect_post_enc (c)) + { + mhd_assert (MHD_POST_PARSE_RES_OK != c->rq.u_proc.post.parse_result); + c->discard_request = true; + c->state = MHD_CONNECTION_FULL_REQ_RECEIVED; + return false; + } + } + else if (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA == + c->rq.app_act.head_act.data.post_parse.enc) + { + if (! detect_mpart_boundary_from_the_header (c)) + { + mhd_assert (MHD_POST_PARSE_RES_OK != c->rq.u_proc.post.parse_result); + c->discard_request = true; + c->state = MHD_CONNECTION_FULL_REQ_RECEIVED; + return false; + } + } + + mhd_assert (MHD_HTTP_POST_ENCODING_OTHER != \ + c->rq.u_proc.post.enc); + mhd_assert ((MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA != \ + c->rq.u_proc.post.enc) || \ + (0 != c->rq.u_proc.post.e_d.m_form.bound.size)); + + init_post_parse_data (c); + + return true; +} + +/** + * Allocate memory from "large shared buffer" for POST parsing + * @param c the stream to use + * @param alloc_size the size to allocate + * @param[out] buf the buffer to allocate + * @return 'true' if succeed, + * 'false' otherwise + */ +static MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_(3) bool +get_lbuf_fixed_size (struct MHD_Connection *restrict c, + size_t alloc_size, + struct mhd_Buffer *restrict buf) +{ + mhd_assert (mhd_ACTION_POST_PARSE == c->rq.app_act.head_act.act); + + if (alloc_size > c->rq.u_proc.post.lbuf_left) + return NULL; + + if (! mhd_daemon_get_lbuf (c->daemon, + alloc_size, + buf)) + return false; + + c->rq.u_proc.post.lbuf_left -= alloc_size; + return true; +} + +/** + * Grow the allocated memory from "large shared buffer" for POST parsing + * @param c the stream to use + * @param grow_size the size to grow + * @param[in,out] buf the buffer to grow + * @return 'true' if succeed, + * 'false' otherwise + */ +static MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_(3) bool +grow_lbuf_fixed_size (struct MHD_Connection *restrict c, + size_t grow_size, + struct mhd_Buffer *restrict buf) +{ + mhd_assert (mhd_ACTION_POST_PARSE == c->rq.app_act.head_act.act); + + if (grow_size > c->rq.u_proc.post.lbuf_left) + return NULL; + + if (! mhd_daemon_grow_lbuf (c->daemon, + grow_size, + buf)) + return false; + + c->rq.u_proc.post.lbuf_left -= grow_size; + return true; +} + +/** + * Add parsed POST field to the list of request's fields + * @param c the stream to use + * @param name the name of the field + * @param filename the filename of the filed + * @param content_type the "Content-Type:" of the field + * @param transfer_encoding the "Transfer-Encoding:" of the field + * @param value the value of the field + * @return 'true' if succeed, + * 'false' if memory allocation failed (no pool memory in the stream) + */ +static MHD_FN_PAR_NONNULL_ALL_ bool +add_parsed_post_field (struct MHD_Connection *restrict c, + struct MHD_String *restrict name, + struct MHD_StringNullable *restrict filename, + struct MHD_StringNullable *restrict content_type, + struct MHD_StringNullable *restrict transfer_encoding, + struct MHD_StringNullable *restrict value) +{ + struct mhd_RequestPostField *pfield; + + mhd_assert (NULL != name->cstr); + mhd_assert ((NULL != filename->cstr) || (0 == filename->len)); + mhd_assert ((NULL != content_type->cstr) || (0 == content_type->len)); + mhd_assert ((NULL != transfer_encoding->cstr) || \ + (0 == transfer_encoding->len)); + mhd_assert ((NULL != value->cstr) || (0 == value->len)); + + pfield = (struct mhd_RequestPostField *) + mhd_stream_alloc_memory (c, + sizeof (struct mhd_RequestPostField)); + if (NULL == pfield) + return false; + + pfield->field.name = *name; + pfield->field.value = *value; + pfield->field.filename = *filename; + pfield->field.content_type = *content_type; + pfield->field.transfer_encoding = *transfer_encoding; + + mhd_DLINKEDL_INIT_LINKS (pfield, post_fields); + + mhd_DLINKEDL_INS_FIRST (&(c->rq), pfield, post_fields); + + return true; +} + +MHD_static_inline_ MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_(1) +MHD_FN_PAR_OUT_(10) MHD_FN_PAR_OUT_(11) +MHD_FN_PAR_OUT_(12) MHD_FN_PAR_OUT_(13) void +make_post_strings_from_buf_and_indices (const char *restrict buf, + size_t name_start, + size_t name_len, + size_t filename_start, + size_t filename_len, + size_t cntn_type_start, + size_t cntn_type_len, + size_t enc_start, + size_t enc_len, + struct MHD_String *name, + struct MHD_StringNullable *filename, + struct MHD_StringNullable *content_type, + struct MHD_StringNullable *encoding) +{ + name->len = name_len; + name->cstr = buf + name_start; + + if (0 != filename_start) + { + filename->len = filename_len; + filename->cstr = buf + filename_start; + } + else + { + filename->len = 0; + filename->cstr = NULL; + } + if (0 != cntn_type_start) + { + content_type->len = cntn_type_len; + content_type->cstr = buf + cntn_type_start; + } + else + { + content_type->len = 0; + content_type->cstr = NULL; + } + if (0 != enc_start) + { + encoding->len = enc_len; + encoding->cstr = buf + enc_start; + } + else + { + encoding->len = 0; + encoding->cstr = NULL; + } +} + + +/** + * Process new full parsed POST filed + * @param c the stream to use + * @param buf the buffer, where the data is + * @param pnext_pos the pointer to variable holding index on the next position + * to be parsed + * @param pdata_size the pointer to variable holding the size of the data + * @param field_start the start of the current field in the @a buf + * @param name_start the start of the name of the filed in the @a buf + * @param name_len the length of the name, not including mandatory terminating + * zero + * @param filename_start the start of the filename of the field in the @a buf, + * zero if no filename is provided + * @param filename_len the length of the filename, not including mandatory + * terminating zero + * @param cntn_type_start the start of the Content-Type value of the field in + * the @a buf, zero if no Content-Type is provided + * @param cntn_type_len the length of the Content-Type value, not including + * mandatory terminating zero + * @param enc_start the start of the Content-Encoding value of the field in + * the @a buf, zero if no Content-Encoding is provided + * @param enc_len the length of the Content-Encoding value, not including + * mandatory terminating zero + * @param value_start the start of the field value in the @a buf, zero if + * no value is provided + * @param value_len the length of the field value, not including mandatory + * terminating zero + * @return 'true' if stream state has been changed, + * 'false' to continue parsing + */ +static MHD_FN_PAR_NONNULL_ALL_ bool +process_complete_field_all (struct MHD_Connection *restrict c, + const char *restrict buf, + size_t *restrict pnext_pos, + size_t *restrict pdata_size, + size_t field_start, + size_t name_start, + size_t name_len, + size_t filename_start, + size_t filename_len, + size_t cntn_type_start, + size_t cntn_type_len, + size_t enc_start, + size_t enc_len, + size_t value_start, + size_t value_len) +{ + struct mhd_PostParseActionData *const p_par = + &(c->rq.app_act.head_act.data.post_parse); + struct mhd_PostParserData *const p_data = &(c->rq.u_proc.post); + + struct MHD_String name; + struct MHD_StringNullable filename; + struct MHD_StringNullable content_type; + struct MHD_StringNullable encoding; + struct MHD_StringNullable value; + + mhd_assert (mhd_ACTION_POST_PARSE == c->rq.app_act.head_act.act); + + mhd_assert ((0 == filename_start) || \ + (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA == p_data->enc)); + mhd_assert ((0 == cntn_type_start) || \ + (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA == p_data->enc)); + mhd_assert ((0 == enc_start) || \ + (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA == p_data->enc)); + + mhd_assert (*pnext_pos < *pdata_size); + mhd_assert (value_start + value_len < *pnext_pos); + mhd_assert ((name_start + name_len < value_start) || \ + (0 == value_start)); + mhd_assert (name_start + name_len < *pnext_pos); + mhd_assert ((filename_start + filename_len < value_start) || \ + (0 == value_start)); + mhd_assert (filename_start + filename_len < *pnext_pos); + mhd_assert ((cntn_type_start + cntn_type_len < value_start) || \ + (0 == value_start)); + mhd_assert (cntn_type_start + cntn_type_len < *pnext_pos); + mhd_assert ((enc_start + enc_len < value_start) || \ + (0 == value_start)); + mhd_assert (enc_start + enc_len < *pnext_pos); + mhd_assert (field_start <= name_start); + mhd_assert ((field_start <= filename_start) || (0 == filename_start)); + mhd_assert ((field_start <= cntn_type_start) || (0 == cntn_type_start)); + mhd_assert ((field_start <= enc_start) || (0 == enc_start)); + mhd_assert ((field_start <= value_start) || (0 == value_start)); + mhd_assert ((0 != filename_start) || (0 == filename_len)); + mhd_assert ((0 != cntn_type_start) || (0 == cntn_type_len)); + mhd_assert ((0 != enc_start) || (0 == enc_len)); + mhd_assert ((0 != value_start) || (0 == value_len)); + + p_data->some_data_provided = true; + + make_post_strings_from_buf_and_indices (buf, + name_start, + name_len, + filename_start, + filename_len, + cntn_type_start, + cntn_type_len, + enc_start, + enc_len, + &name, + &filename, + &content_type, + &encoding); + + if (NULL != p_par->stream_reader) + { + if ((p_par->auto_stream_size < (*pnext_pos - field_start)) + || (0 != p_data->value_off)) + { + bool res; + struct mhd_PostParseActionData *act; + const size_t field_size = *pnext_pos - field_start; + + act = p_par->stream_reader (&(c->rq), + p_par->reader_cls, + &name, + &filename, + &content_type, + &encoding, + value_len, + buf + value_start, + p_data->value_off, + MHD_YES); + res = mhd_stream_process_upload_action (c, act, false); + if (c->suspended) + return true; + p_data->value_off = 0; + if (*pdata_size > *pnext_pos) + { + memmove (buf + field_start, + buf + *pnext_pos, + *pdata_size - *pnext_pos); + } + *pnext_pos -= field_size; + *pdata_size -= field_size; + return res; + } + } + + if (0 != value_start) + { + value.len = value_len; + value.cstr = buf + value_start; + } + + if (! add_parsed_post_field (c, + name, + filename, + content_type, + encoding, + value)) + { + c->discard_request = true; + c->state = MHD_CONNECTION_FULL_REQ_RECEIVED; + mhd_LOG_MSG (c->daemon, MHD_SC_REQ_POST_PARSE_FAILED_NO_POOL_MEM, \ + "The request POST data cannot be parsed completely " \ + "because there is not enough pool memory."); + c->rq.u_proc.post.parse_result = MHD_POST_PARSE_RES_FAILED_NO_POOL_MEM; + return true; + } + + return false; /* Continue parsing */ +} + + +/** + * Process new full parsed POST filed + * @param c the stream to use + * @param buf the buffer, where the data is + * @param pnext_pos the pointer to variable holding index on the next position + * to be parsed + * @param pdata_size the pointer to variable holding the size of the data + * @param field_start the start of the current field in the @a buf + * @param name_start the start of the name of the filed in the @a buf + * @param name_len the length of the name, not including mandatory terminating + * zero + * @param value_start the start of the field value in the @a buf, zero if + * no value is provided + * @param value_len the length of the field value, not including mandatory + * terminating zero + * @return 'true' if stream state has been changed, + * 'false' to continue parsing + */ +static MHD_FN_PAR_NONNULL_ALL_ bool +process_complete_field (struct MHD_Connection *restrict c, + const char *restrict buf, + size_t *restrict *pnext_pos, + size_t *restrict *pdata_size, + size_t field_start, + size_t name_start, + size_t name_len, + size_t value_start, + size_t value_len) +{ + mhd_assert (value_start + value_len < *pnext_pos); + mhd_assert ((name_start + name_len < value_start) || \ + (0 == value_start)); + mhd_assert (name_start + name_len < *pnext_pos); + mhd_assert (field_start <= name_start); + mhd_assert ((field_start <= value_start) || (0 == value_start)); + + mhd_assert (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA != \ + c->rq.u_proc.post.enc); + + return process_complete_field_all (c, + buf, + pnext_pos, + pdata_size, + field_start, + name_start, + name_len, + 0, 0, 0, 0, 0, 0, + value_start, + value_len); +} + + +static MHD_FN_PAR_NONNULL_ALL_ bool +process_partial_value_all (struct MHD_Connection *restrict c, + const char *restrict buf, + size_t *restrict pnext_pos, + size_t *restrict pdata_size, + size_t field_start, + size_t name_start, + size_t name_len, + size_t filename_start, + size_t filename_len, + size_t cntn_type_start, + size_t cntn_type_len, + size_t enc_start, + size_t enc_len, + size_t part_value_start, + size_t part_value_len) +{ + struct mhd_PostParseActionData *const p_par = + &(c->rq.app_act.head_act.data.post_parse); + struct mhd_PostParserData *const p_data = &(c->rq.u_proc.post); + + mhd_assert (*pnext_pos < *pdata_size); + mhd_assert (part_value_start + part_value_len == *pnext_pos); + mhd_assert (0 != part_value_start); + mhd_assert (name_start + name_len < *pnext_pos); + mhd_assert (filename_start + filename_len < part_value_start); + mhd_assert (filename_start + filename_len < *pnext_pos); + mhd_assert (cntn_type_start + cntn_type_len < part_value_start); + mhd_assert (cntn_type_start + cntn_type_len < *pnext_pos); + mhd_assert (enc_start + enc_len < part_value_start); + mhd_assert (enc_start + enc_len < *pnext_pos); + mhd_assert ((0 != filename_start) || (0 == filename_len)); + mhd_assert ((0 != cntn_type_start) || (0 == cntn_type_len)); + mhd_assert ((0 != enc_start) || (0 == enc_len)); + + p_data->some_data_provided = true; + + if (NULL == p_par->stream_reader) + return false; /* Continue parsing */ + + if ((p_par->auto_stream_size < (*pnext_pos - field_start)) + || (0 != p_data->value_off)) + { + struct MHD_String name; + struct MHD_StringNullable filename; + struct MHD_StringNullable content_type; + struct MHD_StringNullable encoding; + struct MHD_StringNullable value; + struct mhd_PostParseActionData *act; + bool res; + + make_post_strings_from_buf_and_indices (buf, + name_start, + name_len, + filename_start, + filename_len, + cntn_type_start, + cntn_type_len, + enc_start, + enc_len, + &name, + &filename, + &content_type, + &encoding); + + act = p_par->stream_reader (&(c->rq), + p_par->reader_cls, + &name, + &filename, + &content_type, + &encoding, + part_value_len, + buf + part_value_start, + p_data->value_off, + MHD_NO); + res = mhd_stream_process_upload_action(c, act, false); + if (c->suspended) + return true; + + p_data->value_off += part_value_len; + if (*pdata_size > *pnext_pos) + { + memmove (buf + part_value_start, + buf + part_value_start + part_value_len, + part_value_len); + } + *pnext_pos -= part_value_len; + *pdata_size -= part_value_len; + return res; + } + return false; /* Continue parsing */ + + +static MHD_FN_PAR_NONNULL_ALL_ bool +process_partial_value (struct MHD_Connection *restrict c, + const char *restrict buf, + size_t *restrict *pnext_pos, + size_t *restrict *pdata_size, + size_t field_start, + size_t name_start, + size_t name_len, + size_t part_value_start, + size_t part_value_len) +{ + mhd_assert (part_value_start + part_value_len < *pnext_pos); + mhd_assert (name_start + name_len < part_value_start); + mhd_assert (0 != part_value_start); + mhd_assert (name_start + name_len < *pnext_pos); + + mhd_assert (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA != \ + c->rq.u_proc.post.enc); + + return process_partial_value_all (c, + buf, + pnext_pos, + pdata_size, + field_start, + name_start, + name_len, + 0, 0, 0, 0, 0, 0, + part_value_start, + part_value_len); +} + +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_(2) MHD_FN_PAR_INOUT_(3) bool +parse_post_text (struct MHD_Connection *restrict c, + size_t *restrict pdata_size, + char *restrict buf) +{ + const int discp_lvl = c->daemon->req_cfg.strictnees; + /* Treat bare LF as the end of the line. + The same logic used here as for parsing HTTP headers. + Bare LF is processed as the end of the line or rejected as broken + request. */ + const bool bare_lf_as_crlf = mhd_ALLOW_BARE_LF_AS_CRLF (discp_lvl); + struct mhd_PostParserData *const p_data = &(c->rq.u_proc.post); + struct mhd_PostParserTextData *const tf = &(p_data->e_d.text); /**< the current "text" field */ + size_t i; + bool enc_broken; + + mhd_assert (MHD_POST_PARSE_RES_OK == c->rq.u_proc.post.parse_result); + mhd_assert (! c->discard_request); + mhd_assert (p_data->next_parse_pos < *pdata_size); + + enc_broken = false; + i = p_data->next_parse_pos; + while (*pdata_size > i) + { + switch (tf->st) + { + case mhd_POST_TEXT_ST_NOT_STARTED: + mhd_assert (0 == p_data->field_start); + mhd_assert (0 == p_data->value_off); + p_data->field_start = i; + tf->name_idx = i; + tf->st = mhd_POST_TEXT_ST_NAME; + /* Intentional fall-through */ + case mhd_POST_TEXT_ST_NAME: + do /* Fast local loop */ + { + if ('=' == buf[i]) + { + tf->st = mhd_POST_TEXT_ST_AT_EQ; + break; + } + else if ('\r' == buf[i]) + { + tf->st = mhd_POST_TEXT_ST_AT_CR; + break; + } + else if ('\n' == buf[i]) + { + tf->st = mhd_POST_TEXT_ST_AT_LF_BARE; + break; + } + } while (*pdata_size > ++i); + mhd_assert ((*pdata_size == i) || \ + (mhd_POST_TEXT_ST_NAME != tf->st)); + continue; + case mhd_POST_TEXT_ST_AT_EQ: + mhd_assert (i > tf->name_idx); + mhd_assert (0 == tf->name_len); + mhd_assert (0 == tf->value_len); + buf[i] = 0; /* Zero-terminate the name */ + tf->name_len = i - tf->name_idx; + tf->st = mhd_POST_TEXT_ST_EQ_FOUND; + ++i; /* Process the next char */ + continue; + case mhd_POST_TEXT_ST_EQ_FOUND: + mhd_assert (0 == p_data->value_off); + mhd_assert (0 == tf->value_idx); + mhd_assert (0 == tf->value_len); + tf->value_idx = i; + tf->st = mhd_POST_TEXT_ST_VALUE; + /* Intentional fall-through */ + case mhd_POST_TEXT_ST_VALUE: + do /* Fast local loop */ + { + if ('\r' == buf[i]) + { + tf->st = mhd_POST_TEXT_ST_AT_CR; + break; + } + else if ('\n' == buf[i]) + { + tf->st = mhd_POST_TEXT_ST_AT_LF_BARE; + break; + } + } while (*pdata_size > ++i); + mhd_assert ((*pdata_size == i) || \ + (mhd_POST_TEXT_ST_VALUE != tf->st)); + continue; + case mhd_POST_TEXT_ST_AT_LF_BARE: + if (! bare_lf_as_crlf) + { + enc_broken = true; + break; + } + /* Intentional fall-through */ + case mhd_POST_TEXT_ST_AT_CR: + mhd_assert (0 == tf->value_len); + buf[i] = 0; /* Zero-terminate the value (or the name) */ + if (0 != tf->value_idx) + tf->value_len = i - tf->value_idx; + else + tf->name_len = i - tf->name_idx; + if (mhd_POST_TEXT_ST_AT_LF_BARE == tf->st) + tf->st = mhd_POST_TEXT_ST_FULL_LINE_FOUND; + else + { + tf->st = mhd_POST_TEXT_ST_CR_FOUND; + ++i; /* Process the next char */ + } + continue; + case mhd_POST_TEXT_ST_CR_FOUND: + if ('\n' != buf[i]) + { + enc_broken = true; + break; + } + /* Intentional fall-through */ + case mhd_POST_TEXT_ST_FULL_LINE_FOUND: + ++i; /* Advance to the next char to be checked */ + if (process_complete_field (c, + buf, + &i, + pdata_size, + p_data->field_start, + tf->name_idx, + tf->name_len, + tf->value_idx, + tf->value_len)) + { + if (c->suspended) + --i; /* Go back to the same position */ + return true; + } + mhd_assert (*pdata_size >= i); + reset_text_parse_field_data (&(c->rq.u_proc.post)); + continue; /* Process the next char */ + default: + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + enc_broken = true; + break; + } + mhd_assert (enc_broken); + break; + } + + mhd_assert ((*pdata_size == i) || enc_broken); + + if (enc_broken) + { + if (p_data->some_data_provided) + { + mhd_LOG_MSG (c->daemon, \ + MHD_SC_REQ_POST_PARSE_PARTIAL_INVALID_POST_FORMAT, \ + "The request POST has broken encoding or format and " \ + "was parsed only partially."); + c->rq.u_proc.post.parse_result = + MHD_POST_PARSE_RES_PARTIAL_INVALID_POST_FORMAT; + } + else + { + mhd_LOG_MSG (c->daemon, \ + MHD_SC_REQ_POST_PARSE_FAILED_INVALID_POST_FORMAT, \ + "The request POST has broken encoding or format and " \ + "cannot be parsed."); + c->rq.u_proc.post.parse_result = + MHD_POST_PARSE_RES_FAILED_INVALID_POST_FORMAT; + } + c->discard_request = true; + c->state = MHD_CONNECTION_FULL_REQ_RECEIVED; + return true; + } + + mhd_assert (mhd_POST_TEXT_ST_AT_EQ != tf->st); + mhd_assert (mhd_POST_TEXT_ST_AT_CR != tf->st); + mhd_assert (mhd_POST_TEXT_ST_AT_LF_BARE != tf->st); + mhd_assert (mhd_POST_TEXT_ST_FULL_LINE_FOUND != tf->st); + + mhd_assert (*pdata_size == i); + + if ((mhd_POST_TEXT_ST_VALUE == tf->st) && + (i != tf->value_idx)) + { + if (process_partial_value(c, + buf, + &i, + pdata_size, + p_data->field_start, + tf->name_idx, + tf->name_len, + tf->value_idx, + i - tf->value_idx)) + return true; + } + + return false; /* Continue parsing */ +} + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_(3) bool +mhd_stream_post_parse (struct MHD_Connection *restrict c, + size_t *restrict data_size, + char *restrict buf) +{ + struct mhd_PostParserData *const p_data = &(c->rq.u_proc.post); + bool alloc_failed; + + mhd_assert (MHD_HTTP_POST_ENCODING_OTHER != p_data->enc); + mhd_assert (p_data->lbuf.size >= p_data->lbuf_used); + + // TODO: support process in the connection buffer + if (NULL == p_data->lbuf.data) + { + alloc_failed = ! get_lbuf_fixed_size (c, + *data_size, + &(p_data->lbuf)); + } + else + { + if (*data_size > (p_data->lbuf.size - p_data->lbuf_used)) + alloc_failed = + ! grow_lbuf_fixed_size (c, + *data_size - + (p_data->lbuf.size - p_data->lbuf_used), + &(p_data->lbuf)); + else + alloc_failed = false; + + } + if (alloc_failed) + { + mhd_LOG_MSG (c->daemon, \ + MHD_SC_REQ_POST_PARSE_FAILED_NO_LARGE_BUF_MEM, \ + "Not enough large shared buffer memory to " \ + "parse POST request."); + c->rq.u_proc.post.parse_result = + MHD_POST_PARSE_RES_FAILED_NO_LARGE_BUF_MEM; + c->discard_request = true; + c->state = MHD_CONNECTION_FULL_REQ_RECEIVED; + return true; + } + memcpy (p_data->lbuf.data + p_data->lbuf_used, + buf, + *data_size); + p_data->lbuf_used += *data_size; + *data_size = 0; /* All data has been moved */ + + switch (p_data->enc) + { + case MHD_HTTP_POST_ENCODING_FORM_URLENCODED: + // TODO: finish + c->state = MHD_CONNECTION_FULL_REQ_RECEIVED; + return true; + case MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA: + // TODO: finish + c->state = MHD_CONNECTION_FULL_REQ_RECEIVED; + return true; + case MHD_HTTP_POST_ENCODING_TEXT_PLAIN: + return parse_post_text (c, + &(p_data->lbuf_used), + p_data->lbuf.data); + case MHD_HTTP_POST_ENCODING_OTHER: + default: + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + c->rq.u_proc.post.parse_result = + MHD_POST_PARSE_RES_PARTIAL_INVALID_POST_FORMAT; + c->discard_request = true; + c->state = MHD_CONNECTION_FULL_REQ_RECEIVED; + break; + } + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_(3) void +mhd_stream_post_final_data (struct MHD_Connection *restrict c, + size_t *restrict data_size, + char *restrict buf) +{ + // TODO: implement check for unprocessed broken partial data + return; +} diff --git a/src/mhd2/post_parser_funcs.h b/src/mhd2/post_parser_funcs.h new file mode 100644 index 00000000..80168977 --- /dev/null +++ b/src/mhd2/post_parser_funcs.h @@ -0,0 +1,65 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd 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. + + GNU libmicrohttpd 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 src/mhd2/post_parser_funcs.h + * @brief The declarations of internal POST parser functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_POST_PARSER_FUNCS_H +#define MHD_POST_PARSER_FUNCS_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +struct MHD_Connection; /* forward declaration */ + +/** + * Make preparation necessary for parsing POST data. + * @param c the stream to use + * @return 'true' if succeed, + * 'false' if failed and error result is set in the stream + */ +MHD_INTERNAL bool +mhd_stream_prepare_for_post_parse (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_(1); + +/** + * Parse POST data. + * @param c the stream to use + * @return // FIXME + */ +MHD_INTERNAL bool +mhd_stream_post_parse (struct MHD_Connection *restrict c, + size_t *restrict data_size, + char *restrict buf) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_SIZE_(3, 2); + +// TODO: describe +MHD_INTERNAL void +mhd_stream_post_final_data (struct MHD_Connection *restrict c, + size_t *restrict data_size, + char *restrict buf) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_(3); + + +#endif /* ! MHD_POST_PARSER_FUNCS_H */ diff --git a/src/mhd2/request_get_value.c b/src/mhd2/request_get_value.c index eda93fce..a96cdf50 100644 --- a/src/mhd2/request_get_value.c +++ b/src/mhd2/request_get_value.c @@ -1,5 +1,6 @@ /* This file is part of GNU libmicrohttpd + Copyright (C) 2024 Christian Grothoff Copyright (C) 2024 Evgeny Grin (Karlson2k) GNU libmicrohttpd is free software; you can redistribute it and/or @@ -48,18 +49,37 @@ mhd_request_get_value_n (struct MHD_Request *restrict request, size_t key_len, const char *restrict key) { - struct mhd_RequestField *f; mhd_assert (strlen (key) == key_len); - for (f = mhd_DLINKEDL_GET_FIRST (request, fields); NULL != f; - f = mhd_DLINKEDL_GET_NEXT (f, fields)) + if (MHD_VK_POSTDATA != kind) { - if ((key_len == f->field.nv.name.len) && - (kind == f->field.kind) && - (0 == memcmp (key, f->field.nv.name.cstr, key_len))) - return &(f->field.nv.value); + struct mhd_RequestField *f; + + for (f = mhd_DLINKEDL_GET_FIRST (request, fields); NULL != f; + f = mhd_DLINKEDL_GET_NEXT (f, fields)) + { + if ((key_len == f->field.nv.name.len) && + (0 != (kind & f->field.kind)) && + (0 == memcmp (key, f->field.nv.name.cstr, key_len))) + return &(f->field.nv.value); + } } + +#if HAVE_POST_PARSER + if (0 != (MHD_VK_POSTDATA & kind)) + { + struct mhd_RequestPostField *f; + for (f = mhd_DLINKEDL_GET_FIRST (request, post_fields); NULL != f; + f = mhd_DLINKEDL_GET_NEXT (f, post_fields)) + { + if ((key_len == f->field.name.len) && + (0 == memcmp (key, f->field.name.cstr, key_len))) + return &(f->field.value); + } + } +#endif /* HAVE_POST_PARSER */ + return NULL; } diff --git a/src/mhd2/respond_with_error.c b/src/mhd2/respond_with_error.c index 91b74bae..82c4ef52 100644 --- a/src/mhd2/respond_with_error.c +++ b/src/mhd2/respond_with_error.c @@ -62,9 +62,9 @@ respond_with_error_len (struct MHD_Connection *c, /* Discard most of the request data */ - if (NULL != c->rq.cntn.lbuf.buf) + if (NULL != c->rq.cntn.lbuf.data) mhd_daemon_free_lbuf (c->daemon, &(c->rq.cntn.lbuf)); - c->rq.cntn.lbuf.buf = NULL; + c->rq.cntn.lbuf.data = NULL; c->write_buffer = NULL; c->write_buffer_size = 0; diff --git a/src/mhd2/response_from.h b/src/mhd2/response_from.h index ca964832..c60587fd 100644 --- a/src/mhd2/response_from.h +++ b/src/mhd2/response_from.h @@ -41,6 +41,16 @@ MHD_INTERNAL void mhd_response_deinit_content_data (struct MHD_Response *restrict r) MHD_FN_PAR_NONNULL_ (1); +/** + * Create special internal-only response for sending automatic error messages + * @param sc the HTTP status code (enum MHD_HTTP_StatusCode) + * @param cntn_len the length of the @a cntn + * @param cntn the response content + * @param spec_hdr_len the length of the @a spec_hdr + * @param spec_hdr the special string to be used as a header string + * @return the response object if succeed, + * NULL if failed (out of memory) + */ MHD_INTERNAL struct MHD_Response * mhd_response_special_for_error (unsigned int sc, size_t cntn_len, diff --git a/src/mhd2/stream_funcs.c b/src/mhd2/stream_funcs.c index 1a4133de..4b0043e0 100644 --- a/src/mhd2/stream_funcs.c +++ b/src/mhd2/stream_funcs.c @@ -496,8 +496,10 @@ mhd_stream_finish_req_serving (struct MHD_Connection *restrict c, { mhd_assert (! c->stop_with_error || (NULL == c->rp.response) || \ (c->rp.response->cfg.int_err_resp)); - /* Next function will destroy response, notify client, - * destroy memory pool and set connection state to "CLOSED" */ + + /* Next function will notify client and set connection + * state to "PRE-CLOSING" */ + /* Later response and memory pool will be destroyed */ mhd_conn_pre_close (c, c->stop_with_error ? mhd_CONN_CLOSE_ERR_REPLY_SENT : @@ -652,7 +654,6 @@ mhd_conn_pre_close (struct MHD_Connection *restrict c, const char *log_msg) { bool close_hard; - bool use_local_lingering; enum MHD_RequestEndedCode end_code; enum MHD_StatusCode sc; @@ -774,7 +775,6 @@ mhd_conn_pre_close (struct MHD_Connection *restrict c, mhd_assert ((NULL == log_msg) || (MHD_SC_INTERNAL_ERROR != sc)); - use_local_lingering = false; /* Make changes on the socket early to let the kernel and the remote * to process the changes in parallel. */ if (close_hard) @@ -789,13 +789,12 @@ mhd_conn_pre_close (struct MHD_Connection *restrict c, if (mhd_socket_shut_wr (c->socket_fd) && (! c->sk_rmt_shut_wr)) { (void) 0; // TODO: start local lingering phase - c->state = MHD_CONNECTION_CLOSED; // TODO: start local lingering phase + c->state = MHD_CONNECTION_PRE_CLOSING; // TODO: start local lingering phase c->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP; // TODO: start local lingering phase - // use_local_lingering = true; } else { /* No need / not possible to linger */ - c->state = MHD_CONNECTION_CLOSED; + c->state = MHD_CONNECTION_PRE_CLOSING; c->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP; } } @@ -809,13 +808,6 @@ mhd_conn_pre_close (struct MHD_Connection *restrict c, (void) log_msg; #endif /* ! HAVE_LOG_FUNCTIONALITY */ - mhd_stream_call_dcc_cleanup_if_needed (c); - if (NULL != c->rp.resp_iov.iov) - { - free (c->rp.resp_iov.iov); - c->rp.resp_iov.iov = NULL; - } - #if 0 // TODO: notification callback mhd_assert ((MHD_CONNECTION_INIT != c->state) || (! c->rq.app_aware)); if ( (NULL != d->notify_completed) && @@ -842,9 +834,6 @@ mhd_conn_pre_close (struct MHD_Connection *restrict c, #ifndef NDEBUG c->dbg.pre_closed = true; #endif - - if (! use_local_lingering) - mhd_conn_pre_clean (c); } @@ -854,8 +843,18 @@ mhd_conn_pre_clean (struct MHD_Connection *restrict c) { // TODO: support suspended connections + mhd_assert (c->dbg.pre_closed); + mhd_assert (! c->dbg.pre_cleaned); + mhd_conn_mark_unready (c, c->daemon); + mhd_stream_call_dcc_cleanup_if_needed (c); + if (NULL != c->rp.resp_iov.iov) + { + free (c->rp.resp_iov.iov); + c->rp.resp_iov.iov = NULL; + } + if (NULL != c->rq.cntn.lbuf.data) mhd_daemon_free_lbuf (c->daemon, &(c->rq.cntn.lbuf)); c->rq.cntn.lbuf.data = NULL; @@ -893,6 +892,7 @@ mhd_conn_pre_clean (struct MHD_Connection *restrict c) } #endif /* MHD_USE_EPOLL */ + c->state = MHD_CONNECTION_CLOSED; #ifndef NDEBUG c->dbg.pre_cleaned = true; #endif diff --git a/src/mhd2/stream_process_reply.c b/src/mhd2/stream_process_reply.c index aeaaa4f1..70a3067b 100644 --- a/src/mhd2/stream_process_reply.c +++ b/src/mhd2/stream_process_reply.c @@ -1009,7 +1009,7 @@ mhd_stream_prep_unchunked_body (struct MHD_Connection *restrict c) size_to_fill); c->rp.app_act_ctx.connection = NULL; /* Block any attempt to create a new action */ if (! preprocess_dcc_action (c, act)) - return false; + return true; if (mhd_DCC_ACTION_FINISH == c->rp.app_act.act) { mhd_assert (MHD_SIZE_UNKNOWN == r->cntn_size); @@ -1029,7 +1029,7 @@ mhd_stream_prep_unchunked_body (struct MHD_Connection *restrict c) mhd_CONN_CLOSE_APP_ERROR, "Closing connection (application returned more data " "than requested)."); - return false; + return true; } c->rp.rsp_cntn_read_pos += filled; c->write_buffer_append_offset += filled; @@ -1122,6 +1122,7 @@ mhd_stream_prep_chunked_body (struct MHD_Connection *restrict c) mhd_STREAM_ABORT (c, mhd_CONN_CLOSE_NO_POOL_MEM_FOR_REPLY, "No memory in the pool for the reply chunked content."); + return true; } mhd_assert (max_chunk_overhead < \ (c->write_buffer_size)); @@ -1178,7 +1179,7 @@ mhd_stream_prep_chunked_body (struct MHD_Connection *restrict c) size_to_fill); c->rp.app_act_ctx.connection = NULL; /* Block any attempt to create a new action */ if (! preprocess_dcc_action (c, act)) - return false; + return true; if (mhd_DCC_ACTION_FINISH == c->rp.app_act.act) { mhd_assert (MHD_SIZE_UNKNOWN == r->cntn_size); @@ -1196,7 +1197,7 @@ mhd_stream_prep_chunked_body (struct MHD_Connection *restrict c) mhd_CONN_CLOSE_APP_ERROR, "Closing connection (application returned more data " "than requested)."); - return false; + return true; } c->rp.rsp_cntn_read_pos += filled; c->write_buffer_append_offset += filled; @@ -1300,7 +1301,7 @@ prep_chunked_footer_inn (struct MHD_Connection *restrict c) } -MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool mhd_stream_prep_chunked_footer (struct MHD_Connection *restrict c) { if (! prep_chunked_footer_inn (c)) @@ -1308,7 +1309,8 @@ mhd_stream_prep_chunked_footer (struct MHD_Connection *restrict c) mhd_STREAM_ABORT (c, mhd_CONN_CLOSE_NO_POOL_MEM_FOR_REPLY, "No memory in the pool for the reply chunked footer."); - return; + return true; } c->state = MHD_CONNECTION_FOOTERS_SENDING; + return false; } diff --git a/src/mhd2/stream_process_reply.h b/src/mhd2/stream_process_reply.h index a733af36..4bce60fa 100644 --- a/src/mhd2/stream_process_reply.h +++ b/src/mhd2/stream_process_reply.h @@ -75,6 +75,8 @@ MHD_FN_PAR_NONNULL_ALL_; /** * Prepare the chunked response content of this connection for sending. * + * @param c the connection + * * @return 'true' if connection new state could be processed now, * 'false' if no new state processing is needed. */ @@ -88,8 +90,11 @@ MHD_FN_PAR_NONNULL_ALL_; * with response footers. * * @param c the connection + * + * @return 'true' if connection new state could be processed now, + * 'false' if no new state processing is needed. */ -MHD_INTERNAL void +MHD_INTERNAL bool mhd_stream_prep_chunked_footer (struct MHD_Connection *restrict c) MHD_FN_PAR_NONNULL_ALL_; diff --git a/src/mhd2/stream_process_request.c b/src/mhd2/stream_process_request.c index a7aa52de..0cee3e34 100644 --- a/src/mhd2/stream_process_request.c +++ b/src/mhd2/stream_process_request.c @@ -422,14 +422,6 @@ */ #define MHD_CHUNK_HEADER_REASONABLE_LEN 24 -/** - * Get whether bare LF in HTTP header and other protocol elements - * should be treated as the line termination depending on the configured - * strictness level. - * RFC 9112, section 2.2 - */ -#define MHD_ALLOW_BARE_LF_AS_CRLF_(discp_lvl) (0 >= discp_lvl) - /** * The valid length of any HTTP version string */ @@ -586,7 +578,7 @@ get_request_line_inner (struct MHD_Connection *restrict c) (skip_empty_lines && (-3 >= discp_lvl)); /* Treat bare LF as the end of the line. RFC 9112, section 2.2 */ - const bool bare_lf_as_crlf = MHD_ALLOW_BARE_LF_AS_CRLF_ (discp_lvl); + const bool bare_lf_as_crlf = mhd_ALLOW_BARE_LF_AS_CRLF (discp_lvl); /* Treat tab as whitespace delimiter. RFC 9112, section 3 */ const bool tab_as_wsp = (0 >= discp_lvl); @@ -1561,7 +1553,7 @@ get_req_header (struct MHD_Connection *restrict c, RFC 9112, section 2.2-3 Note: MHD never replaces bare LF with space (RFC 9110, section 5.5-5). Bare LF is processed as end of the line or rejected as broken request. */ - const bool bare_lf_as_crlf = MHD_ALLOW_BARE_LF_AS_CRLF_ (discp_lvl); + const bool bare_lf_as_crlf = mhd_ALLOW_BARE_LF_AS_CRLF (discp_lvl); /* Keep bare CR character as is. Violates RFC 9112, section 2.2-4 */ const bool bare_cr_keep = (-3 >= discp_lvl); @@ -2949,15 +2941,36 @@ mhd_stream_call_app_request_cb (struct MHD_Connection *restrict c) } c->state = MHD_CONNECTION_FULL_REQ_RECEIVED; return true; +#ifdef HAVE_POST_PARSER case mhd_ACTION_POST_PARSE: - mhd_assert (0 && "Not implemented yet"); + if (0 == c->rq.cntn.cntn_size) + { + c->rq.u_proc.post.parse_result = MHD_POST_PARSE_RES_REQUEST_EMPTY; + c->state = MHD_CONNECTION_FULL_REQ_RECEIVED; + return true; + } + if (! mhd_stream_prepare_for_post_parse (c)) + { + mhd_assert (MHD_CONNECTION_FOOTERS_RECEIVED < c->state); + return true; + } + if (need_100_continue (c)) + { + c->state = MHD_CONNECTION_CONTINUE_SENDING; + return true; + } + c->state = MHD_CONNECTION_BODY_RECEIVING; return true; +#endif /* HAVE_POST_PARSER */ case mhd_ACTION_SUSPEND: c->suspended = true; return false; case mhd_ACTION_ABORT: mhd_conn_pre_close_app_abort (c); - return false; + return true; +#ifndef HAVE_POST_PARSER + case mhd_ACTION_POST_PARSE: +#endif /* ! HAVE_POST_PARSER */ case mhd_ACTION_NO_ACTION: default: mhd_assert (0 && "Impossible value"); @@ -2976,10 +2989,10 @@ mhd_stream_call_app_request_cb (struct MHD_Connection *restrict c) * @return true if connection state has been changed, * false otherwise */ -static MHD_FN_PAR_NONNULL_ (1) bool -process_upload_action (struct MHD_Connection *restrict c, - const struct MHD_UploadAction *act, - bool final) +MHD_INTERNAL MHD_FN_PAR_NONNULL_ (1) bool +mhd_stream_process_upload_action (struct MHD_Connection *restrict c, + const struct MHD_UploadAction *act, + bool final) { if (NULL != act) { @@ -3011,7 +3024,7 @@ process_upload_action (struct MHD_Connection *restrict c, return false; case mhd_UPLOAD_ACTION_ABORT: mhd_conn_pre_close_app_abort (c); - return false; + return true; case mhd_UPLOAD_ACTION_NO_ACTION: default: mhd_assert (0 && "Impossible value"); @@ -3034,7 +3047,7 @@ process_request_chunked_body (struct MHD_Connection *restrict c) RFC 9112, section 2.2-3 Note: MHD never replaces bare LF with space (RFC 9110, section 5.5-5). Bare LF is processed as end of the line or rejected as broken request. */ - const bool bare_lf_as_crlf = MHD_ALLOW_BARE_LF_AS_CRLF_ (discp_lvl); + const bool bare_lf_as_crlf = mhd_ALLOW_BARE_LF_AS_CRLF (discp_lvl); /* Allow "Bad WhiteSpace" in chunk extension. RFC 9112, Section 7.1.1, Paragraph 2 */ const bool allow_bws = (2 < discp_lvl); @@ -3229,79 +3242,94 @@ process_request_chunked_body (struct MHD_Connection *restrict c) } mhd_assert (c->rq.app_aware); +#ifdef HAVE_POST_PARSER if (mhd_ACTION_POST_PARSE == c->rq.app_act.head_act.act) { - mhd_assert (0 && "Not implemented yet"); // TODO: implement POST - return false; - } - - if (NULL != c->rq.app_act.head_act.data.upload.full.cb) - { - need_inc_proc = false; - - mhd_assert (0 == c->rq.cntn.proc_size); - if ((uint_fast64_t) c->rq.cntn.lbuf.size < - c->rq.cntn.recv_size + cntn_data_ready) - { - size_t grow_size; - - grow_size = (size_t) (c->rq.cntn.recv_size + cntn_data_ready - - c->rq.cntn.lbuf.size); - if (((size_t) (c->rq.cntn.recv_size + cntn_data_ready) < - cntn_data_ready) || (! mhd_daemon_grow_lbuf (d, - grow_size, - &(c->rq.cntn.lbuf)))) - { - /* Failed to grow the buffer, no space to put the new data */ - const struct MHD_UploadAction *act; - if (NULL != c->rq.app_act.head_act.data.upload.inc.cb) - { - mhd_RESPOND_WITH_ERROR_STATIC ( - c, - MHD_HTTP_STATUS_CONTENT_TOO_LARGE, - ERR_RSP_MSG_REQUEST_TOO_BIG); - return true; - } - c->rq.app_act.head_act.data.upload.full.cb = NULL; /* Cannot process "full" content */ - /* Process previously buffered data */ - mhd_assert (c->rq.cntn.recv_size <= c->rq.cntn.lbuf.size); - act = c->rq.app_act.head_act.data.upload.inc.cb ( - c->rq.app_act.head_act.data.upload.inc.cls, - &(c->rq), - c->rq.cntn.recv_size, - c->rq.cntn.lbuf.buf); - c->rq.cntn.proc_size = c->rq.cntn.recv_size; - mhd_daemon_free_lbuf (d, &(c->rq.cntn.lbuf)); - if (process_upload_action (c, act, false)) - return true; - need_inc_proc = true; - } - } - if (! need_inc_proc) - { - memcpy (c->rq.cntn.lbuf.buf + c->rq.cntn.recv_size, - buffer_head, cntn_data_ready); - c->rq.cntn.recv_size += cntn_data_ready; - } - } - else - need_inc_proc = true; - - if (need_inc_proc) - { - const struct MHD_UploadAction *act; - mhd_assert (NULL != c->rq.app_act.head_act.data.upload.inc.cb); + size_t size_provided; c->rq.cntn.recv_size += cntn_data_ready; - act = c->rq.app_act.head_act.data.upload.inc.cb ( - c->rq.app_act.head_act.data.upload.inc.cls, - &(c->rq), - cntn_data_ready, - buffer_head); - c->rq.cntn.proc_size += cntn_data_ready; - state_updated = process_upload_action (c, act, false); - } + size_provided = cntn_data_ready; + state_updated = mhd_stream_post_parse (c, + &cntn_data_ready, + buffer_head); + mhd_assert (0 == cntn_data_ready); // TODO: support one chunk in-place processing? + c->rq.cntn.recv_size += size_provided; + + } + else +#endif /* HAVE_POST_PARSER */ + if (1) + { + mhd_assert (mhd_ACTION_UPLOAD == c->rq.app_act.head_act.act); + if (NULL != c->rq.app_act.head_act.data.upload.full.cb) + { + need_inc_proc = false; + + mhd_assert (0 == c->rq.cntn.proc_size); + if ((uint_fast64_t) c->rq.cntn.lbuf.size < + c->rq.cntn.recv_size + cntn_data_ready) + { + size_t grow_size; + + grow_size = (size_t) (c->rq.cntn.recv_size + cntn_data_ready + - c->rq.cntn.lbuf.size); + if (((size_t) (c->rq.cntn.recv_size + cntn_data_ready) < + cntn_data_ready) || + (! mhd_daemon_grow_lbuf (d, + grow_size, + &(c->rq.cntn.lbuf)))) + { + /* Failed to grow the buffer, no space to put the new data */ + const struct MHD_UploadAction *act; + if (NULL != c->rq.app_act.head_act.data.upload.inc.cb) + { + mhd_RESPOND_WITH_ERROR_STATIC ( + c, + MHD_HTTP_STATUS_CONTENT_TOO_LARGE, + ERR_RSP_MSG_REQUEST_TOO_BIG); + return true; + } + c->rq.app_act.head_act.data.upload.full.cb = NULL; /* Cannot process "full" content */ + /* Process previously buffered data */ + mhd_assert (c->rq.cntn.recv_size <= c->rq.cntn.lbuf.size); + act = c->rq.app_act.head_act.data.upload.inc.cb ( + c->rq.app_act.head_act.data.upload.inc.cls, + &(c->rq), + c->rq.cntn.recv_size, + c->rq.cntn.lbuf.data); + c->rq.cntn.proc_size = c->rq.cntn.recv_size; + mhd_daemon_free_lbuf (d, &(c->rq.cntn.lbuf)); + if (process_upload_action (c, act, false)) + return true; + need_inc_proc = true; + } + } + if (! need_inc_proc) + { + memcpy (c->rq.cntn.lbuf.data + c->rq.cntn.recv_size, + buffer_head, cntn_data_ready); + c->rq.cntn.recv_size += cntn_data_ready; + } + } + else + need_inc_proc = true; + + if (need_inc_proc) + { + const struct MHD_UploadAction *act; + mhd_assert (NULL != c->rq.app_act.head_act.data.upload.inc.cb); + + c->rq.cntn.recv_size += cntn_data_ready; + act = c->rq.app_act.head_act.data.upload.inc.cb ( + c->rq.app_act.head_act.data.upload.inc.cls, + &(c->rq), + cntn_data_ready, + buffer_head); + c->rq.cntn.proc_size += cntn_data_ready; + state_updated = process_upload_action (c, act, false); + } + } /* dh left "processed" bytes in buffer for next time... */ buffer_head += cntn_data_ready; available -= cntn_data_ready; @@ -3341,54 +3369,66 @@ process_request_nonchunked_body (struct MHD_Connection *restrict c) else cntn_data_ready = c->read_buffer_offset; - if (mhd_ACTION_POST_PARSE == c->rq.app_act.head_act.act) - { - mhd_assert (0 && "Not implemented yet"); // TODO: implement POST - return false; - } - - mhd_assert (mhd_ACTION_UPLOAD == c->rq.app_act.head_act.act); read_buf_reuse = false; state_updated = false; - if (NULL != c->rq.app_act.head_act.data.upload.full.cb) + + if (mhd_ACTION_POST_PARSE == c->rq.app_act.head_act.act) { - // TODO: implement processing in pool memory if buffer is large enough - mhd_assert ((c->rq.cntn.recv_size + cntn_data_ready) <= - (uint_fast64_t) c->rq.cntn.lbuf.size); - memcpy (c->rq.cntn.lbuf.buf + c->rq.cntn.recv_size, - c->read_buffer, cntn_data_ready); + size_t size_provided; + + c->rq.cntn.recv_size += cntn_data_ready; + size_provided = cntn_data_ready; + + state_updated = mhd_stream_post_parse (c, + &size_provided, + c->read_buffer); + mhd_assert (0 == size_provided); // TODO: support one chunk in-place processing? + read_buf_reuse = true; // TODO: support one chunk in-place processing? c->rq.cntn.recv_size += cntn_data_ready; - read_buf_reuse = true; - if (c->rq.cntn.recv_size == c->rq.cntn.cntn_size) - { - c->state = MHD_CONNECTION_FULL_REQ_RECEIVED; - state_updated = true; - } } else { - const struct MHD_UploadAction *act; - mhd_assert (NULL != c->rq.app_act.head_act.data.upload.inc.cb); + mhd_assert (mhd_ACTION_UPLOAD == c->rq.app_act.head_act.act); + if (NULL != c->rq.app_act.head_act.data.upload.full.cb) + { + // TODO: implement processing in pool memory if buffer is large enough + mhd_assert ((c->rq.cntn.recv_size + cntn_data_ready) <= + (uint_fast64_t) c->rq.cntn.lbuf.size); + memcpy (c->rq.cntn.lbuf.data + c->rq.cntn.recv_size, + c->read_buffer, cntn_data_ready); + c->rq.cntn.recv_size += cntn_data_ready; + read_buf_reuse = true; + if (c->rq.cntn.recv_size == c->rq.cntn.cntn_size) + { + c->state = MHD_CONNECTION_FULL_REQ_RECEIVED; + state_updated = true; + } + } + else + { + const struct MHD_UploadAction *act; + mhd_assert (NULL != c->rq.app_act.head_act.data.upload.inc.cb); - c->rq.cntn.recv_size += cntn_data_ready; - act = c->rq.app_act.head_act.data.upload.inc.cb ( - c->rq.app_act.head_act.data.upload.inc.cls, - &(c->rq), - cntn_data_ready, - c->read_buffer); - c->rq.cntn.proc_size += cntn_data_ready; - read_buf_reuse = true; - state_updated = process_upload_action (c, act, false); + c->rq.cntn.recv_size += cntn_data_ready; + act = c->rq.app_act.head_act.data.upload.inc.cb ( + c->rq.app_act.head_act.data.upload.inc.cls, + &(c->rq), + cntn_data_ready, + c->read_buffer); + c->rq.cntn.proc_size += cntn_data_ready; + read_buf_reuse = true; + state_updated = process_upload_action (c, act, false); + } } - if (read_buf_reuse) { size_t data_left_size; mhd_assert (c->read_buffer_offset >= cntn_data_ready); data_left_size = c->read_buffer_offset - cntn_data_ready; if (0 != data_left_size) - memmove (c->read_buffer, c->read_buffer + cntn_data_ready, data_left_size) - ; + memmove (c->read_buffer, + c->read_buffer + cntn_data_ready, + data_left_size); c->read_buffer_offset = data_left_size; } @@ -3423,14 +3463,14 @@ mhd_stream_call_app_final_upload_cb (struct MHD_Connection *restrict c) { mhd_assert (c->rq.cntn.recv_size == c->rq.cntn.cntn_size); mhd_assert (0 == c->rq.cntn.proc_size); - mhd_assert (NULL != c->rq.cntn.lbuf.buf); + mhd_assert (NULL != c->rq.cntn.lbuf.data); mhd_assert (c->rq.cntn.recv_size <= c->rq.cntn.lbuf.size); // TODO: implement processing in pool memory if it is large enough act = c->rq.app_act.head_act.data.upload.full.cb ( c->rq.app_act.head_act.data.upload.full.cls, &(c->rq), c->rq.cntn.recv_size, - c->rq.cntn.lbuf.buf); + c->rq.cntn.lbuf.data); c->rq.cntn.proc_size = c->rq.cntn.recv_size; } else @@ -3450,9 +3490,9 @@ mhd_stream_call_app_final_upload_cb (struct MHD_Connection *restrict c) MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool mhd_stream_process_req_recv_finished (struct MHD_Connection *restrict c) { - if (NULL != c->rq.cntn.lbuf.buf) + if (NULL != c->rq.cntn.lbuf.data) mhd_daemon_free_lbuf (c->daemon, &(c->rq.cntn.lbuf)); - c->rq.cntn.lbuf.buf = NULL; + c->rq.cntn.lbuf.data = NULL; if (c->rq.cntn.cntn_size != c->rq.cntn.proc_size) c->discard_request = true; mhd_assert (NULL != c->rp.response); diff --git a/src/mhd2/stream_process_request.h b/src/mhd2/stream_process_request.h index 7e317063..00147aec 100644 --- a/src/mhd2/stream_process_request.h +++ b/src/mhd2/stream_process_request.h @@ -145,6 +145,22 @@ MHD_INTERNAL bool mhd_stream_call_app_request_cb (struct MHD_Connection *restrict c) MHD_FN_PAR_NONNULL_ALL_; + + +/** + * React on provided action for upload + * @param c the stream to use + * @param act the action provided by application + * @param final set to 'true' if this is final upload callback + * @return true if connection state has been changed, + * false otherwise + */ +MHD_INTERNAL bool +mhd_stream_process_upload_action (struct MHD_Connection *restrict c, + const struct MHD_UploadAction *act, + bool final) +MHD_FN_PAR_NONNULL_ (1); + /** * Process non-chunked request body or upload chunking encoding. * Call the upload handler of the application. diff --git a/src/mhd2/stream_process_states.c b/src/mhd2/stream_process_states.c index c101670e..4707bf49 100644 --- a/src/mhd2/stream_process_states.c +++ b/src/mhd2/stream_process_states.c @@ -252,7 +252,7 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) return false; } - while (true) // TODO: support suspend + while (! c->suspended) { #ifdef HTTPS_SUPPORT // TODO: support TLS, handshake @@ -357,7 +357,7 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) mhd_assert (NULL != c->rp.response); mhd_stream_switch_from_recv_to_send (c); if (! mhd_stream_build_header_response (c)) - break; + continue; mhd_assert (MHD_CONNECTION_START_REPLY != c->state); break; case MHD_CONNECTION_HEADERS_SENDING: @@ -441,7 +441,8 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) mhd_assert (c->write_buffer_send_offset <= \ c->write_buffer_append_offset); mhd_stream_call_dcc_cleanup_if_needed (c); - mhd_stream_prep_chunked_footer (c); + if (mhd_stream_prep_chunked_footer (c)) + continue; break; case MHD_CONNECTION_FOOTERS_SENDING: mhd_assert (c->rp.props.send_reply_body); @@ -457,6 +458,9 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) && ! c->discard_request && ! c->sk_rmt_shut_wr); continue; + case MHD_CONNECTION_PRE_CLOSING: + mhd_conn_pre_clean (c); + break; case MHD_CONNECTION_CLOSED: break; #if 0 // def UPGRADE_SUPPORT @@ -471,6 +475,8 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) break; } + mhd_assert (MHD_CONNECTION_PRE_CLOSING != c->state); + if (MHD_CONNECTION_CLOSED == c->state) return false; @@ -488,15 +494,22 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) mhd_CONN_CLOSE_HTTP_COMPLETED : mhd_CONN_CLOSE_CLIENT_SHUTDOWN_EARLY, NULL); + mhd_conn_pre_clean (c); return false; } if (mhd_stream_check_timedout (c)) // TODO: centralise timeout checks { mhd_conn_pre_close_timedout (c); + mhd_conn_pre_clean (c); + return false; + } + + if (! update_active_state (c)) + { + mhd_conn_pre_clean (c); return false; } - update_active_state (c); /* MHD_connection_update_event_loop_info (c);*/ return true;