|
|
@@ -0,0 +1,2149 @@
|
|
|
+/*
|
|
|
+ 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/stream_process_request.c
|
|
|
+ * @brief The implementation of internal functions for requests parsing
|
|
|
+ * and processing
|
|
|
+ * @author Karlson2k (Evgeny Grin)
|
|
|
+ */
|
|
|
+
|
|
|
+#include "mhd_sys_options.h"
|
|
|
+
|
|
|
+#include "sys_bool_type.h"
|
|
|
+#include "sys_base_types.h"
|
|
|
+
|
|
|
+#include "mhd_str_macros.h"
|
|
|
+
|
|
|
+#include <string.h>
|
|
|
+
|
|
|
+#include "mhd_connection.h"
|
|
|
+
|
|
|
+#include "mhd_str_types.h"
|
|
|
+#include "daemon_logger.h"
|
|
|
+
|
|
|
+#include "request_funcs.h"
|
|
|
+
|
|
|
+/**
|
|
|
+ * Response text used when the request (http header) is
|
|
|
+ * malformed.
|
|
|
+ */
|
|
|
+#define ERR_RSP_REQUEST_MALFORMED \
|
|
|
+ "<html><head><title>Request malformed</title></head>" \
|
|
|
+ "<body>HTTP request is syntactically incorrect.</body></html>"
|
|
|
+
|
|
|
+/**
|
|
|
+ * Response text used when the request HTTP version is too old.
|
|
|
+ */
|
|
|
+#define ERR_RSP_REQ_HTTP_VER_IS_TOO_OLD \
|
|
|
+ "<html>" \
|
|
|
+ "<head><title>Requested HTTP version is not supported</title></head>" \
|
|
|
+ "<body>Requested HTTP version is too old and not " \
|
|
|
+ "supported.</body></html>"
|
|
|
+/**
|
|
|
+ * Response text used when the request HTTP version is not supported.
|
|
|
+ */
|
|
|
+#define ERR_RSP_REQ_HTTP_VER_IS_NOT_SUPPORTED \
|
|
|
+ "<html>" \
|
|
|
+ "<head><title>Requested HTTP version is not supported</title></head>" \
|
|
|
+ "<body>Requested HTTP version is not supported.</body></html>"
|
|
|
+
|
|
|
+/**
|
|
|
+ * Response text used when the request HTTP header has bare CR character
|
|
|
+ * without LF character (and CR is not allowed to be treated as whitespace).
|
|
|
+ */
|
|
|
+#define ERR_RSP_BARE_CR_IN_HEADER \
|
|
|
+ "<html>" \
|
|
|
+ "<head><title>Request broken</title></head>" \
|
|
|
+ "<body>Request HTTP header has bare CR character without " \
|
|
|
+ "following LF character.</body>" \
|
|
|
+ "</html>"
|
|
|
+
|
|
|
+/**
|
|
|
+ * Response text used when the request HTTP footer has bare CR character
|
|
|
+ * without LF character (and CR is not allowed to be treated as whitespace).
|
|
|
+ */
|
|
|
+#define ERR_RSP_BARE_CR_IN_FOOTER \
|
|
|
+ "<html>" \
|
|
|
+ "<head><title>Request broken</title></head>" \
|
|
|
+ "<body>Request HTTP footer has bare CR character without " \
|
|
|
+ "following LF character.</body>" \
|
|
|
+ "</html>"
|
|
|
+
|
|
|
+/**
|
|
|
+ * Response text used when the request HTTP header has bare LF character
|
|
|
+ * without CR character.
|
|
|
+ */
|
|
|
+#define ERR_RSP_BARE_LF_IN_HEADER \
|
|
|
+ "<html>" \
|
|
|
+ "<head><title>Request broken</title></head>" \
|
|
|
+ "<body>Request HTTP header has bare LF character without " \
|
|
|
+ "preceding CR character.</body>" \
|
|
|
+ "</html>"
|
|
|
+/**
|
|
|
+ * Response text used when the request HTTP footer has bare LF character
|
|
|
+ * without CR character.
|
|
|
+ */
|
|
|
+#define ERR_RSP_BARE_LF_IN_FOOTER \
|
|
|
+ "<html>" \
|
|
|
+ "<head><title>Request broken</title></head>" \
|
|
|
+ "<body>Request HTTP footer has bare LF character without " \
|
|
|
+ "preceding CR character.</body>" \
|
|
|
+ "</html>"
|
|
|
+
|
|
|
+/**
|
|
|
+ * Response text used when the request line has more then two whitespaces.
|
|
|
+ */
|
|
|
+#define ERR_RSP_RQ_LINE_TOO_MANY_WSP \
|
|
|
+ "<html>" \
|
|
|
+ "<head><title>Request broken</title></head>" \
|
|
|
+ "<body>The request line has more then two whitespaces.</body>" \
|
|
|
+ "</html>"
|
|
|
+
|
|
|
+/**
|
|
|
+ * Response text used when the request line has invalid characters in URI.
|
|
|
+ */
|
|
|
+#define ERR_RSP_RQ_TARGET_INVALID_CHAR \
|
|
|
+ "<html>" \
|
|
|
+ "<head><title>Request broken</title></head>" \
|
|
|
+ "<body>HTTP request has invalid characters in " \
|
|
|
+ "the request-target.</body>" \
|
|
|
+ "</html>"
|
|
|
+
|
|
|
+/**
|
|
|
+ * Response text used when line folding is used in request headers.
|
|
|
+ */
|
|
|
+#define ERR_RSP_OBS_FOLD \
|
|
|
+ "<html>" \
|
|
|
+ "<head><title>Request broken</title></head>" \
|
|
|
+ "<body>Obsolete line folding is used in HTTP request header.</body>" \
|
|
|
+ "</html>"
|
|
|
+
|
|
|
+/**
|
|
|
+ * Response text used when line folding is used in request footers.
|
|
|
+ */
|
|
|
+#define ERR_RSP_OBS_FOLD_FOOTER \
|
|
|
+ "<html>" \
|
|
|
+ "<head><title>Request broken</title></head>" \
|
|
|
+ "<body>Obsolete line folding is used in HTTP request footer.</body>" \
|
|
|
+ "</html>"
|
|
|
+
|
|
|
+/**
|
|
|
+ * Response text used when request header has no colon character.
|
|
|
+ */
|
|
|
+#define ERR_RSP_HEADER_WITHOUT_COLON \
|
|
|
+ "<html>" \
|
|
|
+ "<head><title>Request broken</title></head>" \
|
|
|
+ "<body>HTTP request header line has no colon character.</body>" \
|
|
|
+ "</html>"
|
|
|
+
|
|
|
+/**
|
|
|
+ * Response text used when request footer has no colon character.
|
|
|
+ */
|
|
|
+#define ERR_RSP_FOOTER_WITHOUT_COLON \
|
|
|
+ "<html>" \
|
|
|
+ "<head><title>Request broken</title></head>" \
|
|
|
+ "<body>HTTP request footer line has no colon character.</body>" \
|
|
|
+ "</html>"
|
|
|
+/**
|
|
|
+ * Response text used when the request has whitespace at the start
|
|
|
+ * of the first header line.
|
|
|
+ */
|
|
|
+#define ERR_RSP_WSP_BEFORE_HEADER \
|
|
|
+ "<html>" \
|
|
|
+ "<head><title>Request broken</title></head>" \
|
|
|
+ "<body>HTTP request has whitespace between the request line and " \
|
|
|
+ "the first header.</body>" \
|
|
|
+ "</html>"
|
|
|
+
|
|
|
+/**
|
|
|
+ * Response text used when the request has whitespace at the start
|
|
|
+ * of the first footer line.
|
|
|
+ */
|
|
|
+#define ERR_RSP_WSP_BEFORE_FOOTER \
|
|
|
+ "<html>" \
|
|
|
+ "<head><title>Request broken</title></head>" \
|
|
|
+ "<body>First HTTP footer line has whitespace at the first " \
|
|
|
+ "position.</body>" \
|
|
|
+ "</html>"
|
|
|
+
|
|
|
+/**
|
|
|
+ * Response text used when the whitespace found before colon (inside header
|
|
|
+ * name or between header name and colon).
|
|
|
+ */
|
|
|
+#define ERR_RSP_WSP_IN_HEADER_NAME \
|
|
|
+ "<html>" \
|
|
|
+ "<head><title>Request broken</title></head>" \
|
|
|
+ "<body>HTTP request has whitespace before the first colon " \
|
|
|
+ "in header line.</body>" \
|
|
|
+ "</html>"
|
|
|
+
|
|
|
+/**
|
|
|
+ * Response text used when the whitespace found before colon (inside header
|
|
|
+ * name or between header name and colon).
|
|
|
+ */
|
|
|
+#define ERR_RSP_WSP_IN_FOOTER_NAME \
|
|
|
+ "<html>" \
|
|
|
+ "<head><title>Request broken</title></head>" \
|
|
|
+ "<body>HTTP request has whitespace before the first colon " \
|
|
|
+ "in footer line.</body>" \
|
|
|
+ "</html>"
|
|
|
+/**
|
|
|
+ * Response text used when request header has invalid character.
|
|
|
+ */
|
|
|
+#define ERR_RSP_INVALID_CHR_IN_HEADER \
|
|
|
+ "<html>" \
|
|
|
+ "<head><title>Request broken</title></head>" \
|
|
|
+ "<body>HTTP request has invalid character in header.</body>" \
|
|
|
+ "</html>"
|
|
|
+
|
|
|
+/**
|
|
|
+ * Response text used when request header has invalid character.
|
|
|
+ */
|
|
|
+#define ERR_RSP_INVALID_CHR_IN_FOOTER \
|
|
|
+ "<html>" \
|
|
|
+ "<head><title>Request broken</title></head>" \
|
|
|
+ "<body>HTTP request has invalid character in footer.</body>" \
|
|
|
+ "</html>"
|
|
|
+
|
|
|
+/**
|
|
|
+ * Response text used when request header has zero-length header (filed) name.
|
|
|
+ */
|
|
|
+#define ERR_RSP_EMPTY_HEADER_NAME \
|
|
|
+ "<html>" \
|
|
|
+ "<head><title>Request broken</title></head>" \
|
|
|
+ "<body>HTTP request header has empty header name.</body>" \
|
|
|
+ "</html>"
|
|
|
+
|
|
|
+/**
|
|
|
+ * Response text used when request header has zero-length header (filed) name.
|
|
|
+ */
|
|
|
+#define ERR_RSP_EMPTY_FOOTER_NAME \
|
|
|
+ "<html>" \
|
|
|
+ "<head><title>Request broken</title></head>" \
|
|
|
+ "<body>HTTP request footer has empty footer name.</body>" \
|
|
|
+ "</html>"
|
|
|
+
|
|
|
+/**
|
|
|
+ * Response text used when the request header is too big to be processed.
|
|
|
+ */
|
|
|
+#define ERR_RSP_REQUEST_HEADER_TOO_BIG \
|
|
|
+ "<html>" \
|
|
|
+ "<head><title>Request too big</title></head>" \
|
|
|
+ "<body><p>The total size of the request headers, which includes the " \
|
|
|
+ "request target and the request field lines, exceeds the memory " \
|
|
|
+ "constraints of this web server.</p>" \
|
|
|
+ "<p>The request could be re-tried with shorter field lines, a shorter " \
|
|
|
+ "request target or a shorter request method token.</p></body>" \
|
|
|
+ "</html>"
|
|
|
+
|
|
|
+/**
|
|
|
+ * 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
|
|
|
+ */
|
|
|
+#define HTTP_VER_LEN (mhd_SSTR_LEN(MHD_HTTP_VERSION_1_1_STR))
|
|
|
+
|
|
|
+/**
|
|
|
+ * Detect HTTP version, send error response if version is not supported
|
|
|
+ *
|
|
|
+ * @param connection the connection
|
|
|
+ * @param http_string the pointer to HTTP version string
|
|
|
+ * @param len the length of @a http_string in bytes
|
|
|
+ * @return true if HTTP version is correct and supported,
|
|
|
+ * false if HTTP version is not correct or unsupported.
|
|
|
+ */
|
|
|
+static bool
|
|
|
+parse_http_version (struct MHD_Connection *restrict connection,
|
|
|
+ const char *restrict http_string,
|
|
|
+ size_t len)
|
|
|
+{
|
|
|
+ const char *const h = http_string; /**< short alias */
|
|
|
+ mhd_assert (NULL != http_string);
|
|
|
+
|
|
|
+ /* String must start with 'HTTP/d.d', case-sensetive match.
|
|
|
+ * See https://www.rfc-editor.org/rfc/rfc9112#name-http-version */
|
|
|
+ if ((HTTP_VER_LEN != len) ||
|
|
|
+ ('H' != h[0]) || ('T' != h[1]) || ('T' != h[2]) || ('P' != h[3]) ||
|
|
|
+ ('/' != h[4])
|
|
|
+ || ('.' != h[6]))
|
|
|
+ {
|
|
|
+ connection->rq.http_ver = MHD_HTTP_VERSION_INVALID;
|
|
|
+ transmit_error_response_static (connection,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_REQUEST_MALFORMED);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (1 == h[5] - '0')
|
|
|
+ {
|
|
|
+ /* HTTP/1.x */
|
|
|
+ if (1 == h[7] - '0')
|
|
|
+ {
|
|
|
+ connection->rq.http_ver = MHD_HTTP_VERSION_1_1;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ else if (0 == h[7] - '0')
|
|
|
+ {
|
|
|
+ connection->rq.http_ver = MHD_HTTP_VERSION_1_0;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ connection->rq.http_ver = MHD_HTTP_VERSION_INVALID;
|
|
|
+
|
|
|
+ }
|
|
|
+ else if (0 == h[5] - '0')
|
|
|
+ {
|
|
|
+ /* Too old major version */
|
|
|
+ connection->rq.http_ver = MHD_HTTP_VERSION_INVALID;
|
|
|
+ transmit_error_response_static (connection,
|
|
|
+ MHD_HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED,
|
|
|
+ ERR_RSP_REQ_HTTP_VER_IS_TOO_OLD);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ else if ((2 == h[5] - '0') &&
|
|
|
+ (('0' > h[7]) || ('9' < h[7])))
|
|
|
+ connection->rq.http_ver = MHD_HTTP_VERSION_2;
|
|
|
+ else
|
|
|
+ connection->rq.http_ver = MHD_HTTP_VERSION_INVALID;
|
|
|
+
|
|
|
+ transmit_error_response_static (connection,
|
|
|
+ MHD_HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED,
|
|
|
+ ERR_RSP_REQ_HTTP_VER_IS_NOT_SUPPORTED);
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#ifndef MHD_MAX_EMPTY_LINES_SKIP
|
|
|
+/**
|
|
|
+ * The maximum number of ignored empty line before the request line
|
|
|
+ * at default "strictness" level.
|
|
|
+ */
|
|
|
+#define MHD_MAX_EMPTY_LINES_SKIP 1024
|
|
|
+#endif /* ! MHD_MAX_EMPTY_LINES_SKIP */
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * Find and parse the request line.
|
|
|
+ * @param c the connection to process
|
|
|
+ * @return true if request line completely processed (or unrecoverable error
|
|
|
+ * found) and state is changed,
|
|
|
+ * false if not enough data yet in the receive buffer
|
|
|
+ */
|
|
|
+static MHD_FN_PAR_NONNULL_ALL_ bool
|
|
|
+mhd_get_request_line_inner (struct MHD_Connection *restrict c)
|
|
|
+{
|
|
|
+ size_t p; /**< The current processing position */
|
|
|
+ const int discp_lvl = c->daemon->req_cfg.strictnees;
|
|
|
+ /* Allow to skip one or more empty lines before the request line.
|
|
|
+ RFC 9112, section 2.2 */
|
|
|
+ const bool skip_empty_lines = (1 >= discp_lvl);
|
|
|
+ /* Allow to skip more then one empty line before the request line.
|
|
|
+ RFC 9112, section 2.2 */
|
|
|
+ const bool skip_several_empty_lines = (skip_empty_lines && (0 >= discp_lvl));
|
|
|
+ /* Allow to skip unlimited number of empty lines before the request line.
|
|
|
+ RFC 9112, section 2.2 */
|
|
|
+ const bool skip_unlimited_empty_lines =
|
|
|
+ (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);
|
|
|
+ /* Treat tab as whitespace delimiter.
|
|
|
+ RFC 9112, section 3 */
|
|
|
+ const bool tab_as_wsp = (0 >= discp_lvl);
|
|
|
+ /* Treat VT (vertical tab) and FF (form feed) as whitespace delimiters.
|
|
|
+ RFC 9112, section 3 */
|
|
|
+ const bool other_wsp_as_wsp = (-1 >= discp_lvl);
|
|
|
+ /* Treat continuous whitespace block as a single space.
|
|
|
+ RFC 9112, section 3 */
|
|
|
+ const bool wsp_blocks = (-1 >= discp_lvl);
|
|
|
+ /* Parse whitespace in URI, special parsing of the request line.
|
|
|
+ RFC 9112, section 3.2 */
|
|
|
+ const bool wsp_in_uri = (0 >= discp_lvl);
|
|
|
+ /* Keep whitespace in URI, give app URI with whitespace instead of
|
|
|
+ automatic redirect to fixed URI.
|
|
|
+ Violates RFC 9112, section 3.2 */
|
|
|
+ const bool wsp_in_uri_keep = (-2 >= discp_lvl);
|
|
|
+ /* Keep bare CR character as is.
|
|
|
+ Violates RFC 9112, section 2.2 */
|
|
|
+ const bool bare_cr_keep = (wsp_in_uri_keep && (-3 >= discp_lvl));
|
|
|
+ /* Treat bare CR as space; replace it with space before processing.
|
|
|
+ RFC 9112, section 2.2 */
|
|
|
+ const bool bare_cr_as_sp = ((! bare_cr_keep) && (-1 >= discp_lvl));
|
|
|
+
|
|
|
+ mhd_assert (MHD_CONNECTION_INIT == c->state || \
|
|
|
+ MHD_CONNECTION_REQ_LINE_RECEIVING == c->state);
|
|
|
+ mhd_assert (NULL == c->rq.method || \
|
|
|
+ MHD_CONNECTION_REQ_LINE_RECEIVING == c->state);
|
|
|
+ mhd_assert (mhd_HTTP_METHOD_NO_METHOD == c->rq.http_mthd || \
|
|
|
+ MHD_CONNECTION_REQ_LINE_RECEIVING == c->state);
|
|
|
+ mhd_assert (mhd_HTTP_METHOD_NO_METHOD == c->rq.http_mthd || \
|
|
|
+ 0 != c->rq.hdrs.rq_line.proc_pos);
|
|
|
+
|
|
|
+ if (0 == c->read_buffer_offset)
|
|
|
+ {
|
|
|
+ mhd_assert (MHD_CONNECTION_INIT == c->state);
|
|
|
+ return false; /* No data to process */
|
|
|
+ }
|
|
|
+ p = c->rq.hdrs.rq_line.proc_pos;
|
|
|
+ mhd_assert (p <= c->read_buffer_offset);
|
|
|
+
|
|
|
+ /* Skip empty lines, if any (and if allowed) */
|
|
|
+ /* See RFC 9112, section 2.2 */
|
|
|
+ if ((0 == p)
|
|
|
+ && (skip_empty_lines))
|
|
|
+ {
|
|
|
+ /* Skip empty lines before the request line.
|
|
|
+ See RFC 9112, section 2.2 */
|
|
|
+ bool is_empty_line;
|
|
|
+ mhd_assert (MHD_CONNECTION_INIT == c->state);
|
|
|
+ mhd_assert (NULL == c->rq.method);
|
|
|
+ mhd_assert (NULL == c->rq.url);
|
|
|
+ mhd_assert (0 == c->rq.url_len);
|
|
|
+ mhd_assert (NULL == c->rq.hdrs.rq_line.rq_tgt);
|
|
|
+ mhd_assert (0 == c->rq.req_target_len);
|
|
|
+ mhd_assert (NULL == c->rq.version);
|
|
|
+ do
|
|
|
+ {
|
|
|
+ is_empty_line = false;
|
|
|
+ if ('\r' == c->read_buffer[0])
|
|
|
+ {
|
|
|
+ if (1 == c->read_buffer_offset)
|
|
|
+ return false; /* Not enough data yet */
|
|
|
+ if ('\n' == c->read_buffer[1])
|
|
|
+ {
|
|
|
+ is_empty_line = true;
|
|
|
+ c->read_buffer += 2;
|
|
|
+ c->read_buffer_size -= 2;
|
|
|
+ c->read_buffer_offset -= 2;
|
|
|
+ c->rq.hdrs.rq_line.skipped_empty_lines++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (('\n' == c->read_buffer[0]) &&
|
|
|
+ (bare_lf_as_crlf))
|
|
|
+ {
|
|
|
+ is_empty_line = true;
|
|
|
+ c->read_buffer += 1;
|
|
|
+ c->read_buffer_size -= 1;
|
|
|
+ c->read_buffer_offset -= 1;
|
|
|
+ c->rq.hdrs.rq_line.skipped_empty_lines++;
|
|
|
+ }
|
|
|
+ if (is_empty_line)
|
|
|
+ {
|
|
|
+ if ((! skip_unlimited_empty_lines) &&
|
|
|
+ (((unsigned int) ((skip_several_empty_lines) ?
|
|
|
+ MHD_MAX_EMPTY_LINES_SKIP : 1)) <
|
|
|
+ c->rq.hdrs.rq_line.skipped_empty_lines))
|
|
|
+ {
|
|
|
+ connection_close_error (c,
|
|
|
+ _ ("Too many meaningless extra empty lines " \
|
|
|
+ "received before the request"));
|
|
|
+ return true; /* Process connection closure */
|
|
|
+ }
|
|
|
+ if (0 == c->read_buffer_offset)
|
|
|
+ return false; /* No more data to process */
|
|
|
+ }
|
|
|
+ } while (is_empty_line);
|
|
|
+ }
|
|
|
+ /* All empty lines are skipped */
|
|
|
+
|
|
|
+ c->state = MHD_CONNECTION_REQ_LINE_RECEIVING;
|
|
|
+ /* Read and parse the request line */
|
|
|
+ mhd_assert (1 <= c->read_buffer_offset);
|
|
|
+
|
|
|
+ while (p < c->read_buffer_offset)
|
|
|
+ {
|
|
|
+ char *const restrict read_buffer = c->read_buffer;
|
|
|
+ const char chr = read_buffer[p];
|
|
|
+ bool end_of_line;
|
|
|
+ /*
|
|
|
+ The processing logic is different depending on the configured strictness:
|
|
|
+
|
|
|
+ When whitespace BLOCKS are NOT ALLOWED, the end of the whitespace is
|
|
|
+ processed BEFORE processing of the current character.
|
|
|
+ When whitespace BLOCKS are ALLOWED, the end of the whitespace is
|
|
|
+ processed AFTER processing of the current character.
|
|
|
+
|
|
|
+ When space char in the URI is ALLOWED, the delimiter between the URI and
|
|
|
+ the HTTP version string is processed only at the END of the line.
|
|
|
+ When space in the URI is NOT ALLOWED, the delimiter between the URI and
|
|
|
+ the HTTP version string is processed as soon as the FIRST whitespace is
|
|
|
+ found after URI start.
|
|
|
+ */
|
|
|
+
|
|
|
+ end_of_line = false;
|
|
|
+
|
|
|
+ mhd_assert ((0 == c->rq.hdrs.rq_line.last_ws_end) || \
|
|
|
+ (c->rq.hdrs.rq_line.last_ws_end > \
|
|
|
+ c->rq.hdrs.rq_line.last_ws_start));
|
|
|
+ mhd_assert ((0 == c->rq.hdrs.rq_line.last_ws_start) || \
|
|
|
+ (0 != c->rq.hdrs.rq_line.last_ws_end));
|
|
|
+
|
|
|
+ /* Check for the end of the line */
|
|
|
+ if ('\r' == chr)
|
|
|
+ {
|
|
|
+ if (p + 1 == c->read_buffer_offset)
|
|
|
+ {
|
|
|
+ c->rq.hdrs.rq_line.proc_pos = p;
|
|
|
+ return false; /* Not enough data yet */
|
|
|
+ }
|
|
|
+ else if ('\n' == read_buffer[p + 1])
|
|
|
+ end_of_line = true;
|
|
|
+ else
|
|
|
+ {
|
|
|
+ /* Bare CR alone */
|
|
|
+ /* Must be rejected or replaced with space char.
|
|
|
+ See RFC 9112, section 2.2 */
|
|
|
+ if (bare_cr_as_sp)
|
|
|
+ {
|
|
|
+ read_buffer[p] = ' ';
|
|
|
+ c->rq.num_cr_sp_replaced++;
|
|
|
+ continue; /* Re-start processing of the current character */
|
|
|
+ }
|
|
|
+ else if (! bare_cr_keep)
|
|
|
+ {
|
|
|
+ /* A quick simple check whether this line looks like an HTTP request */
|
|
|
+ if ((mhd_HTTP_METHOD_GET <= c->rq.http_mthd) &&
|
|
|
+ (mhd_HTTP_METHOD_DELETE >= c->rq.http_mthd))
|
|
|
+ {
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_BARE_CR_IN_HEADER);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ connection_close_error (c,
|
|
|
+ _ ("Bare CR characters are not allowed " \
|
|
|
+ "in the request line.\n"));
|
|
|
+ return true; /* Error in the request */
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if ('\n' == chr)
|
|
|
+ {
|
|
|
+ /* Bare LF may be recognised as a line delimiter.
|
|
|
+ See RFC 9112, section 2.2 */
|
|
|
+ if (bare_lf_as_crlf)
|
|
|
+ end_of_line = true;
|
|
|
+ else
|
|
|
+ {
|
|
|
+ /* While RFC does not enforce error for bare LF character,
|
|
|
+ if this char is not treated as a line delimiter, it should be
|
|
|
+ rejected to avoid any security weakness due to request smuggling. */
|
|
|
+ /* A quick simple check whether this line looks like an HTTP request */
|
|
|
+ if ((mhd_HTTP_METHOD_GET <= c->rq.http_mthd) &&
|
|
|
+ (mhd_HTTP_METHOD_DELETE >= c->rq.http_mthd))
|
|
|
+ {
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_BARE_LF_IN_HEADER);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ connection_close_error (c,
|
|
|
+ _ ("Bare LF characters are not allowed " \
|
|
|
+ "in the request line.\n"));
|
|
|
+ return true; /* Error in the request */
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (end_of_line)
|
|
|
+ {
|
|
|
+ /* Handle the end of the request line */
|
|
|
+
|
|
|
+ if (NULL != c->rq.method)
|
|
|
+ {
|
|
|
+ if (wsp_in_uri)
|
|
|
+ {
|
|
|
+ /* The end of the URI and the start of the HTTP version string
|
|
|
+ should be determined now. */
|
|
|
+ mhd_assert (NULL == c->rq.version);
|
|
|
+ mhd_assert (0 == c->rq.req_target_len);
|
|
|
+ if (0 != c->rq.hdrs.rq_line.last_ws_end)
|
|
|
+ {
|
|
|
+ /* Determine the end and the length of the URI */
|
|
|
+ if (NULL != c->rq.hdrs.rq_line.rq_tgt)
|
|
|
+ {
|
|
|
+ read_buffer [c->rq.hdrs.rq_line.last_ws_start] = 0; /* Zero terminate the URI */
|
|
|
+ c->rq.req_target_len =
|
|
|
+ c->rq.hdrs.rq_line.last_ws_start
|
|
|
+ - (size_t) (c->rq.hdrs.rq_line.rq_tgt - read_buffer);
|
|
|
+ }
|
|
|
+ else if ((c->rq.hdrs.rq_line.last_ws_start + 1 <
|
|
|
+ c->rq.hdrs.rq_line.last_ws_end) &&
|
|
|
+ (HTTP_VER_LEN == (p - c->rq.hdrs.rq_line.last_ws_end)))
|
|
|
+ {
|
|
|
+ /* Found only HTTP method and HTTP version and more than one
|
|
|
+ whitespace between them. Assume zero-length URI. */
|
|
|
+ mhd_assert (wsp_blocks);
|
|
|
+ c->rq.hdrs.rq_line.last_ws_start++;
|
|
|
+ read_buffer[c->rq.hdrs.rq_line.last_ws_start] = 0; /* Zero terminate the URI */
|
|
|
+ c->rq.hdrs.rq_line.rq_tgt =
|
|
|
+ read_buffer + c->rq.hdrs.rq_line.last_ws_start;
|
|
|
+ c->rq.req_target_len = 0;
|
|
|
+ c->rq.hdrs.rq_line.num_ws_in_uri = 0;
|
|
|
+ c->rq.hdrs.rq_line.rq_tgt_qmark = NULL;
|
|
|
+ }
|
|
|
+ /* Determine the start of the HTTP version string */
|
|
|
+ if (NULL != c->rq.hdrs.rq_line.rq_tgt)
|
|
|
+ {
|
|
|
+ c->rq.version = read_buffer + c->rq.hdrs.rq_line.last_ws_end;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ /* The end of the URI and the start of the HTTP version string
|
|
|
+ should be already known. */
|
|
|
+ if ((NULL == c->rq.version)
|
|
|
+ && (NULL != c->rq.hdrs.rq_line.rq_tgt)
|
|
|
+ && (HTTP_VER_LEN == p - (size_t) (c->rq.hdrs.rq_line.rq_tgt
|
|
|
+ - read_buffer))
|
|
|
+ && (0 != read_buffer[(size_t)
|
|
|
+ (c->rq.hdrs.rq_line.rq_tgt
|
|
|
+ - read_buffer) - 1]))
|
|
|
+ {
|
|
|
+ /* Found only HTTP method and HTTP version and more than one
|
|
|
+ whitespace between them. Assume zero-length URI. */
|
|
|
+ size_t uri_pos;
|
|
|
+ mhd_assert (wsp_blocks);
|
|
|
+ mhd_assert (0 == c->rq.req_target_len);
|
|
|
+ uri_pos = (size_t) (c->rq.hdrs.rq_line.rq_tgt - read_buffer) - 1;
|
|
|
+ mhd_assert (uri_pos < p);
|
|
|
+ c->rq.version = c->rq.hdrs.rq_line.rq_tgt;
|
|
|
+ read_buffer[uri_pos] = 0; /* Zero terminate the URI */
|
|
|
+ c->rq.hdrs.rq_line.rq_tgt = read_buffer + uri_pos;
|
|
|
+ c->rq.req_target_len = 0;
|
|
|
+ c->rq.hdrs.rq_line.num_ws_in_uri = 0;
|
|
|
+ c->rq.hdrs.rq_line.rq_tgt_qmark = NULL;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (NULL != c->rq.version)
|
|
|
+ {
|
|
|
+ mhd_assert (NULL != c->rq.hdrs.rq_line.rq_tgt);
|
|
|
+ if (! parse_http_version (c, c->rq.version,
|
|
|
+ p
|
|
|
+ - (size_t) (c->rq.version
|
|
|
+ - read_buffer)))
|
|
|
+ {
|
|
|
+ mhd_assert (MHD_CONNECTION_REQ_LINE_RECEIVING < c->state);
|
|
|
+ return true; /* Unsupported / broken HTTP version */
|
|
|
+ }
|
|
|
+ read_buffer[p] = 0; /* Zero terminate the HTTP version strings */
|
|
|
+ if ('\r' == chr)
|
|
|
+ {
|
|
|
+ p++; /* Consume CR */
|
|
|
+ mhd_assert (p < c->read_buffer_offset); /* The next character has been already checked */
|
|
|
+ }
|
|
|
+ p++; /* Consume LF */
|
|
|
+ c->read_buffer += p;
|
|
|
+ c->read_buffer_size -= p;
|
|
|
+ c->read_buffer_offset -= p;
|
|
|
+ mhd_assert (c->rq.hdrs.rq_line.num_ws_in_uri <= \
|
|
|
+ c->rq.req_target_len);
|
|
|
+ mhd_assert ((NULL == c->rq.hdrs.rq_line.rq_tgt_qmark) || \
|
|
|
+ (0 != c->rq.req_target_len));
|
|
|
+ mhd_assert ((NULL == c->rq.hdrs.rq_line.rq_tgt_qmark) || \
|
|
|
+ ((size_t) (c->rq.hdrs.rq_line.rq_tgt_qmark \
|
|
|
+ - c->rq.hdrs.rq_line.rq_tgt) < \
|
|
|
+ c->rq.req_target_len));
|
|
|
+ mhd_assert ((NULL == c->rq.hdrs.rq_line.rq_tgt_qmark) || \
|
|
|
+ (c->rq.hdrs.rq_line.rq_tgt_qmark >= \
|
|
|
+ c->rq.hdrs.rq_line.rq_tgt));
|
|
|
+ return true; /* The request line is successfully parsed */
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /* Error in the request line */
|
|
|
+
|
|
|
+ /* A quick simple check whether this line looks like an HTTP request */
|
|
|
+ if ((mhd_HTTP_METHOD_GET <= c->rq.http_mthd) &&
|
|
|
+ (mhd_HTTP_METHOD_DELETE >= c->rq.http_mthd))
|
|
|
+ {
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_REQUEST_MALFORMED);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ connection_close_error (c,
|
|
|
+ _ ("The request line is malformed.\n"));
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Process possible end of the previously found whitespace delimiter */
|
|
|
+ if ((! wsp_blocks) &&
|
|
|
+ (p == c->rq.hdrs.rq_line.last_ws_end) &&
|
|
|
+ (0 != c->rq.hdrs.rq_line.last_ws_end))
|
|
|
+ {
|
|
|
+ /* Previous character was a whitespace char and whitespace blocks
|
|
|
+ are not allowed. */
|
|
|
+ /* The current position is the next character after
|
|
|
+ a whitespace delimiter */
|
|
|
+ if (NULL == c->rq.hdrs.rq_line.rq_tgt)
|
|
|
+ {
|
|
|
+ /* The current position is the start of the URI */
|
|
|
+ mhd_assert (0 == c->rq.req_target_len);
|
|
|
+ mhd_assert (NULL == c->rq.version);
|
|
|
+ c->rq.hdrs.rq_line.rq_tgt = read_buffer + p;
|
|
|
+ /* Reset the whitespace marker */
|
|
|
+ c->rq.hdrs.rq_line.last_ws_start = 0;
|
|
|
+ c->rq.hdrs.rq_line.last_ws_end = 0;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ /* It was a whitespace after the start of the URI */
|
|
|
+ if (! wsp_in_uri)
|
|
|
+ {
|
|
|
+ mhd_assert ((0 != c->rq.req_target_len) || \
|
|
|
+ (c->rq.hdrs.rq_line.rq_tgt + 1 == read_buffer + p));
|
|
|
+ mhd_assert (NULL == c->rq.version); /* Too many whitespaces? This error is handled at whitespace start */
|
|
|
+ c->rq.version = read_buffer + p;
|
|
|
+ /* Reset the whitespace marker */
|
|
|
+ c->rq.hdrs.rq_line.last_ws_start = 0;
|
|
|
+ c->rq.hdrs.rq_line.last_ws_end = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Process the current character.
|
|
|
+ Is it not the end of the line. */
|
|
|
+ if ((' ' == chr)
|
|
|
+ || (('\t' == chr) && (tab_as_wsp))
|
|
|
+ || ((other_wsp_as_wsp) && ((0xb == chr) || (0xc == chr))))
|
|
|
+ {
|
|
|
+ /* A whitespace character */
|
|
|
+ if ((0 == c->rq.hdrs.rq_line.last_ws_end) ||
|
|
|
+ (p != c->rq.hdrs.rq_line.last_ws_end) ||
|
|
|
+ (! wsp_blocks))
|
|
|
+ {
|
|
|
+ /* Found first whitespace char of the new whitespace block */
|
|
|
+ if (NULL == c->rq.method)
|
|
|
+ {
|
|
|
+ /* Found the end of the HTTP method string */
|
|
|
+ mhd_assert (0 == c->rq.hdrs.rq_line.last_ws_start);
|
|
|
+ mhd_assert (0 == c->rq.hdrs.rq_line.last_ws_end);
|
|
|
+ mhd_assert (NULL == c->rq.hdrs.rq_line.rq_tgt);
|
|
|
+ mhd_assert (0 == c->rq.req_target_len);
|
|
|
+ mhd_assert (NULL == c->rq.version);
|
|
|
+ if (0 == p)
|
|
|
+ {
|
|
|
+ connection_close_error (c,
|
|
|
+ _ ("The request line starts with "
|
|
|
+ "a whitespace.\n"));
|
|
|
+ return true; /* Error in the request */
|
|
|
+ }
|
|
|
+ read_buffer[p] = 0; /* Zero-terminate the request method string */
|
|
|
+ c->rq.method = read_buffer;
|
|
|
+ parse_http_std_method (c, c->rq.method, p);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ /* A whitespace after the start of the URI */
|
|
|
+ if (! wsp_in_uri)
|
|
|
+ {
|
|
|
+ /* Whitespace in URI is not allowed to be parsed */
|
|
|
+ if (NULL == c->rq.version)
|
|
|
+ {
|
|
|
+ mhd_assert (NULL != c->rq.hdrs.rq_line.rq_tgt);
|
|
|
+ /* This is a delimiter between URI and HTTP version string */
|
|
|
+ read_buffer[p] = 0; /* Zero-terminate request URI string */
|
|
|
+ mhd_assert (((size_t) (c->rq.hdrs.rq_line.rq_tgt \
|
|
|
+ - read_buffer)) <= p);
|
|
|
+ c->rq.req_target_len =
|
|
|
+ p - (size_t) (c->rq.hdrs.rq_line.rq_tgt - read_buffer);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ /* This is a delimiter AFTER version string */
|
|
|
+
|
|
|
+ /* A quick simple check whether this line looks like an HTTP request */
|
|
|
+ if ((mhd_HTTP_METHOD_GET <= c->rq.http_mthd) &&
|
|
|
+ (mhd_HTTP_METHOD_DELETE >= c->rq.http_mthd))
|
|
|
+ {
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_RQ_LINE_TOO_MANY_WSP);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ connection_close_error (c,
|
|
|
+ _ ("The request line has more than "
|
|
|
+ "two whitespaces.\n"));
|
|
|
+ return true; /* Error in the request */
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ /* Whitespace in URI is allowed to be parsed */
|
|
|
+ if (0 != c->rq.hdrs.rq_line.last_ws_end)
|
|
|
+ {
|
|
|
+ /* The whitespace after the start of the URI has been found already */
|
|
|
+ c->rq.hdrs.rq_line.num_ws_in_uri +=
|
|
|
+ c->rq.hdrs.rq_line.last_ws_end
|
|
|
+ - c->rq.hdrs.rq_line.last_ws_start;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ c->rq.hdrs.rq_line.last_ws_start = p;
|
|
|
+ c->rq.hdrs.rq_line.last_ws_end = p + 1; /* Will be updated on the next char parsing */
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ /* Continuation of the whitespace block */
|
|
|
+ mhd_assert (0 != c->rq.hdrs.rq_line.last_ws_end);
|
|
|
+ mhd_assert (0 != p);
|
|
|
+ c->rq.hdrs.rq_line.last_ws_end = p + 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ /* Non-whitespace char, not the end of the line */
|
|
|
+ mhd_assert ((0 == c->rq.hdrs.rq_line.last_ws_end) || \
|
|
|
+ (c->rq.hdrs.rq_line.last_ws_end == p) || \
|
|
|
+ wsp_in_uri);
|
|
|
+
|
|
|
+ if ((p == c->rq.hdrs.rq_line.last_ws_end) &&
|
|
|
+ (0 != c->rq.hdrs.rq_line.last_ws_end) &&
|
|
|
+ (wsp_blocks))
|
|
|
+ {
|
|
|
+ /* The end of the whitespace block */
|
|
|
+ if (NULL == c->rq.hdrs.rq_line.rq_tgt)
|
|
|
+ {
|
|
|
+ /* This is the first character of the URI */
|
|
|
+ mhd_assert (0 == c->rq.req_target_len);
|
|
|
+ mhd_assert (NULL == c->rq.version);
|
|
|
+ c->rq.hdrs.rq_line.rq_tgt = read_buffer + p;
|
|
|
+ /* Reset the whitespace marker */
|
|
|
+ c->rq.hdrs.rq_line.last_ws_start = 0;
|
|
|
+ c->rq.hdrs.rq_line.last_ws_end = 0;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (! wsp_in_uri)
|
|
|
+ {
|
|
|
+ /* This is the first character of the HTTP version */
|
|
|
+ mhd_assert (NULL != c->rq.hdrs.rq_line.rq_tgt);
|
|
|
+ mhd_assert ((0 != c->rq.req_target_len) || \
|
|
|
+ (c->rq.hdrs.rq_line.rq_tgt + 1 == read_buffer + p));
|
|
|
+ mhd_assert (NULL == c->rq.version); /* Handled at whitespace start */
|
|
|
+ c->rq.version = read_buffer + p;
|
|
|
+ /* Reset the whitespace marker */
|
|
|
+ c->rq.hdrs.rq_line.last_ws_start = 0;
|
|
|
+ c->rq.hdrs.rq_line.last_ws_end = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Handle other special characters */
|
|
|
+ if ('?' == chr)
|
|
|
+ {
|
|
|
+ if ((NULL == c->rq.hdrs.rq_line.rq_tgt_qmark) &&
|
|
|
+ (NULL != c->rq.hdrs.rq_line.rq_tgt))
|
|
|
+ {
|
|
|
+ c->rq.hdrs.rq_line.rq_tgt_qmark = read_buffer + p;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if ((0xb == chr) || (0xc == chr))
|
|
|
+ {
|
|
|
+ /* VT or LF characters */
|
|
|
+ mhd_assert (! other_wsp_as_wsp);
|
|
|
+ if ((NULL != c->rq.hdrs.rq_line.rq_tgt) &&
|
|
|
+ (NULL == c->rq.version) &&
|
|
|
+ (wsp_in_uri))
|
|
|
+ {
|
|
|
+ c->rq.hdrs.rq_line.num_ws_in_uri++;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ connection_close_error (c,
|
|
|
+ _ ("Invalid character is in the "
|
|
|
+ "request line.\n"));
|
|
|
+ return true; /* Error in the request */
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (0 == chr)
|
|
|
+ {
|
|
|
+ /* NUL character */
|
|
|
+ connection_close_error (c,
|
|
|
+ _ ("The NUL character is in the "
|
|
|
+ "request line.\n"));
|
|
|
+ return true; /* Error in the request */
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ p++;
|
|
|
+ }
|
|
|
+
|
|
|
+ c->rq.hdrs.rq_line.proc_pos = p;
|
|
|
+ return false; /* Not enough data yet */
|
|
|
+}
|
|
|
+
|
|
|
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
|
|
|
+mhd_get_request_line (struct MHD_Connection *restrict c)
|
|
|
+{
|
|
|
+ const int discp_lvl = c->daemon->req_cfg.strictnees;
|
|
|
+ /* Parse whitespace in URI, special parsing of the request line */
|
|
|
+ const bool wsp_in_uri = (0 >= discp_lvl);
|
|
|
+ /* Keep whitespace in URI, give app URI with whitespace instead of
|
|
|
+ automatic redirect to fixed URI */
|
|
|
+ const bool wsp_in_uri_keep = (-2 >= discp_lvl);
|
|
|
+
|
|
|
+ if (! get_request_line_inner (c))
|
|
|
+ {
|
|
|
+ /* End of the request line has not been found yet */
|
|
|
+ mhd_assert ((! wsp_in_uri) || NULL == c->rq.version);
|
|
|
+ if ((NULL != c->rq.version) &&
|
|
|
+ (HTTP_VER_LEN <
|
|
|
+ (c->rq.hdrs.rq_line.proc_pos
|
|
|
+ - (size_t) (c->rq.version - c->read_buffer))))
|
|
|
+ {
|
|
|
+ c->rq.http_ver = MHD_HTTP_VERSION_INVALID;
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_REQUEST_MALFORMED);
|
|
|
+ return true; /* Error in the request */
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (MHD_CONNECTION_REQ_LINE_RECEIVING < c->state)
|
|
|
+ return true; /* Error in the request */
|
|
|
+
|
|
|
+ mhd_assert (MHD_CONNECTION_REQ_LINE_RECEIVING == c->state);
|
|
|
+ mhd_assert (NULL == c->rq.url);
|
|
|
+ mhd_assert (0 == c->rq.url_len);
|
|
|
+ mhd_assert (NULL != c->rq.hdrs.rq_line.rq_tgt);
|
|
|
+ if (0 != c->rq.hdrs.rq_line.num_ws_in_uri)
|
|
|
+ {
|
|
|
+ if (! wsp_in_uri)
|
|
|
+ {
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_RQ_TARGET_INVALID_CHAR);
|
|
|
+ return true; /* Error in the request */
|
|
|
+ }
|
|
|
+ if (! wsp_in_uri_keep)
|
|
|
+ {
|
|
|
+ send_redirect_fixed_rq_target (c);
|
|
|
+ return true; /* Error in the request */
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (! process_request_target (c))
|
|
|
+ return true; /* Error in processing */
|
|
|
+
|
|
|
+ c->state = MHD_CONNECTION_REQ_LINE_RECEIVED;
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void
|
|
|
+mhd_switch_to_rq_headers_processing (struct MHD_Connection *restrict c)
|
|
|
+{
|
|
|
+ c->rq.field_lines.start = c->read_buffer;
|
|
|
+ memset (&c->rq.hdrs.hdr, 0, sizeof(c->rq.hdrs.hdr));
|
|
|
+ c->state = MHD_CONNECTION_REQ_HEADERS_RECEIVING;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Send error reply when receive buffer space exhausted while receiving or
|
|
|
+ * storing the request headers
|
|
|
+ * @param c the connection to handle
|
|
|
+ * @param add_header the optional pointer to the current header string being
|
|
|
+ * processed or the header failed to be added.
|
|
|
+ * Could be not zero-terminated and can contain binary zeros.
|
|
|
+ * Can be NULL.
|
|
|
+ * @param add_header_size the size of the @a add_header
|
|
|
+ */
|
|
|
+static void
|
|
|
+handle_req_headers_no_space (struct MHD_Connection *restrict c,
|
|
|
+ const char *restrict add_header,
|
|
|
+ size_t add_header_size)
|
|
|
+{
|
|
|
+ unsigned int err_code;
|
|
|
+
|
|
|
+ err_code = get_no_space_err_status_code (c,
|
|
|
+ MHD_PROC_RECV_HEADERS,
|
|
|
+ add_header,
|
|
|
+ add_header_size);
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ err_code,
|
|
|
+ ERR_RSP_REQUEST_HEADER_TOO_BIG);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * Results of header line reading
|
|
|
+ */
|
|
|
+enum mhd_HdrLineReadRes
|
|
|
+{
|
|
|
+ /**
|
|
|
+ * Not enough data yet
|
|
|
+ */
|
|
|
+ MHD_HDR_LINE_READING_NEED_MORE_DATA = 0,
|
|
|
+ /**
|
|
|
+ * New header line has been read
|
|
|
+ */
|
|
|
+ MHD_HDR_LINE_READING_GOT_HEADER,
|
|
|
+ /**
|
|
|
+ * Error in header data, error response has been queued
|
|
|
+ */
|
|
|
+ MHD_HDR_LINE_READING_DATA_ERROR,
|
|
|
+ /**
|
|
|
+ * Found the end of the request header (end of field lines)
|
|
|
+ */
|
|
|
+ MHD_HDR_LINE_READING_GOT_END_OF_HEADER
|
|
|
+} _MHD_FIXED_ENUM;
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * Find the end of the request header line and make basic header parsing.
|
|
|
+ * Handle errors and header folding.
|
|
|
+ * @param c the connection to process
|
|
|
+ * @param process_footers if true then footers are processed,
|
|
|
+ * if false then headers are processed
|
|
|
+ * @param[out] hdr_name the name of the parsed header (field)
|
|
|
+ * @param[out] hdr_name the value of the parsed header (field)
|
|
|
+ * @return true if request header line completely processed,
|
|
|
+ * false if not enough data yet in the receive buffer
|
|
|
+ */
|
|
|
+static enum MHD_HdrLineReadRes_
|
|
|
+get_req_header (struct MHD_Connection *restrict c,
|
|
|
+ bool process_footers,
|
|
|
+ struct MHD_String *restrict hdr_name,
|
|
|
+ struct MHD_String *restrict hdr_value)
|
|
|
+{
|
|
|
+ const int discp_lvl = c->daemon->req_cfg.strictnees;
|
|
|
+ /* Treat bare LF as the end of the line.
|
|
|
+ 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);
|
|
|
+ /* Keep bare CR character as is.
|
|
|
+ Violates RFC 9112, section 2.2-4 */
|
|
|
+ const bool bare_cr_keep = (-3 >= discp_lvl);
|
|
|
+ /* Treat bare CR as space; replace it with space before processing.
|
|
|
+ RFC 9112, section 2.2-4 */
|
|
|
+ const bool bare_cr_as_sp = ((! bare_cr_keep) && (-1 >= discp_lvl));
|
|
|
+ /* Treat NUL as space; replace it with space before processing.
|
|
|
+ RFC 9110, section 5.5-5 */
|
|
|
+ const bool nul_as_sp = (-1 >= discp_lvl);
|
|
|
+ /* Allow folded header lines.
|
|
|
+ RFC 9112, section 5.2-4 */
|
|
|
+ const bool allow_folded = (0 >= discp_lvl);
|
|
|
+ /* Do not reject headers with the whitespace at the start of the first line.
|
|
|
+ When allowed, the first line with whitespace character at the first
|
|
|
+ position is ignored (as well as all possible line foldings of the first
|
|
|
+ line).
|
|
|
+ RFC 9112, section 2.2-8 */
|
|
|
+ const bool allow_wsp_at_start = allow_folded && (-1 >= discp_lvl);
|
|
|
+ /* Allow whitespace in header (field) name.
|
|
|
+ Violates RFC 9110, section 5.1-2 */
|
|
|
+ const bool allow_wsp_in_name = (-2 >= discp_lvl);
|
|
|
+ /* Allow zero-length header (field) name.
|
|
|
+ Violates RFC 9110, section 5.1-2 */
|
|
|
+ const bool allow_empty_name = (-2 >= discp_lvl);
|
|
|
+ /* Allow whitespace before colon.
|
|
|
+ Violates RFC 9112, section 5.1-2 */
|
|
|
+ const bool allow_wsp_before_colon = (-3 >= discp_lvl);
|
|
|
+ /* Do not abort the request when header line has no colon, just skip such
|
|
|
+ bad lines.
|
|
|
+ RFC 9112, section 5-1 */
|
|
|
+ const bool allow_line_without_colon = (-2 >= discp_lvl);
|
|
|
+
|
|
|
+ size_t p; /**< The position of the currently processed character */
|
|
|
+
|
|
|
+ (void) process_footers; /* Unused parameter in non-debug and no messages */
|
|
|
+
|
|
|
+ mhd_assert ((process_footers ? MHD_CONNECTION_FOOTERS_RECEIVING : \
|
|
|
+ MHD_CONNECTION_REQ_HEADERS_RECEIVING) == \
|
|
|
+ c->state);
|
|
|
+
|
|
|
+ p = c->rq.hdrs.hdr.proc_pos;
|
|
|
+
|
|
|
+ mhd_assert (p <= c->read_buffer_offset);
|
|
|
+ while (p < c->read_buffer_offset)
|
|
|
+ {
|
|
|
+ char *const restrict read_buffer = c->read_buffer;
|
|
|
+ const char chr = read_buffer[p];
|
|
|
+ bool end_of_line;
|
|
|
+
|
|
|
+ mhd_assert ((0 == c->rq.hdrs.hdr.name_len) || \
|
|
|
+ (c->rq.hdrs.hdr.name_len < p));
|
|
|
+ mhd_assert ((0 == c->rq.hdrs.hdr.name_len) || (0 != p));
|
|
|
+ mhd_assert ((0 == c->rq.hdrs.hdr.name_len) || \
|
|
|
+ (c->rq.hdrs.hdr.name_end_found));
|
|
|
+ mhd_assert ((0 == c->rq.hdrs.hdr.value_start) || \
|
|
|
+ (c->rq.hdrs.hdr.name_len < c->rq.hdrs.hdr.value_start));
|
|
|
+ mhd_assert ((0 == c->rq.hdrs.hdr.value_start) || \
|
|
|
+ (0 != c->rq.hdrs.hdr.name_len));
|
|
|
+ mhd_assert ((0 == c->rq.hdrs.hdr.ws_start) || \
|
|
|
+ (0 == c->rq.hdrs.hdr.name_len) || \
|
|
|
+ (c->rq.hdrs.hdr.ws_start > c->rq.hdrs.hdr.name_len));
|
|
|
+ mhd_assert ((0 == c->rq.hdrs.hdr.ws_start) || \
|
|
|
+ (0 == c->rq.hdrs.hdr.value_start) || \
|
|
|
+ (c->rq.hdrs.hdr.ws_start > c->rq.hdrs.hdr.value_start));
|
|
|
+
|
|
|
+ /* Check for the end of the line */
|
|
|
+ if ('\r' == chr)
|
|
|
+ {
|
|
|
+ if (0 != p)
|
|
|
+ {
|
|
|
+ /* Line is not empty, need to check for possible line folding */
|
|
|
+ if (p + 2 >= c->read_buffer_offset)
|
|
|
+ break; /* Not enough data yet to check for folded line */
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ /* Line is empty, no need to check for possible line folding */
|
|
|
+ if (p + 2 > c->read_buffer_offset)
|
|
|
+ break; /* Not enough data yet to check for the end of the line */
|
|
|
+ }
|
|
|
+ if ('\n' == read_buffer[p + 1])
|
|
|
+ end_of_line = true;
|
|
|
+ else
|
|
|
+ {
|
|
|
+ /* Bare CR alone */
|
|
|
+ /* Must be rejected or replaced with space char.
|
|
|
+ See RFC 9112, section 2.2-4 */
|
|
|
+ if (bare_cr_as_sp)
|
|
|
+ {
|
|
|
+ read_buffer[p] = ' ';
|
|
|
+ c->rq.num_cr_sp_replaced++;
|
|
|
+ continue; /* Re-start processing of the current character */
|
|
|
+ }
|
|
|
+ else if (! bare_cr_keep)
|
|
|
+ {
|
|
|
+ if (! process_footers)
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_BARE_CR_IN_HEADER);
|
|
|
+ else
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_BARE_CR_IN_FOOTER);
|
|
|
+ return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */
|
|
|
+ }
|
|
|
+ end_of_line = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if ('\n' == chr)
|
|
|
+ {
|
|
|
+ /* Bare LF may be recognised as a line delimiter.
|
|
|
+ See RFC 9112, section 2.2-3 */
|
|
|
+ if (bare_lf_as_crlf)
|
|
|
+ {
|
|
|
+ if (0 != p)
|
|
|
+ {
|
|
|
+ /* Line is not empty, need to check for possible line folding */
|
|
|
+ if (p + 1 >= c->read_buffer_offset)
|
|
|
+ break; /* Not enough data yet to check for folded line */
|
|
|
+ }
|
|
|
+ end_of_line = true;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (! process_footers)
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_BARE_LF_IN_HEADER);
|
|
|
+ else
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_BARE_LF_IN_FOOTER);
|
|
|
+ return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ end_of_line = false;
|
|
|
+
|
|
|
+ if (end_of_line)
|
|
|
+ {
|
|
|
+ /* Handle the end of the line */
|
|
|
+ /**
|
|
|
+ * The full length of the line, including CRLF (or bare LF).
|
|
|
+ */
|
|
|
+ const size_t line_len = p + (('\r' == chr) ? 2 : 1);
|
|
|
+ char next_line_char;
|
|
|
+ mhd_assert (line_len <= c->read_buffer_offset);
|
|
|
+
|
|
|
+ if (0 == p)
|
|
|
+ {
|
|
|
+ /* Zero-length header line. This is the end of the request header
|
|
|
+ section.
|
|
|
+ RFC 9112, Section 2.1-1 */
|
|
|
+ mhd_assert (! c->rq.hdrs.hdr.starts_with_ws);
|
|
|
+ mhd_assert (! c->rq.hdrs.hdr.name_end_found);
|
|
|
+ mhd_assert (0 == c->rq.hdrs.hdr.name_len);
|
|
|
+ mhd_assert (0 == c->rq.hdrs.hdr.ws_start);
|
|
|
+ mhd_assert (0 == c->rq.hdrs.hdr.value_start);
|
|
|
+ /* Consume the line with CRLF (or bare LF) */
|
|
|
+ c->read_buffer += line_len;
|
|
|
+ c->read_buffer_offset -= line_len;
|
|
|
+ c->read_buffer_size -= line_len;
|
|
|
+ return MHD_HDR_LINE_READING_GOT_END_OF_HEADER;
|
|
|
+ }
|
|
|
+
|
|
|
+ mhd_assert (line_len < c->read_buffer_offset);
|
|
|
+ mhd_assert (0 != line_len);
|
|
|
+ mhd_assert ('\n' == read_buffer[line_len - 1]);
|
|
|
+ next_line_char = read_buffer[line_len];
|
|
|
+ if ((' ' == next_line_char) ||
|
|
|
+ ('\t' == next_line_char))
|
|
|
+ {
|
|
|
+ /* Folded line */
|
|
|
+ if (! allow_folded)
|
|
|
+ {
|
|
|
+ if (! process_footers)
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_OBS_FOLD);
|
|
|
+ else
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_OBS_FOLD_FOOTER);
|
|
|
+
|
|
|
+ return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */
|
|
|
+ }
|
|
|
+ /* Replace CRLF (or bare LF) character(s) with space characters.
|
|
|
+ See RFC 9112, Section 5.2-4 */
|
|
|
+ read_buffer[p] = ' ';
|
|
|
+ if ('\r' == chr)
|
|
|
+ read_buffer[p + 1] = ' ';
|
|
|
+ continue; /* Re-start processing of the current character */
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ /* It is not a folded line, it's the real end of the non-empty line */
|
|
|
+ bool skip_line = false;
|
|
|
+ mhd_assert (0 != p);
|
|
|
+ if (c->rq.hdrs.hdr.starts_with_ws)
|
|
|
+ {
|
|
|
+ /* This is the first line and it starts with whitespace. This line
|
|
|
+ must be discarded completely.
|
|
|
+ See RFC 9112, Section 2.2-8 */
|
|
|
+ mhd_assert (allow_wsp_at_start);
|
|
|
+
|
|
|
+ MHD_LOG_MSG (c->daemon, MHD_SC_REQ_FIRST_HEADER_LINE_SPACE_PREFIXED,
|
|
|
+ "Whitespace-prefixed first header line " \
|
|
|
+ "has been skipped.");
|
|
|
+ skip_line = true;
|
|
|
+ }
|
|
|
+ else if (! c->rq.hdrs.hdr.name_end_found)
|
|
|
+ {
|
|
|
+ if (! allow_line_without_colon)
|
|
|
+ {
|
|
|
+ if (! process_footers)
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_HEADER_WITHOUT_COLON);
|
|
|
+ else
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_FOOTER_WITHOUT_COLON);
|
|
|
+
|
|
|
+ return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */
|
|
|
+ }
|
|
|
+ /* Skip broken line completely */
|
|
|
+ c->rq.skipped_broken_lines++;
|
|
|
+ skip_line = true;
|
|
|
+ }
|
|
|
+ if (skip_line)
|
|
|
+ {
|
|
|
+ /* Skip the entire line */
|
|
|
+ c->read_buffer += line_len;
|
|
|
+ c->read_buffer_offset -= line_len;
|
|
|
+ c->read_buffer_size -= line_len;
|
|
|
+ p = 0;
|
|
|
+ /* Reset processing state */
|
|
|
+ memset (&c->rq.hdrs.hdr, 0, sizeof(c->rq.hdrs.hdr));
|
|
|
+ /* Start processing of the next line */
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ /* This line should be valid header line */
|
|
|
+ size_t value_len;
|
|
|
+ mhd_assert ((0 != c->rq.hdrs.hdr.name_len) || allow_empty_name);
|
|
|
+
|
|
|
+ hdr_name->cstr = read_buffer + 0; /* The name always starts at the first character */
|
|
|
+ hdr_name->len = c->rq.hdrs.hdr.name_len;
|
|
|
+ mhd_assert (0 == hdr_name->cstr[hdr_name->len]);
|
|
|
+
|
|
|
+ if (0 == c->rq.hdrs.hdr.value_start)
|
|
|
+ {
|
|
|
+ c->rq.hdrs.hdr.value_start = p;
|
|
|
+ read_buffer[p] = 0;
|
|
|
+ value_len = 0;
|
|
|
+ }
|
|
|
+ else if (0 != c->rq.hdrs.hdr.ws_start)
|
|
|
+ {
|
|
|
+ mhd_assert (p > c->rq.hdrs.hdr.ws_start);
|
|
|
+ mhd_assert (c->rq.hdrs.hdr.ws_start > c->rq.hdrs.hdr.value_start);
|
|
|
+ read_buffer[c->rq.hdrs.hdr.ws_start] = 0;
|
|
|
+ value_len = c->rq.hdrs.hdr.ws_start - c->rq.hdrs.hdr.value_start;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ mhd_assert (p > c->rq.hdrs.hdr.ws_start);
|
|
|
+ read_buffer[p] = 0;
|
|
|
+ value_len = p - c->rq.hdrs.hdr.value_start;
|
|
|
+ }
|
|
|
+ hdr_value->cstr = read_buffer + c->rq.hdrs.hdr.value_start;
|
|
|
+ hdr_value->len = value_len;
|
|
|
+ mhd_assert (0 == hdr_value->cstr[hdr_value->len]);
|
|
|
+ /* Consume the entire line */
|
|
|
+ c->read_buffer += line_len;
|
|
|
+ c->read_buffer_offset -= line_len;
|
|
|
+ c->read_buffer_size -= line_len;
|
|
|
+ return MHD_HDR_LINE_READING_GOT_HEADER;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if ((' ' == chr) || ('\t' == chr))
|
|
|
+ {
|
|
|
+ if (0 == p)
|
|
|
+ {
|
|
|
+ if (! allow_wsp_at_start)
|
|
|
+ {
|
|
|
+ if (! process_footers)
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_WSP_BEFORE_HEADER);
|
|
|
+ else
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_WSP_BEFORE_FOOTER);
|
|
|
+ return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */
|
|
|
+ }
|
|
|
+ c->rq.hdrs.hdr.starts_with_ws = true;
|
|
|
+ }
|
|
|
+ else if ((! c->rq.hdrs.hdr.name_end_found) &&
|
|
|
+ (! c->rq.hdrs.hdr.starts_with_ws))
|
|
|
+ {
|
|
|
+ /* Whitespace in header name / between header name and colon */
|
|
|
+ if (allow_wsp_in_name || allow_wsp_before_colon)
|
|
|
+ {
|
|
|
+ if (0 == c->rq.hdrs.hdr.ws_start)
|
|
|
+ c->rq.hdrs.hdr.ws_start = p;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (! process_footers)
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_WSP_IN_HEADER_NAME);
|
|
|
+ else
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_WSP_IN_FOOTER_NAME);
|
|
|
+
|
|
|
+ return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ /* Whitespace before/inside/after header (field) value */
|
|
|
+ if (0 == c->rq.hdrs.hdr.ws_start)
|
|
|
+ c->rq.hdrs.hdr.ws_start = p;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (0 == chr)
|
|
|
+ {
|
|
|
+ if (! nul_as_sp)
|
|
|
+ {
|
|
|
+ if (! process_footers)
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_INVALID_CHR_IN_HEADER);
|
|
|
+ else
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_INVALID_CHR_IN_FOOTER);
|
|
|
+
|
|
|
+ return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */
|
|
|
+ }
|
|
|
+ read_buffer[p] = ' ';
|
|
|
+ continue; /* Re-start processing of the current character */
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ /* Not a whitespace, not the end of the header line */
|
|
|
+ mhd_assert ('\r' != chr);
|
|
|
+ mhd_assert ('\n' != chr);
|
|
|
+ mhd_assert ('\0' != chr);
|
|
|
+ if ((! c->rq.hdrs.hdr.name_end_found) &&
|
|
|
+ (! c->rq.hdrs.hdr.starts_with_ws))
|
|
|
+ {
|
|
|
+ /* Processing the header (field) name */
|
|
|
+ if (':' == chr)
|
|
|
+ {
|
|
|
+ if (0 == c->rq.hdrs.hdr.ws_start)
|
|
|
+ c->rq.hdrs.hdr.name_len = p;
|
|
|
+ else
|
|
|
+ {
|
|
|
+ mhd_assert (allow_wsp_in_name || allow_wsp_before_colon);
|
|
|
+ if (! allow_wsp_before_colon)
|
|
|
+ {
|
|
|
+ if (! process_footers)
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_WSP_IN_HEADER_NAME);
|
|
|
+ else
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_WSP_IN_FOOTER_NAME);
|
|
|
+ return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */
|
|
|
+ }
|
|
|
+ c->rq.hdrs.hdr.name_len = c->rq.hdrs.hdr.ws_start;
|
|
|
+#ifndef MHD_FAVOR_SMALL_CODE
|
|
|
+ c->rq.hdrs.hdr.ws_start = 0; /* Not on whitespace anymore */
|
|
|
+#endif /* ! MHD_FAVOR_SMALL_CODE */
|
|
|
+ }
|
|
|
+ if ((0 == c->rq.hdrs.hdr.name_len) && ! allow_empty_name)
|
|
|
+ {
|
|
|
+ if (! process_footers)
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_EMPTY_HEADER_NAME);
|
|
|
+ else
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_EMPTY_FOOTER_NAME);
|
|
|
+ return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */
|
|
|
+ }
|
|
|
+ c->rq.hdrs.hdr.name_end_found = true;
|
|
|
+ read_buffer[c->rq.hdrs.hdr.name_len] = 0; /* Zero-terminate the name */
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (0 != c->rq.hdrs.hdr.ws_start)
|
|
|
+ {
|
|
|
+ /* End of the whitespace in header (field) name */
|
|
|
+ mhd_assert (allow_wsp_in_name || allow_wsp_before_colon);
|
|
|
+ if (! allow_wsp_in_name)
|
|
|
+ {
|
|
|
+ if (! process_footers)
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_WSP_IN_HEADER_NAME);
|
|
|
+ else
|
|
|
+ transmit_error_response_static (c,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ ERR_RSP_WSP_IN_FOOTER_NAME);
|
|
|
+
|
|
|
+ return MHD_HDR_LINE_READING_DATA_ERROR; /* Error in the request */
|
|
|
+ }
|
|
|
+#ifndef MHD_FAVOR_SMALL_CODE
|
|
|
+ c->rq.hdrs.hdr.ws_start = 0; /* Not on whitespace anymore */
|
|
|
+#endif /* ! MHD_FAVOR_SMALL_CODE */
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ /* Processing the header (field) value */
|
|
|
+ if (0 == c->rq.hdrs.hdr.value_start)
|
|
|
+ c->rq.hdrs.hdr.value_start = p;
|
|
|
+#ifndef MHD_FAVOR_SMALL_CODE
|
|
|
+ c->rq.hdrs.hdr.ws_start = 0; /* Not on whitespace anymore */
|
|
|
+#endif /* ! MHD_FAVOR_SMALL_CODE */
|
|
|
+ }
|
|
|
+#ifdef MHD_FAVOR_SMALL_CODE
|
|
|
+ c->rq.hdrs.hdr.ws_start = 0; /* Not on whitespace anymore */
|
|
|
+#endif /* MHD_FAVOR_SMALL_CODE */
|
|
|
+ }
|
|
|
+ p++;
|
|
|
+ }
|
|
|
+ c->rq.hdrs.hdr.proc_pos = p;
|
|
|
+ return MHD_HDR_LINE_READING_NEED_MORE_DATA; /* Not enough data yet */
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
|
|
|
+mhd_get_req_headers (struct MHD_Connection *restrict c, bool process_footers)
|
|
|
+{
|
|
|
+ do
|
|
|
+ {
|
|
|
+ struct MHD_String hdr_name;
|
|
|
+ struct MHD_String hdr_value;
|
|
|
+ enum mhd_HdrLineReadRes res;
|
|
|
+
|
|
|
+ mhd_assert ((process_footers ? MHD_CONNECTION_FOOTERS_RECEIVING : \
|
|
|
+ MHD_CONNECTION_REQ_HEADERS_RECEIVING) == \
|
|
|
+ c->state);
|
|
|
+
|
|
|
+#ifndef NDEBUG
|
|
|
+ hdr_name.cstr = NULL;
|
|
|
+ hdr_value.cstr = NULL;
|
|
|
+#endif /* ! NDEBUG */
|
|
|
+ res = get_req_header (c, process_footers, &hdr_name, &hdr_value);
|
|
|
+ if (MHD_HDR_LINE_READING_GOT_HEADER == res)
|
|
|
+ {
|
|
|
+ mhd_assert ((process_footers ? MHD_CONNECTION_FOOTERS_RECEIVING : \
|
|
|
+ MHD_CONNECTION_REQ_HEADERS_RECEIVING) == \
|
|
|
+ c->state);
|
|
|
+ mhd_assert (NULL != hdr_name.cstr);
|
|
|
+ mhd_assert (NULL != hdr_value.cstr);
|
|
|
+ /* Values must be zero-terminated and must not have binary zeros */
|
|
|
+ mhd_assert (strlen (hdr_name.cstr) == hdr_name.len);
|
|
|
+ mhd_assert (strlen (hdr_value.cstr) == hdr_value.len);
|
|
|
+ /* Values must not have whitespaces at the start or at the end */
|
|
|
+ mhd_assert ((hdr_name.len == 0) || (hdr_name.cstr[0] != ' '));
|
|
|
+ mhd_assert ((hdr_name.len == 0) || (hdr_name.cstr[0] != '\t'));
|
|
|
+ mhd_assert ((hdr_name.len == 0) || \
|
|
|
+ (hdr_name.cstr[hdr_name.len - 1] != ' '));
|
|
|
+ mhd_assert ((hdr_name.len == 0) || \
|
|
|
+ (hdr_name.cstr[hdr_name.len - 1] != '\t'));
|
|
|
+ mhd_assert ((hdr_value.len == 0) || (hdr_value.cstr[0] != ' '));
|
|
|
+ mhd_assert ((hdr_value.len == 0) || (hdr_value.cstr[0] != '\t'));
|
|
|
+ mhd_assert ((hdr_value.len == 0) || \
|
|
|
+ (hdr_value.cstr[hdr_value.len - 1] != ' '));
|
|
|
+ mhd_assert ((hdr_value.len == 0) || \
|
|
|
+ (hdr_value.cstr[hdr_value.len - 1] != '\t'));
|
|
|
+
|
|
|
+ if (! mhd_stream_add_field (c,
|
|
|
+ process_footers ?
|
|
|
+ MHD_VK_FOOTER : MHD_VK_HEADER,
|
|
|
+ &hdr_name,
|
|
|
+ &hdr_value))
|
|
|
+ {
|
|
|
+ size_t add_element_size;
|
|
|
+
|
|
|
+ mhd_assert (hdr_name.cstr < hdr_value.cstr);
|
|
|
+
|
|
|
+ if (!process_footers)
|
|
|
+ MHD_LOG_MSG (c->daemon, MHD_SC_CONNECTION_POOL_MALLOC_FAILURE_REQ, \
|
|
|
+ "Failed to allocate memory in the connection memory " \
|
|
|
+ "pool to store header.");
|
|
|
+ else
|
|
|
+ MHD_LOG_MSG (c->daemon, MHD_SC_CONNECTION_POOL_MALLOC_FAILURE_REQ, \
|
|
|
+ "Failed to allocate memory in the connection memory " \
|
|
|
+ "pool to store footer.");
|
|
|
+
|
|
|
+ add_element_size = hdr_value.len
|
|
|
+ + (size_t) (hdr_value.cstr - hdr_name.cstr);
|
|
|
+
|
|
|
+ if (! process_footers)
|
|
|
+ handle_req_headers_no_space (c, hdr_name.cstr, add_element_size);
|
|
|
+ else
|
|
|
+ handle_req_footers_no_space (c, hdr_name.cstr, add_element_size);
|
|
|
+
|
|
|
+ mhd_assert (MHD_CONNECTION_FULL_REQ_RECEIVED < c->state);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ /* Reset processing state */
|
|
|
+ reset_rq_header_processing_state (c);
|
|
|
+ mhd_assert ((process_footers ? MHD_CONNECTION_FOOTERS_RECEIVING : \
|
|
|
+ MHD_CONNECTION_REQ_HEADERS_RECEIVING) == \
|
|
|
+ c->state);
|
|
|
+ /* Read the next header (field) line */
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ else if (MHD_HDR_LINE_READING_NEED_MORE_DATA == res)
|
|
|
+ {
|
|
|
+ mhd_assert ((process_footers ? MHD_CONNECTION_FOOTERS_RECEIVING : \
|
|
|
+ MHD_CONNECTION_REQ_HEADERS_RECEIVING) == \
|
|
|
+ c->state);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ else if (MHD_HDR_LINE_READING_DATA_ERROR == res)
|
|
|
+ {
|
|
|
+ mhd_assert ((process_footers ? \
|
|
|
+ MHD_CONNECTION_FOOTERS_RECEIVING : \
|
|
|
+ MHD_CONNECTION_REQ_HEADERS_RECEIVING) < c->state);
|
|
|
+ mhd_assert (c->stop_with_error);
|
|
|
+ mhd_assert (c->discard_request);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ mhd_assert (MHD_HDR_LINE_READING_GOT_END_OF_HEADER == res);
|
|
|
+ break;
|
|
|
+ } while (1);
|
|
|
+
|
|
|
+ if (1 == c->rq.num_cr_sp_replaced)
|
|
|
+ {
|
|
|
+ if (! process_footers)
|
|
|
+ MHD_LOG_MSG (c->daemon, MHD_SC_REQ_HEADER_CR_REPLACED, \
|
|
|
+ "One bare CR character has been replaced with space " \
|
|
|
+ "in the request line or in the request headers.");
|
|
|
+ else
|
|
|
+ MHD_LOG_MSG (c->daemon, MHD_SC_REQ_FOOTER_CR_REPLACED, \
|
|
|
+ "One bare CR character has been replaced with space " \
|
|
|
+ "in the request footers.");
|
|
|
+ }
|
|
|
+ else if (0 != c->rq.num_cr_sp_replaced)
|
|
|
+ {
|
|
|
+ if (! process_footers)
|
|
|
+ MHD_LOG_PRINT (c->daemon, MHD_SC_REQ_HEADER_CR_REPLACED, \
|
|
|
+ MHD_LOG_FMT("%" PRIuFAST64 " bare CR characters have " \
|
|
|
+ "been replaced with spaces in the request " \
|
|
|
+ "line and/or in the request headers."), \
|
|
|
+ (uint_fast64_t) c->rq.num_cr_sp_replaced);
|
|
|
+ else
|
|
|
+ MHD_LOG_PRINT (c->daemon, MHD_SC_REQ_HEADER_CR_REPLACED, \
|
|
|
+ MHD_LOG_FMT("%" PRIuFAST64 " bare CR characters have " \
|
|
|
+ "been replaced with spaces in the request " \
|
|
|
+ "footers."), \
|
|
|
+ (uint_fast64_t) c->rq.num_cr_sp_replaced);
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+ if (1 == c->rq.skipped_broken_lines)
|
|
|
+ {
|
|
|
+ if (! process_footers)
|
|
|
+ MHD_LOG_MSG (c->daemon, MHD_SC_REQ_HEADER_LINE_NO_COLON, \
|
|
|
+ "One header line without colon has been skipped.");
|
|
|
+ else
|
|
|
+ MHD_LOG_MSG (c->daemon, MHD_SC_REQ_FOOTER_LINE_NO_COLON, \
|
|
|
+ "One footer line without colon has been skipped.");
|
|
|
+ }
|
|
|
+ else if (0 != c->rq.skipped_broken_lines)
|
|
|
+ {
|
|
|
+ if (! process_footers)
|
|
|
+ MHD_LOG_PRINT (c->daemon, MHD_SC_REQ_HEADER_CR_REPLACED, \
|
|
|
+ MHD_LOG_FMT("%" PRIu64 " header lines without colons "
|
|
|
+ "have been skipped."),
|
|
|
+ (uint_fast64_t) c->rq.skipped_broken_lines);
|
|
|
+ else
|
|
|
+ MHD_LOG_PRINT (c->daemon, MHD_SC_REQ_HEADER_CR_REPLACED, \
|
|
|
+ MHD_LOG_FMT("%" PRIu64 " footer lines without colons "
|
|
|
+ "have been skipped."),
|
|
|
+ (uint_fast64_t) c->rq.skipped_broken_lines);
|
|
|
+ }
|
|
|
+
|
|
|
+ mhd_assert (c->rq.method < c->read_buffer);
|
|
|
+ if (! process_footers)
|
|
|
+ {
|
|
|
+ c->rq.header_size = (size_t) (c->read_buffer - c->rq.method);
|
|
|
+ mhd_assert (NULL != c->rq.field_lines.start);
|
|
|
+ c->rq.field_lines.size =
|
|
|
+ (size_t) ((c->read_buffer - c->rq.field_lines.start) - 1);
|
|
|
+ if ('\r' == *(c->read_buffer - 2))
|
|
|
+ c->rq.field_lines.size--;
|
|
|
+ c->state = MHD_CONNECTION_HEADERS_RECEIVED;
|
|
|
+
|
|
|
+ if (mhd_BUF_INC_SIZE > c->read_buffer_size)
|
|
|
+ {
|
|
|
+ /* Try to re-use some of the last bytes of the request header */
|
|
|
+ /* Do this only if space in the read buffer is limited AND
|
|
|
+ amount of read ahead data is small. */
|
|
|
+ /**
|
|
|
+ * The position of the terminating NUL after the last character of
|
|
|
+ * the last header element.
|
|
|
+ */
|
|
|
+ const char *last_elmnt_end;
|
|
|
+ size_t shift_back_size;
|
|
|
+ struct mhd_RequestField *header;
|
|
|
+ header = mhd_DLINKEDL_GET_LAST(&(c->rq), fields);
|
|
|
+ if (NULL != header)
|
|
|
+ last_elmnt_end =
|
|
|
+ header->field.nv.value.cstr + header->field.nv.value.len;
|
|
|
+ else
|
|
|
+ last_elmnt_end = c->rq.version + HTTP_VER_LEN;
|
|
|
+ mhd_assert ((last_elmnt_end + 1) < c->read_buffer);
|
|
|
+ shift_back_size = (size_t) (c->read_buffer - (last_elmnt_end + 1));
|
|
|
+ if (0 != c->read_buffer_offset)
|
|
|
+ memmove (c->read_buffer - shift_back_size,
|
|
|
+ c->read_buffer,
|
|
|
+ c->read_buffer_offset);
|
|
|
+ c->read_buffer -= shift_back_size;
|
|
|
+ c->read_buffer_size += shift_back_size;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ c->state = MHD_CONNECTION_FOOTERS_RECEIVED;
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#ifdef COOKIE_SUPPORT
|
|
|
+
|
|
|
+/**
|
|
|
+ * Cookie parsing result
|
|
|
+ */
|
|
|
+enum _MHD_ParseCookie
|
|
|
+{
|
|
|
+ MHD_PARSE_COOKIE_OK = MHD_YES, /**< Success or no cookies in headers */
|
|
|
+ MHD_PARSE_COOKIE_OK_LAX = 2, /**< Cookies parsed, but workarounds used */
|
|
|
+ MHD_PARSE_COOKIE_MALFORMED = -1, /**< Invalid cookie header */
|
|
|
+ MHD_PARSE_COOKIE_NO_MEMORY = MHD_NO /**< Not enough memory in the pool */
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * Parse the cookies string (see RFC 6265).
|
|
|
+ *
|
|
|
+ * Try to parse the cookies string even if it is not strictly formed
|
|
|
+ * as specified by RFC 6265.
|
|
|
+ *
|
|
|
+ * @param str the string to parse, without leading whitespaces
|
|
|
+ * @param str_len the size of the @a str, not including mandatory
|
|
|
+ * zero-termination
|
|
|
+ * @param connection the connection to add parsed cookies
|
|
|
+ * @return #MHD_PARSE_COOKIE_OK for success, error code otherwise
|
|
|
+ */
|
|
|
+static MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_CSTR_(2)
|
|
|
+MHD_FN_PAR_INOUT_SIZE_(2,1) enum _MHD_ParseCookie
|
|
|
+parse_cookies_string (const size_t str_len,
|
|
|
+ char *restrict str,
|
|
|
+ struct MHD_Connection *restrict connection)
|
|
|
+{
|
|
|
+ size_t i;
|
|
|
+ bool non_strict;
|
|
|
+ /* Skip extra whitespaces and empty cookies */
|
|
|
+ const bool allow_wsp_empty = (0 >= connection->daemon->req_cfg.strictnees);
|
|
|
+ /* Allow whitespaces around '=' character */
|
|
|
+ const bool wsp_around_eq = (-3 >= connection->daemon->req_cfg.strictnees);
|
|
|
+ /* Allow whitespaces in quoted cookie value */
|
|
|
+ const bool wsp_in_quoted = (-2 >= connection->daemon->req_cfg.strictnees);
|
|
|
+ /* Allow tab as space after semicolon between cookies */
|
|
|
+ const bool tab_as_sp = (0 >= connection->daemon->req_cfg.strictnees);
|
|
|
+ /* Allow no space after semicolon between cookies */
|
|
|
+ const bool allow_no_space = (0 >= connection->daemon->req_cfg.strictnees);
|
|
|
+
|
|
|
+ non_strict = false;
|
|
|
+ i = 0;
|
|
|
+ while (i < str_len)
|
|
|
+ {
|
|
|
+ size_t name_start;
|
|
|
+ size_t name_len;
|
|
|
+ size_t value_start;
|
|
|
+ size_t value_len;
|
|
|
+ bool val_quoted;
|
|
|
+ /* Skip any whitespaces and empty cookies */
|
|
|
+ while (' ' == str[i] || '\t' == str[i] || ';' == str[i])
|
|
|
+ {
|
|
|
+ if (! allow_wsp_empty)
|
|
|
+ return MHD_PARSE_COOKIE_MALFORMED;
|
|
|
+ non_strict = true;
|
|
|
+ i++;
|
|
|
+ if (i == str_len)
|
|
|
+ return non_strict? MHD_PARSE_COOKIE_OK_LAX : MHD_PARSE_COOKIE_OK;
|
|
|
+ }
|
|
|
+ /* 'i' must point to the first char of cookie-name */
|
|
|
+ name_start = i;
|
|
|
+ /* Find the end of the cookie-name */
|
|
|
+ do
|
|
|
+ {
|
|
|
+ const char l = str[i];
|
|
|
+ if (('=' == l) || (' ' == l) || ('\t' == l) || ('"' == l) || (',' == l) ||
|
|
|
+ (';' == l) || (0 == l))
|
|
|
+ break;
|
|
|
+ } while (str_len > ++i);
|
|
|
+ name_len = i - name_start;
|
|
|
+ /* Skip any whitespaces */
|
|
|
+ while (str_len > i && (' ' == str[i] || '\t' == str[i]))
|
|
|
+ {
|
|
|
+ if (! wsp_around_eq)
|
|
|
+ return MHD_PARSE_COOKIE_MALFORMED;
|
|
|
+ non_strict = true;
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+ if ((str_len == i) || ('=' != str[i]) || (0 == name_len))
|
|
|
+ return MHD_PARSE_COOKIE_MALFORMED; /* Incomplete cookie name */
|
|
|
+ /* 'i' must point to the '=' char */
|
|
|
+ mhd_assert ('=' == str[i]);
|
|
|
+ i++;
|
|
|
+ /* Skip any whitespaces */
|
|
|
+ while (str_len > i && (' ' == str[i] || '\t' == str[i]))
|
|
|
+ {
|
|
|
+ if (! wsp_around_eq)
|
|
|
+ return MHD_PARSE_COOKIE_MALFORMED;
|
|
|
+ non_strict = true;
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+ /* 'i' must point to the first char of cookie-value */
|
|
|
+ if (str_len == i)
|
|
|
+ {
|
|
|
+ value_start = 0;
|
|
|
+ value_len = 0;
|
|
|
+#ifndef NDEBUG
|
|
|
+ val_quoted = false; /* This assignment used in assert */
|
|
|
+#endif
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ bool valid_cookie;
|
|
|
+ val_quoted = ('"' == str[i]);
|
|
|
+ if (val_quoted)
|
|
|
+ i++;
|
|
|
+ value_start = i;
|
|
|
+ /* Find the end of the cookie-value */
|
|
|
+ while (str_len > i)
|
|
|
+ {
|
|
|
+ const char l = str[i];
|
|
|
+ if ((';' == l) || ('"' == l) || (',' == l) || (';' == l) ||
|
|
|
+ ('\\' == l) || (0 == l))
|
|
|
+ break;
|
|
|
+ if ((' ' == l) || ('\t' == l))
|
|
|
+ {
|
|
|
+ if (! val_quoted)
|
|
|
+ break;
|
|
|
+ if (! wsp_in_quoted)
|
|
|
+ return MHD_PARSE_COOKIE_MALFORMED;
|
|
|
+ non_strict = true;
|
|
|
+ }
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+ value_len = i - value_start;
|
|
|
+ if (val_quoted)
|
|
|
+ {
|
|
|
+ if ((str_len == i) || ('"' != str[i]))
|
|
|
+ return MHD_PARSE_COOKIE_MALFORMED; /* Incomplete cookie value, no closing quote */
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+ /* Skip any whitespaces */
|
|
|
+ if ((str_len > i) && ((' ' == str[i]) || ('\t' == str[i])))
|
|
|
+ {
|
|
|
+ do
|
|
|
+ {
|
|
|
+ i++;
|
|
|
+ } while (str_len > i && (' ' == str[i] || '\t' == str[i]));
|
|
|
+ /* Whitespace at the end? */
|
|
|
+ if (str_len > i)
|
|
|
+ {
|
|
|
+ if (! allow_wsp_empty)
|
|
|
+ return MHD_PARSE_COOKIE_MALFORMED;
|
|
|
+ non_strict = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (str_len == i)
|
|
|
+ valid_cookie = true;
|
|
|
+ else if (';' == str[i])
|
|
|
+ valid_cookie = true;
|
|
|
+ else
|
|
|
+ valid_cookie = false;
|
|
|
+
|
|
|
+ if (! valid_cookie)
|
|
|
+ return MHD_PARSE_COOKIE_MALFORMED; /* Garbage at the end of the cookie value */
|
|
|
+ }
|
|
|
+ mhd_assert (0 != name_len);
|
|
|
+ str[name_start + name_len] = 0; /* Zero-terminate the name */
|
|
|
+ if (0 != value_len)
|
|
|
+ {
|
|
|
+ struct MHD_String name;
|
|
|
+ struct MHD_String value;
|
|
|
+ mhd_assert (value_start + value_len <= str_len);
|
|
|
+ name.cstr = str + name_start;
|
|
|
+ name.len = name_len;
|
|
|
+ str[value_start + value_len] = 0; /* Zero-terminate the value */
|
|
|
+ value.cstr = str + value_start;
|
|
|
+ value.len = value_len;
|
|
|
+ if (! mhd_stream_add_field(connection,
|
|
|
+ MHD_VK_COOKIE,
|
|
|
+ &name,
|
|
|
+ &value))
|
|
|
+ return MHD_PARSE_COOKIE_NO_MEMORY;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ struct MHD_String name;
|
|
|
+ struct MHD_String value;
|
|
|
+ name.cstr = str + name_start;
|
|
|
+ name.len = name_len;
|
|
|
+ value.cstr = ""
|
|
|
+ value.len = 0;
|
|
|
+ if (! mhd_stream_add_field(connection,
|
|
|
+ MHD_VK_COOKIE,
|
|
|
+ &name,
|
|
|
+ &value))
|
|
|
+ return MHD_PARSE_COOKIE_NO_MEMORY;
|
|
|
+ }
|
|
|
+ if (str_len > i)
|
|
|
+ {
|
|
|
+ mhd_assert (0 == str[i] || ';' == str[i]);
|
|
|
+ mhd_assert (! val_quoted || ';' == str[i]);
|
|
|
+ mhd_assert (';' != str[i] || val_quoted || non_strict || 0 == value_len);
|
|
|
+ i++;
|
|
|
+ if (str_len == i)
|
|
|
+ { /* No next cookie after semicolon */
|
|
|
+ if (! allow_wsp_empty)
|
|
|
+ return MHD_PARSE_COOKIE_MALFORMED;
|
|
|
+ non_strict = true;
|
|
|
+ }
|
|
|
+ else if (' ' != str[i])
|
|
|
+ {/* No space after semicolon */
|
|
|
+ if (('\t' == str[i]) && tab_as_sp)
|
|
|
+ i++;
|
|
|
+ else if (! allow_no_space)
|
|
|
+ return MHD_PARSE_COOKIE_MALFORMED;
|
|
|
+ non_strict = true;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ i++;
|
|
|
+ if (str_len == i)
|
|
|
+ {
|
|
|
+ if (! allow_wsp_empty)
|
|
|
+ return MHD_PARSE_COOKIE_MALFORMED;
|
|
|
+ non_strict = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return non_strict? MHD_PARSE_COOKIE_OK_LAX : MHD_PARSE_COOKIE_OK;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * Parse the cookie header (see RFC 6265).
|
|
|
+ *
|
|
|
+ * @param connection connection to parse header of
|
|
|
+ * @return #MHD_PARSE_COOKIE_OK for success, error code otherwise
|
|
|
+ */
|
|
|
+static enum _MHD_ParseCookie
|
|
|
+parse_cookie_header (struct MHD_Connection *connection)
|
|
|
+{
|
|
|
+ struct MHD_String *hvalue;
|
|
|
+ char *cpy;
|
|
|
+ size_t i;
|
|
|
+ enum _MHD_ParseCookie parse_res;
|
|
|
+ struct mhd_RequestField *const saved_tail =
|
|
|
+ connection->rq.fields.last;
|
|
|
+ const bool allow_partially_correct_cookie =
|
|
|
+ (1 >= connection->daemon->req_cfg.strictnees);
|
|
|
+
|
|
|
+ hvalue = mhd_request_get_value_len(&(connection->rq),
|
|
|
+ MHD_VK_HEADER,
|
|
|
+ mhd_SSTR_LEN (MHD_HTTP_HEADER_COOKIE),
|
|
|
+ MHD_HTTP_HEADER_COOKIE);
|
|
|
+ if (NULL == hvalue)
|
|
|
+ return MHD_PARSE_COOKIE_OK;
|
|
|
+ if (0 == hvalue->len)
|
|
|
+ return MHD_PARSE_COOKIE_OK;
|
|
|
+
|
|
|
+ cpy = mhd_stream_alloc_memory (connection,
|
|
|
+ hvalue->len + 1);
|
|
|
+ if (NULL == cpy)
|
|
|
+ parse_res = MHD_PARSE_COOKIE_NO_MEMORY;
|
|
|
+ else
|
|
|
+ {
|
|
|
+ memcpy (cpy,
|
|
|
+ hdr,
|
|
|
+ hdr_len);
|
|
|
+ cpy[hdr_len] = '\0';
|
|
|
+
|
|
|
+ i = 0;
|
|
|
+ /* Skip all initial whitespaces */
|
|
|
+ while (i < hdr_len && (' ' == cpy[i] || '\t' == cpy[i]))
|
|
|
+ i++;
|
|
|
+
|
|
|
+ parse_res = parse_cookies_string (cpy + i, hdr_len - i, connection);
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (parse_res)
|
|
|
+ {
|
|
|
+ case MHD_PARSE_COOKIE_OK:
|
|
|
+ break;
|
|
|
+ case MHD_PARSE_COOKIE_OK_LAX:
|
|
|
+#ifdef HAVE_MESSAGES
|
|
|
+ if (saved_tail != connection->rq.headers_received_tail)
|
|
|
+ MHD_DLOG (connection->daemon,
|
|
|
+ _ ("The Cookie header has been parsed, but it is not fully "
|
|
|
+ "compliant with the standard.\n"));
|
|
|
+#endif /* HAVE_MESSAGES */
|
|
|
+ break;
|
|
|
+ case MHD_PARSE_COOKIE_MALFORMED:
|
|
|
+ if (saved_tail != connection->rq.headers_received_tail)
|
|
|
+ {
|
|
|
+ if (! allow_partially_correct_cookie)
|
|
|
+ {
|
|
|
+ /* Remove extracted values from partially broken cookie */
|
|
|
+ /* Memory remains allocated until the end of the request processing */
|
|
|
+ connection->rq.headers_received_tail = saved_tail;
|
|
|
+ saved_tail->next = NULL;
|
|
|
+#ifdef HAVE_MESSAGES
|
|
|
+ MHD_DLOG (connection->daemon,
|
|
|
+ _ ("The Cookie header has been ignored as it contains "
|
|
|
+ "malformed data.\n"));
|
|
|
+#endif /* HAVE_MESSAGES */
|
|
|
+ }
|
|
|
+#ifdef HAVE_MESSAGES
|
|
|
+ else
|
|
|
+ MHD_DLOG (connection->daemon,
|
|
|
+ _ ("The Cookie header has been only partially parsed as it "
|
|
|
+ "contains malformed data.\n"));
|
|
|
+#endif /* HAVE_MESSAGES */
|
|
|
+ }
|
|
|
+#ifdef HAVE_MESSAGES
|
|
|
+ else
|
|
|
+ MHD_DLOG (connection->daemon,
|
|
|
+ _ ("The Cookie header has malformed data.\n"));
|
|
|
+#endif /* HAVE_MESSAGES */
|
|
|
+ break;
|
|
|
+ case MHD_PARSE_COOKIE_NO_MEMORY:
|
|
|
+#ifdef HAVE_MESSAGES
|
|
|
+ MHD_DLOG (connection->daemon,
|
|
|
+ _ ("Not enough memory in the connection pool to "
|
|
|
+ "parse client cookies!\n"));
|
|
|
+#endif /* HAVE_MESSAGES */
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ mhd_assert (0);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+#ifndef HAVE_MESSAGES
|
|
|
+ (void) saved_tail; /* Mute compiler warning */
|
|
|
+#endif /* ! HAVE_MESSAGES */
|
|
|
+
|
|
|
+ return parse_res;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#endif /* COOKIE_SUPPORT */
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void
|
|
|
+mhd_parse_connection_headers (struct MHD_Connection *restrict c)
|
|
|
+{
|
|
|
+ const char *clen;
|
|
|
+ const char *enc;
|
|
|
+ size_t val_len;
|
|
|
+
|
|
|
+#ifdef COOKIE_SUPPORT
|
|
|
+ if (MHD_PARSE_COOKIE_NO_MEMORY == parse_cookie_header (connection))
|
|
|
+ {
|
|
|
+ handle_req_cookie_no_space (connection);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+#endif /* COOKIE_SUPPORT */
|
|
|
+ if ( (-3 < connection->daemon->client_discipline) &&
|
|
|
+ (MHD_IS_HTTP_VER_1_1_COMPAT (connection->rq.http_ver)) &&
|
|
|
+ (MHD_NO ==
|
|
|
+ MHD_lookup_connection_value_n (connection,
|
|
|
+ MHD_VK_HEADER,
|
|
|
+ MHD_HTTP_HEADER_HOST,
|
|
|
+ mhd_SSTR_LEN (
|
|
|
+ MHD_HTTP_HEADER_HOST),
|
|
|
+ NULL,
|
|
|
+ NULL)) )
|
|
|
+ {
|
|
|
+#ifdef HAVE_MESSAGES
|
|
|
+ MHD_DLOG (connection->daemon,
|
|
|
+ _ ("Received HTTP/1.1 request without `Host' header.\n"));
|
|
|
+#endif
|
|
|
+ transmit_error_response_static (connection,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ REQUEST_LACKS_HOST);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* The presence of the request body is indicated by "Content-Length:" or
|
|
|
+ "Transfer-Encoding:" request headers.
|
|
|
+ Unless one of these two headers is used, the request has no request body.
|
|
|
+ See RFC9112, Section 6, paragraph 4. */
|
|
|
+ connection->rq.remaining_upload_size = 0;
|
|
|
+ if (MHD_NO !=
|
|
|
+ MHD_lookup_connection_value_n (connection,
|
|
|
+ MHD_VK_HEADER,
|
|
|
+ MHD_HTTP_HEADER_TRANSFER_ENCODING,
|
|
|
+ mhd_SSTR_LEN (
|
|
|
+ MHD_HTTP_HEADER_TRANSFER_ENCODING),
|
|
|
+ &enc,
|
|
|
+ NULL))
|
|
|
+ {
|
|
|
+ if (! MHD_str_equal_caseless_ (enc,
|
|
|
+ "chunked"))
|
|
|
+ {
|
|
|
+ transmit_error_response_static (connection,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ REQUEST_UNSUPPORTED_TR_ENCODING);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ else if (MHD_NO !=
|
|
|
+ MHD_lookup_connection_value_n (connection,
|
|
|
+ MHD_VK_HEADER,
|
|
|
+ MHD_HTTP_HEADER_CONTENT_LENGTH,
|
|
|
+ mhd_SSTR_LEN ( \
|
|
|
+ MHD_HTTP_HEADER_CONTENT_LENGTH),
|
|
|
+ NULL,
|
|
|
+ NULL))
|
|
|
+ {
|
|
|
+ /* TODO: add individual settings */
|
|
|
+ if (1 <= connection->daemon->client_discipline)
|
|
|
+ {
|
|
|
+ transmit_error_response_static (connection,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ REQUEST_LENGTH_WITH_TR_ENCODING);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ /* Must close connection after reply to prevent potential attack */
|
|
|
+ connection->keepalive = MHD_CONN_MUST_CLOSE;
|
|
|
+#ifdef HAVE_MESSAGES
|
|
|
+ MHD_DLOG (connection->daemon,
|
|
|
+ _ ("The 'Content-Length' request header is ignored "
|
|
|
+ "as chunked Transfer-Encoding is used "
|
|
|
+ "for this request.\n"));
|
|
|
+#endif /* HAVE_MESSAGES */
|
|
|
+ }
|
|
|
+ }
|
|
|
+ connection->rq.have_chunked_upload = true;
|
|
|
+ connection->rq.remaining_upload_size = MHD_SIZE_UNKNOWN;
|
|
|
+ }
|
|
|
+ else if (MHD_NO !=
|
|
|
+ MHD_lookup_connection_value_n (connection,
|
|
|
+ MHD_VK_HEADER,
|
|
|
+ MHD_HTTP_HEADER_CONTENT_LENGTH,
|
|
|
+ mhd_SSTR_LEN (
|
|
|
+ MHD_HTTP_HEADER_CONTENT_LENGTH),
|
|
|
+ &clen,
|
|
|
+ &val_len))
|
|
|
+ {
|
|
|
+ size_t num_digits;
|
|
|
+
|
|
|
+ num_digits = MHD_str_to_uint64_n_ (clen,
|
|
|
+ val_len,
|
|
|
+ &connection->rq.remaining_upload_size);
|
|
|
+
|
|
|
+ if (((0 == num_digits) &&
|
|
|
+ (0 != val_len) &&
|
|
|
+ ('0' <= clen[0]) && ('9' >= clen[0]))
|
|
|
+ || (MHD_SIZE_UNKNOWN == connection->rq.remaining_upload_size))
|
|
|
+ {
|
|
|
+ connection->rq.remaining_upload_size = 0;
|
|
|
+#ifdef HAVE_MESSAGES
|
|
|
+ MHD_DLOG (connection->daemon,
|
|
|
+ _ ("Too large value of 'Content-Length' header. " \
|
|
|
+ "Closing connection.\n"));
|
|
|
+#endif
|
|
|
+ transmit_error_response_static (connection,
|
|
|
+ MHD_HTTP_STATUS_CONTENT_TOO_LARGE,
|
|
|
+ REQUEST_CONTENTLENGTH_TOOLARGE);
|
|
|
+ }
|
|
|
+ else if ((val_len != num_digits) ||
|
|
|
+ (0 == num_digits))
|
|
|
+ {
|
|
|
+ connection->rq.remaining_upload_size = 0;
|
|
|
+#ifdef HAVE_MESSAGES
|
|
|
+ MHD_DLOG (connection->daemon,
|
|
|
+ _ ("Failed to parse 'Content-Length' header. " \
|
|
|
+ "Closing connection.\n"));
|
|
|
+#endif
|
|
|
+ transmit_error_response_static (connection,
|
|
|
+ MHD_HTTP_STATUS_BAD_REQUEST,
|
|
|
+ REQUEST_CONTENTLENGTH_MALFORMED);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|