浏览代码

Merge branch 'master' into fix/uri-len-dup

bel2125 2 天之前
父节点
当前提交
48b58d1bfe
共有 7 个文件被更改,包括 115 次插入31 次删除
  1. 1 1
      .github/workflows/cibuild.yml
  2. 1 1
      .github/workflows/codeql.yml
  3. 1 1
      README.md
  4. 70 16
      src/civetweb.c
  5. 38 8
      src/handle_form.inl
  6. 2 2
      test/page4.lp
  7. 2 2
      test/page4kepler.lp

+ 1 - 1
.github/workflows/cibuild.yml

@@ -392,7 +392,7 @@ jobs:
 
 
     steps:
     steps:
         - name: Checkout code
         - name: Checkout code
-          uses: actions/checkout@v4.1.7
+          uses: actions/checkout@v5
 
 
         - name: Export number of CPUs
         - name: Export number of CPUs
           run: |
           run: |

+ 1 - 1
.github/workflows/codeql.yml

@@ -45,7 +45,7 @@ jobs:
 
 
     steps:
     steps:
     - name: Checkout repository
     - name: Checkout repository
-      uses: actions/checkout@v4
+      uses: actions/checkout@v5
       with:
       with:
         submodules: recursive
         submodules: recursive
 
 

+ 1 - 1
README.md

