[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[libmicrohttpd] 01/02: Rewritten cookie parsing.
From: |
gnunet |
Subject: |
[libmicrohttpd] 01/02: Rewritten cookie parsing. |
Date: |
Sun, 15 May 2022 20:02:57 +0200 |
This is an automated email from the git hooks/post-receive script.
karlson2k pushed a commit to branch master
in repository libmicrohttpd.
commit d08fb47c0a53a658fdd97fd1a4e265aade576418
Author: Evgeny Grin (Karlson2k) <k2k@narod.ru>
AuthorDate: Sun May 15 20:59:22 2022 +0300
Rewritten cookie parsing.
Updated to match the current RFC, fixed some edge cases.
---
src/microhttpd/connection.c | 426 +++++++++++++++++++++++++++++++++++---------
1 file changed, 338 insertions(+), 88 deletions(-)
diff --git a/src/microhttpd/connection.c b/src/microhttpd/connection.c
index 5d795b47..47d63d1f 100644
--- a/src/microhttpd/connection.c
+++ b/src/microhttpd/connection.c
@@ -2758,15 +2758,314 @@ connection_add_header (struct MHD_Connection
*connection,
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).
+ *
+ * Parsing may fail if the string is not formed strictly as defined 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 enum _MHD_ParseCookie
+parse_cookies_string_strict (char *str,
+ size_t str_len,
+ struct MHD_Connection *connection)
+{
+ size_t i;
+
+ 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;
+ /* '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);
+ if ((str_len == i) || ('=' != str[i]) || (name_start == i))
+ return MHD_PARSE_COOKIE_MALFORMED; /* Incomplete cookie name */
+ name_len = i - name_start;
+ /* 'i' must point to the '=' char */
+ mhd_assert ('=' == str[i]);
+ i++;
+ /* 'i' must point to the first char of cookie-value */
+ if (str_len == i)
+ {
+ value_start = 0;
+ value_len = 0;
+ }
+ 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) || ('\t' == l)
+ || (',' == l) || ('\\' == l) || (0 == l))
+ break;
+ 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++;
+ }
+ if (str_len == i)
+ valid_cookie = true;
+ else if (';' == str[i])
+ valid_cookie = true;
+ else if ((' ' == str[i]) || ('\t' == str[i]))
+ { /* Optional whitespace at the end of the string? */
+ while (str_len > ++i)
+ {
+ if ((' ' != str[i]) && ('\t' != str[i]))
+ break;
+ }
+ if (str_len == i)
+ valid_cookie = true;
+ else
+ valid_cookie = false;
+ }
+ 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)
+ {
+ mhd_assert (0 == str[i] || ';' == str[i]);
+ mhd_assert (! val_quoted || ';' == str[i]);
+ str[value_start + value_len] = 0; /* Zero-terminate the value */
+ if (MHD_NO ==
+ MHD_set_connection_value_n_nocheck_ (connection,
+ MHD_COOKIE_KIND,
+ str + name_start,
+ name_len,
+ str + value_start,
+ value_len))
+ return MHD_PARSE_COOKIE_NO_MEMORY;
+ }
+ else
+ {
+ if (MHD_NO ==
+ MHD_set_connection_value_n_nocheck_ (connection,
+ MHD_COOKIE_KIND,
+ str + name_start,
+ name_len,
+ "",
+ 0))
+ 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 || 0 == value_len);
+ i++;
+ if (str_len == i)
+ return MHD_PARSE_COOKIE_MALFORMED; /* No cookie name after semicolon
*/
+ if (' ' != str[i])
+ return MHD_PARSE_COOKIE_MALFORMED; /* No space after semicolon */
+ i++;
+ }
+ }
+ return MHD_PARSE_COOKIE_OK;
+}
+
+
+/**
+ * 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 enum _MHD_ParseCookie
+parse_cookies_string_lenient (char *str,
+ size_t str_len,
+ struct MHD_Connection *connection)
+{
+ size_t i;
+ bool non_strict;
+
+ 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])
+ {
+ 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]))
+ {
+ 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]))
+ {
+ non_strict = true;
+ i++;
+ }
+ /* 'i' must point to the first char of cookie-value */
+ if (str_len == i)
+ {
+ value_start = 0;
+ value_len = 0;
+ }
+ 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;
+ 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 */
+ while (str_len > i && (' ' == str[i] || '\t' == str[i]))
+ {
+ non_strict = true;
+ i++;
+ }
+ 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)
+ {
+ mhd_assert (value_start + value_len <= str_len);
+ str[value_start + value_len] = 0; /* Zero-terminate the value */
+ if (MHD_NO ==
+ MHD_set_connection_value_n_nocheck_ (connection,
+ MHD_COOKIE_KIND,
+ str + name_start,
+ name_len,
+ str + value_start,
+ value_len))
+ return MHD_PARSE_COOKIE_NO_MEMORY;
+ }
+ else
+ {
+ if (MHD_NO ==
+ MHD_set_connection_value_n_nocheck_ (connection,
+ MHD_COOKIE_KIND,
+ str + name_start,
+ name_len,
+ "",
+ 0))
+ 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)
+ non_strict = true; /* No cookie name after semicolon */
+ else if (' ' != str[i])
+ non_strict = true; /* No space after semicolon */
+ else
+ i++;
+ }
+ }
+ return non_strict? MHD_PARSE_COOKIE_OK_LAX : MHD_PARSE_COOKIE_OK;
+}
+
+
/**
- * Parse the cookie header (see RFC 2109).
+ * Parse the cookie header (see RFC 6265).
*
* @param connection connection to parse header of
- * @return #MHD_YES for success, #MHD_NO for failure (malformed, out of memory)
+ * @return #MHD_PARSE_COOKIE_OK for success, error code otherwise
*/
static enum _MHD_ParseCookie
parse_cookie_header (struct MHD_Connection *connection)
@@ -2774,14 +3073,8 @@ parse_cookie_header (struct MHD_Connection *connection)
const char *hdr;
size_t hdr_len;
char *cpy;
- char *pos;
- char *sce;
- char *semicolon;
- char *equals;
- char *ekill;
- char *end;
- char old;
- int quotes;
+ bool strict_parsing;
+ size_t i;
if (MHD_NO == MHD_lookup_connection_value_n (connection,
MHD_HEADER_KIND,
@@ -2791,6 +3084,9 @@ parse_cookie_header (struct MHD_Connection *connection)
&hdr,
&hdr_len))
return MHD_PARSE_COOKIE_OK;
+ if (0 == hdr_len)
+ return MHD_PARSE_COOKIE_OK;
+
cpy = connection_alloc_memory (connection,
hdr_len + 1);
if (NULL == cpy)
@@ -2800,83 +3096,18 @@ parse_cookie_header (struct MHD_Connection *connection)
hdr,
hdr_len);
cpy[hdr_len] = '\0';
- pos = cpy;
- while (NULL != pos)
- {
- while (' ' == *pos)
- pos++; /* skip spaces */
-
- sce = pos;
- while ( ((*sce) != '\0') &&
- ((*sce) != ',') &&
- ((*sce) != ';') &&
- ((*sce) != '=') )
- sce++;
- /* remove tailing whitespace (if any) from key */
- ekill = sce - 1;
- while ( (*ekill == ' ') &&
- (ekill >= pos) )
- *(ekill--) = '\0';
- old = *sce;
- *sce = '\0';
- if (old != '=')
- {
- /* value part omitted, use empty string... */
- mhd_assert (ekill >= pos);
- if (MHD_NO ==
- MHD_set_connection_value_n_nocheck_ (connection,
- MHD_COOKIE_KIND,
- pos,
- (size_t) (ekill - pos + 1),
- "",
- 0))
- return MHD_PARSE_COOKIE_NO_MEMORY;
- if (old == '\0')
- break;
- pos = sce + 1;
- continue;
- }
- equals = sce + 1;
- quotes = 0;
- semicolon = equals;
- while ( ('\0' != semicolon[0]) &&
- ( (0 != quotes) ||
- ( (';' != semicolon[0]) &&
- (',' != semicolon[0]) ) ) )
- {
- if ('"' == semicolon[0])
- quotes = (quotes + 1) & 1;
- semicolon++;
- }
- end = semicolon;
- if ('\0' == semicolon[0])
- semicolon = NULL;
- if (NULL != semicolon)
- {
- semicolon[0] = '\0';
- semicolon++;
- }
- /* remove quotes */
- if ( ('"' == equals[0]) &&
- ('"' == end[-1]) )
- {
- equals++;
- end--;
- *end = '\0';
- }
- mhd_assert (ekill >= pos);
- mhd_assert (end >= equals);
- if (MHD_NO ==
- MHD_set_connection_value_n_nocheck_ (connection,
- MHD_COOKIE_KIND,
- pos,
- (size_t) (ekill - pos + 1),
- equals,
- (size_t) (end - equals)))
- return MHD_PARSE_COOKIE_NO_MEMORY;
- pos = semicolon;
- }
- return MHD_PARSE_COOKIE_OK;
+
+ strict_parsing = false; /* TODO: make it configurable */
+ i = 0;
+ /* Skip all initial whitespaces */
+ while (i < hdr_len && (' ' == cpy[i] || '\t' == cpy[i]))
+ i++;
+
+ /* 'i' points to the first non-whitespace char or to the end of the string */
+ if (strict_parsing)
+ return parse_cookies_string_strict (cpy + i, hdr_len - i, connection);
+
+ return parse_cookies_string_lenient (cpy + i, hdr_len - i, connection);
}
@@ -3619,8 +3850,10 @@ parse_connection_headers (struct MHD_Connection
*connection)
const char *clen;
const char *enc;
size_t val_len;
+ enum _MHD_ParseCookie cookie_res;
- if (MHD_PARSE_COOKIE_NO_MEMORY == parse_cookie_header (connection))
+ cookie_res = parse_cookie_header (connection);
+ if (MHD_PARSE_COOKIE_NO_MEMORY == cookie_res)
{
#ifdef HAVE_MESSAGES
MHD_DLOG (connection->daemon,
@@ -3631,6 +3864,23 @@ parse_connection_headers (struct MHD_Connection
*connection)
REQUEST_TOO_BIG);
return;
}
+ else if (MHD_PARSE_COOKIE_OK_LAX == cookie_res)
+ {
+#ifdef HAVE_MESSAGES
+ MHD_DLOG (connection->daemon,
+ _ ("The Cookie header has been parsed, but is not fully "
+ "compliant with the standard.\n"));
+#endif
+ (void) 0; /* Mute compiler warning */
+ }
+ else if (MHD_PARSE_COOKIE_MALFORMED == cookie_res)
+ {
+#ifdef HAVE_MESSAGES
+ MHD_DLOG (connection->daemon,
+ _ ("The Cookie header has malformed data.\n"));
+#endif
+ (void) 0; /* Mute compiler warning */
+ }
if ( (1 <= connection->daemon->strict_for_client) &&
(MHD_IS_HTTP_VER_1_1_COMPAT (connection->http_ver)) &&
(MHD_NO ==
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.