File php7-CVE-2025-1217.patch of Package php7.37963

Index: php-7.4.33/ext/standard/http_fopen_wrapper.c
===================================================================
--- php-7.4.33.orig/ext/standard/http_fopen_wrapper.c
+++ php-7.4.33/ext/standard/http_fopen_wrapper.c
@@ -30,6 +30,7 @@
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <stdbool.h>
 #include <errno.h>
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -82,6 +83,22 @@
 #define HTTP_WRAPPER_HEADER_INIT    1
 #define HTTP_WRAPPER_REDIRECTED     2
 
+zend_string *zend_string_concat3(
+                const char *str1, size_t str1_len,
+                const char *str2, size_t str2_len,
+                const char *str3, size_t str3_len)
+{
+        size_t len = str1_len + str2_len + str3_len;
+        zend_string *res = zend_string_alloc(len, 0);
+
+        memcpy(ZSTR_VAL(res), str1, str1_len);
+        memcpy(ZSTR_VAL(res) + str1_len, str2, str2_len);
+        memcpy(ZSTR_VAL(res) + str1_len + str2_len, str3, str3_len);
+        ZSTR_VAL(res)[len] = '\0';
+
+        return res;
+} 
+
 static inline void strip_header(char *header_bag, char *lc_header_bag,
 		const char *lc_header_name)
 {
@@ -116,6 +133,171 @@ static zend_bool check_has_header(const
 	return 0;
 }
 
+typedef struct _php_stream_http_response_header_info {
+	php_stream_filter *transfer_encoding;
+	size_t file_size;
+	bool follow_location;
+	char location[HTTP_HEADER_BLOCK_SIZE];
+} php_stream_http_response_header_info;
+
+static void php_stream_http_response_header_info_init(
+		php_stream_http_response_header_info *header_info)
+{
+	header_info->transfer_encoding = NULL;
+	header_info->file_size = 0;
+	header_info->follow_location = 1;
+	header_info->location[0] = '\0';
+}
+
+/* Trim white spaces from response header line and update its length */
+static bool php_stream_http_response_header_trim(char *http_header_line,
+		size_t *http_header_line_length)
+{
+	char *http_header_line_end = http_header_line + *http_header_line_length - 1;
+	while (http_header_line_end >= http_header_line &&
+			(*http_header_line_end == '\n' || *http_header_line_end == '\r')) {
+		http_header_line_end--;
+	}
+
+	/* The primary definition of an HTTP header in RFC 7230 states:
+	* > Each header field consists of a case-insensitive field name followed
+	* > by a colon (":"), optional leading whitespace, the field value, and
+	* > optional trailing whitespace. */
+
+	/* Strip trailing whitespace */
+	bool space_trim = (*http_header_line_end == ' ' || *http_header_line_end == '\t');
+	if (space_trim) {
+		do {
+			http_header_line_end--;
+		} while (http_header_line_end >= http_header_line &&
+				(*http_header_line_end == ' ' || *http_header_line_end == '\t'));
+	}
+	http_header_line_end++;
+	*http_header_line_end = '\0';
+	*http_header_line_length = http_header_line_end - http_header_line;
+
+	return space_trim;
+}
+
+/* Process folding headers of the current line and if there are none, parse last full response
+ * header line. It returns NULL if the last header is finished, otherwise it returns updated
+ * last header line.  */
+static zend_string *php_stream_http_response_headers_parse(php_stream *stream,
+		php_stream_context *context, int options, zend_string *last_header_line_str,
+		char *header_line, size_t *header_line_length, int response_code,
+		zval *response_header, php_stream_http_response_header_info *header_info)
+{
+	char *last_header_line = ZSTR_VAL(last_header_line_str);
+	size_t last_header_line_length = ZSTR_LEN(last_header_line_str);
+	char *last_header_line_end = ZSTR_VAL(last_header_line_str) + ZSTR_LEN(last_header_line_str) - 1;
+
+	/* Process non empty header line. */
+	if (header_line && (*header_line != '\n' && *header_line != '\r')) {
+		/* Removing trailing white spaces. */
+		if (php_stream_http_response_header_trim(header_line, header_line_length) &&
+				*header_line_length == 0) {
+			/* Only spaces so treat as an empty folding header. */
+			return last_header_line_str;
+		}
+
+		/* Process folding headers if starting with a space or a tab. */
+		if (header_line && (*header_line == ' ' || *header_line == '\t')) {
+			char *http_folded_header_line = header_line;
+			size_t http_folded_header_line_length = *header_line_length;
+			/* Remove the leading white spaces. */
+			while (*http_folded_header_line == ' ' || *http_folded_header_line == '\t') {
+				http_folded_header_line++;
+				http_folded_header_line_length--;
+			}
+			/* It has to have some characters because it would get returned after the call
+			 * php_stream_http_response_header_trim above. */
+			ZEND_ASSERT(http_folded_header_line_length > 0);
+			/* Concatenate last header line, space and current header line. */
+			zend_string *extended_header_str = zend_string_concat3(
+					last_header_line, last_header_line_length,
+					" ", 1,
+					http_folded_header_line, http_folded_header_line_length);
+			zend_string_efree(last_header_line_str);
+			last_header_line_str = extended_header_str;
+			/* Return new header line. */
+			return last_header_line_str;
+		}
+	}
+
+	/* Find header separator position. */
+	char *last_header_value = memchr(last_header_line, ':', last_header_line_length);
+	if (last_header_value) {
+		last_header_value++; /* Skip ':'. */
+
+		/* Strip leading whitespace. */
+		while (last_header_value < last_header_line_end
+				&& (*last_header_value == ' ' || *last_header_value == '\t')) {
+			last_header_value++;
+		}
+	} else {
+		/* There is no colon. Set the value to the end of the header line, which is effectively
+		 * an empty string. */
+		last_header_value = last_header_line_end;
+	}
+
+	bool store_header = true;
+	zval *tmpzval = NULL;
+
+	if (!strncasecmp(last_header_line, "Location:", sizeof("Location:")-1)) {
+		/* Check if the location should be followed. */
+		if (context && (tmpzval = php_stream_context_get_option(context, "http", "follow_location")) != NULL) {
+			header_info->follow_location = zval_is_true(tmpzval);
+		} else if (!((response_code >= 300 && response_code < 304)
+				|| 307 == response_code || 308 == response_code)) {
+			/* The redirection should not be automatic if follow_location is not set and
+			 * response_code not in (300, 301, 302, 303 and 307)
+			 * see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.1
+			 * RFC 7238 defines 308: http://tools.ietf.org/html/rfc7238 */
+			header_info->follow_location = 0;
+		}
+		strlcpy(header_info->location, last_header_value, sizeof(header_info->location));
+	} else if (!strncasecmp(last_header_line, "Content-Type:", sizeof("Content-Type:")-1)) {
+		php_stream_notify_info(context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, last_header_value, 0);
+	} else if (!strncasecmp(last_header_line, "Content-Length:", sizeof("Content-Length:")-1)) {
+		header_info->file_size = atoi(last_header_value);
+		php_stream_notify_file_size(context, header_info->file_size, last_header_line, 0);
+	} else if (
+		!strncasecmp(last_header_line, "Transfer-Encoding:", sizeof("Transfer-Encoding:")-1)
+		&& !strncasecmp(last_header_value, "Chunked", sizeof("Chunked")-1)
+	) {
+		/* Create filter to decode response body. */
+		if (!(options & STREAM_ONLY_GET_HEADERS)) {
+			zend_long decode = 1;
+
+			if (context && (tmpzval = php_stream_context_get_option(context, "http", "auto_decode")) != NULL) {
+				decode = zend_is_true(tmpzval);
+			}
+			if (decode) {
+				if (header_info->transfer_encoding != NULL) {
+					/* Prevent a memory leak in case there are more transfer-encoding headers. */
+					php_stream_filter_free(header_info->transfer_encoding);
+				}
+				header_info->transfer_encoding = php_stream_filter_create(
+						"dechunk", NULL, php_stream_is_persistent(stream));
+				if (header_info->transfer_encoding != NULL) {
+					/* Do not store transfer-encoding header. */
+					store_header = false;
+				}
+			}
+		}
+	}
+
+	if (store_header) {
+		zval http_header;
+		ZVAL_NEW_STR(&http_header, last_header_line_str);
+		zend_hash_next_index_insert(Z_ARRVAL_P(response_header), &http_header);
+	} else {
+		zend_string_efree(last_header_line_str);
+	}
+
+	return NULL;
+}
+
 static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
 		const char *path, const char *mode, int options, zend_string **opened_path,
 		php_stream_context *context, int redirect_max, int flags,
@@ -128,11 +310,12 @@ static php_stream *php_stream_url_wrap_h
 	zend_string *tmp = NULL;
 	char *ua_str = NULL;
 	zval *ua_zval = NULL, *tmpzval = NULL, ssl_proxy_peer_name;
-	char location[HTTP_HEADER_BLOCK_SIZE];
 	int reqok = 0;
 	char *http_header_line = NULL;
+	zend_string *last_header_line_str = NULL;
+	php_stream_http_response_header_info header_info;
 	char tmp_line[128];
-	size_t chunk_size = 0, file_size = 0;
+	size_t chunk_size = 0;
 	int eol_detect = 0;
 	char *transport_string;
 	zend_string *errstr = NULL;
@@ -143,8 +326,6 @@ static php_stream *php_stream_url_wrap_h
 	char *user_headers = NULL;
 	int header_init = ((flags & HTTP_WRAPPER_HEADER_INIT) != 0);
 	int redirected = ((flags & HTTP_WRAPPER_REDIRECTED) != 0);
-	zend_bool follow_location = 1;
-	php_stream_filter *transfer_encoding = NULL;
 	int response_code;
 	smart_str req_buf = {0};
 	zend_bool custom_request_method;
@@ -657,8 +838,6 @@ finish:
 	/* send it */
 	php_stream_write(stream, ZSTR_VAL(req_buf.s), ZSTR_LEN(req_buf.s));
 
-	location[0] = '\0';
-
 	if (Z_ISUNDEF_P(response_header)) {
 		array_init(response_header);
 	}
@@ -736,125 +915,96 @@ finish:
 		}
 	}
 