@@ -182,7 +182,7 @@ CivetWeb is based on the [Mongoose project](https://github.com/cesanta/mongoose)
 Sergey Lyubka(2004-2013) who released it under the MIT license.
 Sergey Lyubka(2004-2013) who released it under the MIT license.
 However, on August 16, 2013,
 However, on August 16, 2013,
 [Mongoose was relicensed to a dual GPL V2 + commercial license](https://groups.google.com/forum/#!topic/mongoose-users/aafbOnHonkI)
 [Mongoose was relicensed to a dual GPL V2 + commercial license](https://groups.google.com/forum/#!topic/mongoose-users/aafbOnHonkI)
-and CiwetWeb was created by Thomas Davis (sunsetbrew) as "the MIT fork of mongoose".
+and CivetWeb was created by Thomas Davis (sunsetbrew) as "the MIT fork of mongoose".
 The license change and CivetWeb fork was mentioned on the Mongoose
 The license change and CivetWeb fork was mentioned on the Mongoose
 [Wikipedia](https://en.wikipedia.org/wiki/Mongoose_(web_server))
 [Wikipedia](https://en.wikipedia.org/wiki/Mongoose_(web_server))
 page as well, but it's getting deleted (and added again) there every
 page as well, but it's getting deleted (and added again) there every

+ 70 - 16
src/civetweb.c

@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2024 the Civetweb developers
+/* Copyright (c) 2013-2025 the Civetweb developers
  * Copyright (c) 2004-2013 Sergey Lyubka
  * Copyright (c) 2004-2013 Sergey Lyubka
  *
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -2087,7 +2087,7 @@ enum {
 #if defined(USE_LUA) && defined(USE_WEBSOCKET)
 #if defined(USE_LUA) && defined(USE_WEBSOCKET)
 	LUA_WEBSOCKET_EXTENSIONS,
 	LUA_WEBSOCKET_EXTENSIONS,
 #endif
 #endif
-
+	REPLACE_ASTERISK_WITH_ORIGIN,
 	ACCESS_CONTROL_ALLOW_ORIGIN,
 	ACCESS_CONTROL_ALLOW_ORIGIN,
 	ACCESS_CONTROL_ALLOW_METHODS,
 	ACCESS_CONTROL_ALLOW_METHODS,
 	ACCESS_CONTROL_ALLOW_HEADERS,
 	ACCESS_CONTROL_ALLOW_HEADERS,
@@ -2253,6 +2253,7 @@ static const struct mg_option config_options[] = {
 #if defined(USE_LUA) && defined(USE_WEBSOCKET)
 #if defined(USE_LUA) && defined(USE_WEBSOCKET)
     {"lua_websocket_pattern", MG_CONFIG_TYPE_EXT_PATTERN, "**.lua$"},
     {"lua_websocket_pattern", MG_CONFIG_TYPE_EXT_PATTERN, "**.lua$"},
 #endif
 #endif
+	{"replace_asterisk_with_origin", MG_CONFIG_TYPE_BOOLEAN, "no"},
     {"access_control_allow_origin", MG_CONFIG_TYPE_STRING, "*"},
     {"access_control_allow_origin", MG_CONFIG_TYPE_STRING, "*"},
     {"access_control_allow_methods", MG_CONFIG_TYPE_STRING, "*"},
     {"access_control_allow_methods", MG_CONFIG_TYPE_STRING, "*"},
     {"access_control_allow_headers", MG_CONFIG_TYPE_STRING, "*"},
     {"access_control_allow_headers", MG_CONFIG_TYPE_STRING, "*"},
@@ -4234,16 +4235,27 @@ send_cors_header(struct mg_connection *conn)
 	    conn->dom_ctx->config[ACCESS_CONTROL_EXPOSE_HEADERS];
 	    conn->dom_ctx->config[ACCESS_CONTROL_EXPOSE_HEADERS];
 	const char *cors_meth_cfg =
 	const char *cors_meth_cfg =
 	    conn->dom_ctx->config[ACCESS_CONTROL_ALLOW_METHODS];
 	    conn->dom_ctx->config[ACCESS_CONTROL_ALLOW_METHODS];
-
-	if (cors_orig_cfg && *cors_orig_cfg && origin_hdr && *origin_hdr) {
+	const char *cors_repl_asterisk_with_orig_cfg = 
+		conn->dom_ctx->config[REPLACE_ASTERISK_WITH_ORIGIN];
+		
+	if (cors_orig_cfg && *cors_orig_cfg && origin_hdr && *origin_hdr && cors_repl_asterisk_with_orig_cfg && *cors_repl_asterisk_with_orig_cfg) {
+		int cors_repl_asterisk_with_orig = mg_strcasecmp(cors_repl_asterisk_with_orig_cfg, "yes");
+		
 		/* Cross-origin resource sharing (CORS), see
 		/* Cross-origin resource sharing (CORS), see
 		 * http://www.html5rocks.com/en/tutorials/cors/,
 		 * http://www.html5rocks.com/en/tutorials/cors/,
 		 * http://www.html5rocks.com/static/images/cors_server_flowchart.png
 		 * http://www.html5rocks.com/static/images/cors_server_flowchart.png
 		 * CORS preflight is not supported for files. */
 		 * CORS preflight is not supported for files. */
-		mg_response_header_add(conn,
+		if (cors_repl_asterisk_with_orig == 0 && cors_orig_cfg[0] == '*') {
+			mg_response_header_add(conn,
+		                       "Access-Control-Allow-Origin",
+		                       origin_hdr,
+		                       -1);
+		} else {
+			mg_response_header_add(conn,
 		                       "Access-Control-Allow-Origin",
 		                       "Access-Control-Allow-Origin",
 		                       cors_orig_cfg,
 		                       cors_orig_cfg,
 		                       -1);
 		                       -1);
+		}
 	}
 	}
 
 
 	if (cors_cred_cfg && *cors_cred_cfg && origin_hdr && *origin_hdr) {
 	if (cors_cred_cfg && *cors_cred_cfg && origin_hdr && *origin_hdr) {
@@ -7272,6 +7284,7 @@ mg_url_decode(const char *src,
               int is_form_url_encoded)
               int is_form_url_encoded)
 {
 {
 	int i, j, a, b;
 	int i, j, a, b;
+
 #define HEXTOI(x) (isdigit(x) ? (x - '0') : (x - 'W'))
 #define HEXTOI(x) (isdigit(x) ? (x - '0') : (x - 'W'))
 
 
 	for (i = j = 0; (i < src_len) && (j < (dst_len - 1)); i++, j++) {
 	for (i = j = 0; (i < src_len) && (j < (dst_len - 1)); i++, j++) {
@@ -7284,11 +7297,15 @@ mg_url_decode(const char *src,
 			i += 2;
 			i += 2;
 		} else if (is_form_url_encoded && (src[i] == '+')) {
 		} else if (is_form_url_encoded && (src[i] == '+')) {
 			dst[j] = ' ';
 			dst[j] = ' ';
+		} else if ((unsigned char)src[i] <= ' ') {
+			return -1; /* invalid character */
 		} else {
 		} else {
 			dst[j] = src[i];
 			dst[j] = src[i];
 		}
 		}
 	}
 	}
 
 
+#undef HEXTOI
+
 	dst[j] = '\0'; /* Null-terminate the destination */
 	dst[j] = '\0'; /* Null-terminate the destination */
 
 
 	return (i >= src_len) ? j : -1;
 	return (i >= src_len) ? j : -1;
@@ -10783,9 +10800,13 @@ is_not_modified(const struct mg_connection *conn,
 	const char *inm = mg_get_header(conn, "If-None-Match");
 	const char *inm = mg_get_header(conn, "If-None-Match");
 	construct_etag(etag, sizeof(etag), filestat);
 	construct_etag(etag, sizeof(etag), filestat);
 
 
-	return ((inm != NULL) && !mg_strcasecmp(etag, inm))
-	       || ((ims != NULL)
-	           && (filestat->last_modified <= parse_date_string(ims)));
+	if (inm) {
+		return !mg_strcasecmp(etag, inm);
+	}
+	if (ims) {
+		return (filestat->last_modified <= parse_date_string(ims));
+	}
+	return 0;
 }
 }
 
 
 
 
@@ -15165,13 +15186,18 @@ handle_request(struct mg_connection *conn)
 		const char *cors_acrm = get_header(ri->http_headers,
 		const char *cors_acrm = get_header(ri->http_headers,
 		                                   ri->num_headers,
 		                                   ri->num_headers,
 		                                   "Access-Control-Request-Method");
 		                                   "Access-Control-Request-Method");
-
+		const char *cors_repl_asterisk_with_orig_cfg = 
+			conn->dom_ctx->config[REPLACE_ASTERISK_WITH_ORIGIN];
+		
 		/* Todo: check if cors_origin is in cors_orig_cfg.
 		/* Todo: check if cors_origin is in cors_orig_cfg.
 		 * Or, let the client check this. */
 		 * Or, let the client check this. */
 
 
 		if ((cors_meth_cfg != NULL) && (*cors_meth_cfg != 0)
 		if ((cors_meth_cfg != NULL) && (*cors_meth_cfg != 0)
 		    && (cors_orig_cfg != NULL) && (*cors_orig_cfg != 0)
 		    && (cors_orig_cfg != NULL) && (*cors_orig_cfg != 0)
-		    && (cors_origin != NULL) && (cors_acrm != NULL)) {
+		    && (cors_origin != NULL) && (cors_acrm != NULL)
+			&& (cors_repl_asterisk_with_orig_cfg != NULL) && (*cors_repl_asterisk_with_orig_cfg != 0)) {
+			int cors_repl_asterisk_with_orig = mg_strcasecmp(cors_repl_asterisk_with_orig_cfg, "yes");
+			
 			/* This is a valid CORS preflight, and the server is configured
 			/* This is a valid CORS preflight, and the server is configured
 			 * to handle it automatically. */
 			 * to handle it automatically. */
 			const char *cors_acrh =
 			const char *cors_acrh =
@@ -15192,7 +15218,7 @@ handle_request(struct mg_connection *conn)
 			          "Content-Length: 0\r\n"
 			          "Content-Length: 0\r\n"
 			          "Connection: %s\r\n",
 			          "Connection: %s\r\n",
 			          date,
 			          date,
-			          cors_orig_cfg,
+			          (cors_repl_asterisk_with_orig == 0 && cors_orig_cfg[0] == '*') ? cors_origin : cors_orig_cfg,
 			          ((cors_meth_cfg[0] == '*') ? cors_acrm : cors_meth_cfg),
 			          ((cors_meth_cfg[0] == '*') ? cors_acrm : cors_meth_cfg),
 			          suggest_connection_header(conn));
 			          suggest_connection_header(conn));
 
 
@@ -15586,6 +15612,7 @@ handle_request(struct mg_connection *conn)
 	if (file.stat.is_directory && (uri_len > 0)
 	if (file.stat.is_directory && (uri_len > 0)
         && (ri->local_uri[uri_len - 1] != '/')) {
         && (ri->local_uri[uri_len - 1] != '/')) {
 
 
+
 		/* Path + server root */
 		/* Path + server root */
 		size_t buflen = UTF8_PATH_MAX * 2 + 2;
 		size_t buflen = UTF8_PATH_MAX * 2 + 2;
 		char *new_path;
 		char *new_path;
@@ -15598,12 +15625,26 @@ handle_request(struct mg_connection *conn)
 			mg_send_http_error(conn, 500, "out or memory");
 			mg_send_http_error(conn, 500, "out or memory");
 		} else {
 		} else {
 			mg_get_request_link(conn, new_path, buflen - 1);
 			mg_get_request_link(conn, new_path, buflen - 1);
-			strcat(new_path, "/");
+
+			size_t len = strlen(new_path);
+			if (len + 1 < buflen) {
+				new_path[len] = '/';
+				new_path[len + 1] = '\0';
+				len++;
+			}
+
 			if (ri->query_string) {
 			if (ri->query_string) {
-				/* Append ? and query string */
-				strcat(new_path, "?");
-				strcat(new_path, ri->query_string);
+				if (len + 1 < buflen) {
+					new_path[len] = '?';
+					new_path[len + 1] = '\0';
+					len++;
+				}
+
+				/* Append with size of space left for query string + null terminator */
+				size_t max_append = buflen - len - 1;
+				strncat(new_path, ri->query_string, max_append);
 			}
 			}
+
 			mg_send_http_redirect(conn, new_path, 301);
 			mg_send_http_redirect(conn, new_path, 301);
 			mg_free(new_path);
 			mg_free(new_path);
 		}
 		}
@@ -18864,6 +18905,19 @@ get_uri_type(const char *uri)
 	 * and % encoded symbols.
 	 * and % encoded symbols.
 	 */
 	 */
 	for (i = 0; uri[i] != 0; i++) {
 	for (i = 0; uri[i] != 0; i++) {
+		/* Check for CRLF injection attempts */
+		if (uri[i] == '%') {
+			if (uri[i+1] == '0' && (uri[i+2] == 'd' || uri[i+2] == 'D')) {
+				/* Found %0d (CR) */
+				DEBUG_TRACE("CRLF injection attempt detected: %s\r\n", uri);
+				return 0;
+			}
+			if (uri[i+1] == '0' && (uri[i+2] == 'a' || uri[i+2] == 'A')) {
+				/* Found %0a (LF) */
+				DEBUG_TRACE("CRLF injection attempt detected: %s\r\n", uri);
+				return 0;
+			}
+		}
 		if ((unsigned char)uri[i] < 33) {
 		if ((unsigned char)uri[i] < 33) {
 			/* control characters and spaces are invalid */
 			/* control characters and spaces are invalid */
 			return 0;
 			return 0;
@@ -22026,7 +22080,7 @@ mg_start_domain2(struct mg_context *ctx,
 		}
 		}
 	}
 	}
 
 
-	new_dom->handlers = NULL;
+	new_dom->handlers = ctx->dd.handlers;
 	new_dom->next = NULL;
 	new_dom->next = NULL;
 	new_dom->nonce_count = 0;
 	new_dom->nonce_count = 0;
 	new_dom->auth_nonce_mask = get_random() ^ (get_random() << 31);
 	new_dom->auth_nonce_mask = get_random() ^ (get_random() << 31);

+ 38 - 8
src/handle_form.inl

@@ -1,4 +1,4 @@
-/* Copyright (c) 2016-2021 the Civetweb developers
+/* Copyright (c) 2016-2025 the Civetweb developers
  *
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * of this software and associated documentation files (the "Software"), to deal
@@ -39,7 +39,7 @@ url_encoded_field_found(const struct mg_connection *conn,
 	    mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1);
 	    mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1);
 
 
 	if (((size_t)key_dec_len >= (size_t)sizeof(key_dec)) || (key_dec_len < 0)) {
 	if (((size_t)key_dec_len >= (size_t)sizeof(key_dec)) || (key_dec_len < 0)) {
-		return MG_FORM_FIELD_STORAGE_SKIP;
+		return MG_FORM_FIELD_STORAGE_ABORT;
 	}
 	}
 
 
 	if (filename) {
 	if (filename) {
@@ -53,7 +53,7 @@ url_encoded_field_found(const struct mg_connection *conn,
 		    || (filename_dec_len < 0)) {
 		    || (filename_dec_len < 0)) {
 			/* Log error message and skip this field. */
 			/* Log error message and skip this field. */
 			mg_cry_internal(conn, "%s: Cannot decode filename", __func__);
 			mg_cry_internal(conn, "%s: Cannot decode filename", __func__);
-			return MG_FORM_FIELD_STORAGE_SKIP;
+			return MG_FORM_FIELD_STORAGE_ABORT;
 		}
 		}
 		remove_dot_segments(filename_dec);
 		remove_dot_segments(filename_dec);
 
 
@@ -95,6 +95,7 @@ url_encoded_field_get(
     struct mg_form_data_handler *fdh)
     struct mg_form_data_handler *fdh)
 {
 {
 	char key_dec[1024];
 	char key_dec[1024];
+	int key_dec_len;
 
 
 	char *value_dec = (char *)mg_malloc_ctx(*value_len + 1, conn->phys_ctx);
 	char *value_dec = (char *)mg_malloc_ctx(*value_len + 1, conn->phys_ctx);
 	int value_dec_len, ret;
 	int value_dec_len, ret;
@@ -108,7 +109,8 @@ url_encoded_field_get(
 		return MG_FORM_FIELD_STORAGE_ABORT;
 		return MG_FORM_FIELD_STORAGE_ABORT;
 	}
 	}
 
 
-	mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1);
+	key_dec_len = mg_url_decode(
+	    key, (int)key_len, key_dec, (int)sizeof(key_dec), 1);
 
 
 	if (*value_len >= 2 && value[*value_len - 2] == '%')
 	if (*value_len >= 2 && value[*value_len - 2] == '%')
 		*value_len -= 2;
 		*value_len -= 2;
@@ -117,6 +119,11 @@ url_encoded_field_get(
 	value_dec_len = mg_url_decode(
 	value_dec_len = mg_url_decode(
 	    value, (int)*value_len, value_dec, ((int)*value_len) + 1, 1);
 	    value, (int)*value_len, value_dec, ((int)*value_len) + 1, 1);
 
 
+	if ((key_dec_len < 0) || (value_dec_len < 0)) {
+		mg_free(value_dec);
+		return MG_FORM_FIELD_STORAGE_ABORT;
+	}
+
 	ret = fdh->field_get(key_dec,
 	ret = fdh->field_get(key_dec,
 	                     value_dec,
 	                     value_dec,
 	                     (size_t)value_dec_len,
 	                     (size_t)value_dec_len,
@@ -136,9 +143,13 @@ unencoded_field_get(const struct mg_connection *conn,
                     struct mg_form_data_handler *fdh)
                     struct mg_form_data_handler *fdh)
 {
 {
 	char key_dec[1024];
 	char key_dec[1024];
+	int key_dec_len;
 	(void)conn;
 	(void)conn;
 
 
-	mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1);
+	key_dec_len = mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1);
+	if (key_dec_len < 0) {
+		return MG_FORM_FIELD_STORAGE_ABORT;
+	}
 
 
 	return fdh->field_get(key_dec, value, value_len, fdh->user_data);
 	return fdh->field_get(key_dec, value, value_len, fdh->user_data);
 }
 }
@@ -191,6 +202,7 @@ mg_handle_form_request(struct mg_connection *conn,
 	size_t buf_fill = 0;
 	size_t buf_fill = 0;
 	int r;
 	int r;
 	int field_count = 0;
 	int field_count = 0;
+	int abort_read = 0;
 	struct mg_file fstore = STRUCT_FILE_INITIALIZER;
 	struct mg_file fstore = STRUCT_FILE_INITIALIZER;
 	int64_t file_size = 0; /* init here, to a avoid a false positive
 	int64_t file_size = 0; /* init here, to a avoid a false positive
 	                         "uninitialized variable used" warning */
 	                         "uninitialized variable used" warning */
@@ -281,6 +293,7 @@ mg_handle_form_request(struct mg_connection *conn,
 				    conn, data, (size_t)keylen, val, (size_t *)&vallen, fdh);
 				    conn, data, (size_t)keylen, val, (size_t *)&vallen, fdh);
 				if (r == MG_FORM_FIELD_HANDLE_ABORT) {
 				if (r == MG_FORM_FIELD_HANDLE_ABORT) {
 					/* Stop request handling */
 					/* Stop request handling */
+					abort_read = 1;
 					break;
 					break;
 				}
 				}
 				if (r == MG_FORM_FIELD_HANDLE_NEXT) {
 				if (r == MG_FORM_FIELD_HANDLE_NEXT) {
@@ -323,6 +336,7 @@ mg_handle_form_request(struct mg_connection *conn,
 							r = field_stored(conn, path, file_size, fdh);
 							r = field_stored(conn, path, file_size, fdh);
 							if (r == MG_FORM_FIELD_HANDLE_ABORT) {
 							if (r == MG_FORM_FIELD_HANDLE_ABORT) {
 								/* Stop request handling */
 								/* Stop request handling */
+								abort_read = 1;
 								break;
 								break;
 							}
 							}
 
 
@@ -361,6 +375,7 @@ mg_handle_form_request(struct mg_connection *conn,
 			if ((field_storage & MG_FORM_FIELD_STORAGE_ABORT)
 			if ((field_storage & MG_FORM_FIELD_STORAGE_ABORT)
 			    == MG_FORM_FIELD_STORAGE_ABORT) {
 			    == MG_FORM_FIELD_STORAGE_ABORT) {
 				/* Stop parsing the request */
 				/* Stop parsing the request */
+				abort_read = 1;
 				break;
 				break;
 			}
 			}
 
 
@@ -389,7 +404,7 @@ mg_handle_form_request(struct mg_connection *conn,
 		 * Here we use "POST", and read the data from the request body.
 		 * Here we use "POST", and read the data from the request body.
 		 * The data read on the fly, so it is not required to buffer the
 		 * The data read on the fly, so it is not required to buffer the
 		 * entire request in memory before processing it. */
 		 * entire request in memory before processing it. */
-		for (;;) {
+		while (!abort_read) {
 			const char *val;
 			const char *val;
 			const char *next;
 			const char *next;
 			ptrdiff_t keylen, vallen;
 			ptrdiff_t keylen, vallen;
@@ -443,6 +458,7 @@ mg_handle_form_request(struct mg_connection *conn,
 			if ((field_storage & MG_FORM_FIELD_STORAGE_ABORT)
 			if ((field_storage & MG_FORM_FIELD_STORAGE_ABORT)
 			    == MG_FORM_FIELD_STORAGE_ABORT) {
 			    == MG_FORM_FIELD_STORAGE_ABORT) {
 				/* Stop parsing the request */
 				/* Stop parsing the request */
+				abort_read = 1;
 				break;
 				break;
 			}
 			}
 
 
@@ -471,6 +487,15 @@ mg_handle_form_request(struct mg_connection *conn,
 				} else {
 				} else {
 					vallen = (ptrdiff_t)strlen(val);
 					vallen = (ptrdiff_t)strlen(val);
 					end_of_key_value_pair_found = all_data_read;
 					end_of_key_value_pair_found = all_data_read;
+					if ((buf + buf_fill) > (val + vallen)) {
+						/* Avoid DoS attacks by having a zero byte in the middle of
+						 * a request that is supposed to be URL encoded. Since this
+						 * request is certainly invalid, according to the protocol
+						 * specification, stop processing it. Fixes #1348 */
+						abort_read = 1;
+						break;
+					}
+
 				}
 				}
 
 
 				if (field_storage == MG_FORM_FIELD_STORAGE_GET) {
 				if (field_storage == MG_FORM_FIELD_STORAGE_GET) {
@@ -492,6 +517,7 @@ mg_handle_form_request(struct mg_connection *conn,
 					get_block++;
 					get_block++;
 					if (r == MG_FORM_FIELD_HANDLE_ABORT) {
 					if (r == MG_FORM_FIELD_HANDLE_ABORT) {
 						/* Stop request handling */
 						/* Stop request handling */
+						abort_read = 1;
 						break;
 						break;
 					}
 					}
 					if (r == MG_FORM_FIELD_HANDLE_NEXT) {
 					if (r == MG_FORM_FIELD_HANDLE_NEXT) {
@@ -560,7 +586,6 @@ mg_handle_form_request(struct mg_connection *conn,
 						val = buf;
 						val = buf;
 					}
 					}
 				}
 				}
-
 			} while (!end_of_key_value_pair_found);
 			} while (!end_of_key_value_pair_found);
 
 
 #if !defined(NO_FILESYSTEMS)
 #if !defined(NO_FILESYSTEMS)
@@ -571,6 +596,7 @@ mg_handle_form_request(struct mg_connection *conn,
 					r = field_stored(conn, path, file_size, fdh);
 					r = field_stored(conn, path, file_size, fdh);
 					if (r == MG_FORM_FIELD_HANDLE_ABORT) {
 					if (r == MG_FORM_FIELD_HANDLE_ABORT) {
 						/* Stop request handling */
 						/* Stop request handling */
+						abort_read = 1;
 						break;
 						break;
 					}
 					}
 				} else {
 				} else {
@@ -584,7 +610,7 @@ mg_handle_form_request(struct mg_connection *conn,
 			}
 			}
 #endif /* NO_FILESYSTEMS */
 #endif /* NO_FILESYSTEMS */
 
 
-			if (all_data_read && (buf_fill == 0)) {
+			if ((all_data_read && (buf_fill == 0)) || abort_read) {
 				/* nothing more to process */
 				/* nothing more to process */
 				break;
 				break;
 			}
 			}
@@ -972,6 +998,7 @@ mg_handle_form_request(struct mg_connection *conn,
 					get_block++;
 					get_block++;
 					if (r == MG_FORM_FIELD_HANDLE_ABORT) {
 					if (r == MG_FORM_FIELD_HANDLE_ABORT) {
 						/* Stop request handling */
 						/* Stop request handling */
+						abort_read = 1;
 						break;
 						break;
 					}
 					}
 					if (r == MG_FORM_FIELD_HANDLE_NEXT) {
 					if (r == MG_FORM_FIELD_HANDLE_NEXT) {
@@ -1046,6 +1073,7 @@ mg_handle_form_request(struct mg_connection *conn,
 				                        fdh);
 				                        fdh);
 				if (r == MG_FORM_FIELD_HANDLE_ABORT) {
 				if (r == MG_FORM_FIELD_HANDLE_ABORT) {
 					/* Stop request handling */
 					/* Stop request handling */
+					abort_read = 1;
 					break;
 					break;
 				}
 				}
 				if (r == MG_FORM_FIELD_HANDLE_NEXT) {
 				if (r == MG_FORM_FIELD_HANDLE_NEXT) {
@@ -1074,6 +1102,7 @@ mg_handle_form_request(struct mg_connection *conn,
 							r = field_stored(conn, path, file_size, fdh);
 							r = field_stored(conn, path, file_size, fdh);
 							if (r == MG_FORM_FIELD_HANDLE_ABORT) {
 							if (r == MG_FORM_FIELD_HANDLE_ABORT) {
 								/* Stop request handling */
 								/* Stop request handling */
+								abort_read = 1;
 								break;
 								break;
 							}
 							}
 						} else {
 						} else {
@@ -1092,6 +1121,7 @@ mg_handle_form_request(struct mg_connection *conn,
 			if ((field_storage & MG_FORM_FIELD_STORAGE_ABORT)
 			if ((field_storage & MG_FORM_FIELD_STORAGE_ABORT)
 			    == MG_FORM_FIELD_STORAGE_ABORT) {
 			    == MG_FORM_FIELD_STORAGE_ABORT) {
 				/* Stop parsing the request */
 				/* Stop parsing the request */
+				abort_read = 1;
 				break;
 				break;
 			}
 			}
 
 

+ 2 - 2
test/page4.lp

@@ -22,10 +22,10 @@ the "Kepler syntax" uses <code>&lt;?lua chunk ?&gt;</code>, <code>&lt;?lua= expr
 <h2>Tags</h2>
 <h2>Tags</h2>
 
 
 <code>
 <code>
-&lt;? greeting = 'CiwetWeb' ?&gt;<br/>
+&lt;? greeting = 'CivetWeb' ?&gt;<br/>
 &lt;strong&gt;&lt;?= greeting %&gt;&lt;/strong&gt;<br/>
 &lt;strong&gt;&lt;?= greeting %&gt;&lt;/strong&gt;<br/>
 </code><br/>
 </code><br/>
-<? greeting = 'CiwetWeb' ?>
+<? greeting = 'CivetWeb' ?>
 ==> <strong><?= greeting ?></strong><br/>
 ==> <strong><?= greeting ?></strong><br/>
 
 
 <br/>
 <br/>

+ 2 - 2
test/page4kepler.lp

@@ -19,10 +19,10 @@ the "Kepler syntax" uses <code>&lt;?lua chunk ?&gt;</code>, <code>&lt;?lua= expr
 <h2>Tags</h2>
 <h2>Tags</h2>
 
 
 <code>
 <code>
-&lt;? greeting = 'CiwetWeb' ?&gt;<br/>
+&lt;? greeting = 'CivetWeb' ?&gt;<br/>
 &lt;strong&gt;&lt;?= greeting %&gt;&lt;/strong&gt;<br/>
 &lt;strong&gt;&lt;?= greeting %&gt;&lt;/strong&gt;<br/>
 </code><br/>
 </code><br/>
-<? greeting = 'CiwetWeb' ?>
+<? greeting = 'CivetWeb' ?>
 ==> <strong><?= greeting ?></strong><br/>
 ==> <strong><?= greeting ?></strong><br/>
 
 
 <br/>
 <br/>