-	/* read past HTTP headers */
+        php_stream_http_response_header_info_init(&header_info);
 
+	/* read past HTTP headers */
 	while (!php_stream_eof(stream)) {
 		size_t http_header_line_length;
 
 		if (http_header_line != NULL) {
 			efree(http_header_line);
 		}
-		if ((http_header_line = php_stream_get_line(stream, NULL, 0, &http_header_line_length)) && *http_header_line != '\n' && *http_header_line != '\r') {
-			char *e = http_header_line + http_header_line_length - 1;
-			char *http_header_value;
-
-			while (e >= http_header_line && (*e == '\n' || *e == '\r')) {
-				e--;
-			}
-
-			/* The primary definition of an HTTP header in RFC 7230 states:
-			 * > Each header field consists of a case-insensitive field name followed
-			 * > by a colon (":"), optional leading whitespace, the field value, and
-			 * > optional trailing whitespace. */
-
-			/* Strip trailing whitespace */
-			while (e >= http_header_line && (*e == ' ' || *e == '\t')) {
-				e--;
-			}
-
-			/* Terminate header line */
-			e++;
-			*e = '\0';
-			http_header_line_length = e - http_header_line;
-
-			http_header_value = memchr(http_header_line, ':', http_header_line_length);
-			if (http_header_value) {
-				http_header_value++; /* Skip ':' */
-
-				/* Strip leading whitespace */
-				while (http_header_value < e
-						&& (*http_header_value == ' ' || *http_header_value == '\t')) {
-					http_header_value++;
-				}
+                if ((http_header_line = php_stream_get_line(stream, NULL, 0, &http_header_line_length))) {
+                        bool last_line;
+                        if (*http_header_line == '\r') {
+                                if (http_header_line[1] != '\n') {
+                                        php_stream_close(stream);
+                                        stream = NULL;
+                                        php_stream_wrapper_log_error(wrapper, options,
+                                                        "HTTP invalid header name (cannot start with CR character)!");
+                                        goto out;
+                                }
+                                last_line = true;
+                        } else if (*http_header_line == '\n') {
+                               last_line = true;
 			} else {
-				/* There is no colon. Set the value to the end of the header line, which is
-				 * effectively an empty string. */
-				http_header_value = e;
+                               last_line = false;
 			}
 
-			if (!strncasecmp(http_header_line, "Location:", sizeof("Location:")-1)) {
-				if (context && (tmpzval = php_stream_context_get_option(context, "http", "follow_location")) != NULL) {
-					follow_location = zval_is_true(tmpzval);
-				} else if (!((response_code >= 300 && response_code < 304)
-						|| 307 == response_code || 308 == response_code)) {
-					/* we shouldn't redirect automatically
-					if follow_location isn't set and response_code not in (300, 301, 302, 303 and 307)
-					see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.1
-					RFC 7238 defines 308: http://tools.ietf.org/html/rfc7238 */
-					follow_location = 0;
-				}
-				strlcpy(location, http_header_value, sizeof(location));
-			} else if (!strncasecmp(http_header_line, "Content-Type:", sizeof("Content-Type:")-1)) {
-				php_stream_notify_info(context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, http_header_value, 0);
-			} else if (!strncasecmp(http_header_line, "Content-Length:", sizeof("Content-Length:")-1)) {
-				file_size = atoi(http_header_value);
-				php_stream_notify_file_size(context, file_size, http_header_line, 0);
-			} else if (
-				!strncasecmp(http_header_line, "Transfer-Encoding:", sizeof("Transfer-Encoding:")-1)
-				&& !strncasecmp(http_header_value, "Chunked", sizeof("Chunked")-1)
-			) {
-
-				/* create filter to decode response body */
-				if (!(options & STREAM_ONLY_GET_HEADERS)) {
-					zend_long decode = 1;
-
-					if (context && (tmpzval = php_stream_context_get_option(context, "http", "auto_decode")) != NULL) {
-						decode = zend_is_true(tmpzval);
-					}
-					if (decode) {
-						transfer_encoding = php_stream_filter_create("dechunk", NULL, php_stream_is_persistent(stream));
-						if (transfer_encoding) {
-							/* don't store transfer-encodeing header */
-							continue;
-						}
-					}
-				}
-			}
-
-			{
-				zval http_header;
-				ZVAL_STRINGL(&http_header, http_header_line, http_header_line_length);
-				zend_hash_next_index_insert(Z_ARRVAL_P(response_header), &http_header);
-			}
+                        if (last_header_line_str != NULL) {
+                                /* Parse last header line. */
+                                last_header_line_str = php_stream_http_response_headers_parse(stream, context,
+                                                options, last_header_line_str, http_header_line, &http_header_line_length,
+                                                response_code, response_header, &header_info);
+                                if (last_header_line_str != NULL) {
+                                        /* Folding header present so continue. */
+                                        continue;
+                                }
+                        } else if (!last_line) {
+                                /* The first line cannot start with spaces. */
+                                if (*http_header_line == ' ' || *http_header_line == '\t') {
+                                        php_stream_close(stream);
+                                        stream = NULL;
+                                        php_stream_wrapper_log_error(wrapper, options,
+                                                        "HTTP invalid response format (folding header at the start)!");
+                                        goto out;
+                                }
+                                /* Trim the first line if it is not the last line. */
+                                php_stream_http_response_header_trim(http_header_line, &http_header_line_length);
+                        }
+                        if (last_line) {
+                                /* For the last line the last header line must be NULL. */
+                                ZEND_ASSERT(last_header_line_str == NULL);
+                                break;
+                        }
+                        /* Save current line as the last line so it gets parsed in the next round. */
+                        last_header_line_str = zend_string_init(http_header_line, http_header_line_length, 0);
 		} else {
 			break;
 		}
 	}
 
-	if (!reqok || (location[0] != '\0' && follow_location)) {
-		if (!follow_location || (((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) && redirect_max <= 1)) {
+       /* If the stream was closed early, we still want to process the last line to keep BC. */
+       if (last_header_line_str != NULL) {
+               php_stream_http_response_headers_parse(stream, context, options, last_header_line_str,
+                               NULL, NULL, response_code, response_header, &header_info);
+       }
+
+       if (!reqok || (header_info.location[0] != '\0' && header_info.follow_location)) {
+               if (!header_info.follow_location || (((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) && redirect_max <= 1)) {
 			goto out;
 		}
 
-		if (location[0] != '\0')
-			php_stream_notify_info(context, PHP_STREAM_NOTIFY_REDIRECTED, location, 0);
+                if (header_info.location[0] != '\0')
+                        php_stream_notify_info(context, PHP_STREAM_NOTIFY_REDIRECTED, header_info.location, 0);
 
 		php_stream_close(stream);
 		stream = NULL;
 
-		if (location[0] != '\0') {
+               if (header_info.location[0] != '\0') {
 
 			char new_path[HTTP_HEADER_BLOCK_SIZE];
 			char loc_path[HTTP_HEADER_BLOCK_SIZE];
 
 			*new_path='\0';
-			if (strlen(location)<8 || (strncasecmp(location, "http://", sizeof("http://")-1) &&
-							strncasecmp(location, "https://", sizeof("https://")-1) &&
-							strncasecmp(location, "ftp://", sizeof("ftp://")-1) &&
-							strncasecmp(location, "ftps://", sizeof("ftps://")-1)))
+                        if (strlen(header_info.location) < 8 ||
+                                        (strncasecmp(header_info.location, "http://", sizeof("http://")-1) &&
+                                                        strncasecmp(header_info.location, "https://", sizeof("https://")-1) &&
+                                                        strncasecmp(header_info.location, "ftp://", sizeof("ftp://")-1) &&
+                                                        strncasecmp(header_info.location, "ftps://", sizeof("ftps://")-1)))
 			{
-				if (*location != '/') {
-					if (*(location+1) != '\0' && resource->path) {
+                                if (*header_info.location != '/') {
+                                        if (*(header_info.location+1) != '\0' && resource->path) {
 						char *s = strrchr(ZSTR_VAL(resource->path), '/');
 						if (!s) {
 							s = ZSTR_VAL(resource->path);
@@ -870,15 +1020,17 @@ finish:
 						if (resource->path &&
 							ZSTR_VAL(resource->path)[0] == '/' &&
 							ZSTR_VAL(resource->path)[1] == '\0') {
-							snprintf(loc_path, sizeof(loc_path) - 1, "%s%s", ZSTR_VAL(resource->path), location);
+                                                        snprintf(loc_path, sizeof(loc_path) - 1, "%s%s",
+                                                                        ZSTR_VAL(resource->path), header_info.location);
 						} else {
-							snprintf(loc_path, sizeof(loc_path) - 1, "%s/%s", ZSTR_VAL(resource->path), location);
+                                                        snprintf(loc_path, sizeof(loc_path) - 1, "%s/%s",
+                                                                        ZSTR_VAL(resource->path), header_info.location);
 						}
 					} else {
-						snprintf(loc_path, sizeof(loc_path) - 1, "/%s", location);
+                                                snprintf(loc_path, sizeof(loc_path) - 1, "/%s", header_info.location);
 					}
 				} else {
-					strlcpy(loc_path, location, sizeof(loc_path));
+                                        strlcpy(loc_path, header_info.location, sizeof(loc_path));
 				}
 				if ((use_ssl && resource->port != 443) || (!use_ssl && resource->port != 80)) {
 					snprintf(new_path, sizeof(new_path) - 1, "%s://%s:%d%s", ZSTR_VAL(resource->scheme), ZSTR_VAL(resource->host), resource->port, loc_path);
@@ -886,7 +1038,7 @@ finish:
 					snprintf(new_path, sizeof(new_path) - 1, "%s://%s%s", ZSTR_VAL(resource->scheme), ZSTR_VAL(resource->host), loc_path);
 				}
 			} else {
-				strlcpy(new_path, location, sizeof(new_path));
+				strlcpy(new_path, header_info.location, sizeof(new_path));
 			}
 
 			php_url_free(resource);
@@ -939,7 +1091,7 @@ out:
 		if (header_init) {
 			ZVAL_COPY(&stream->wrapperdata, response_header);
 		}
-		php_stream_notify_progress_init(context, 0, file_size);
+		php_stream_notify_progress_init(context, 0, header_info.file_size);
 
 		/* Restore original chunk size now that we're done with headers */
 		if (options & STREAM_WILL_CAST)
@@ -955,12 +1107,8 @@ out:
 		/* restore mode */
 		strlcpy(stream->mode, mode, sizeof(stream->mode));
 
-		if (transfer_encoding) {
-			php_stream_filter_append(&stream->readfilters, transfer_encoding);
-		}
-	} else {
-		if (transfer_encoding) {
-			php_stream_filter_free(transfer_encoding);
+		if (header_info.transfer_encoding) {
+			php_stream_filter_append(&stream->readfilters, header_info.transfer_encoding);
 		}
 	}
 
openSUSE Build Service is sponsored by