diff --git a/NEWS b/NEWS index 54a45028c6a14..e75b26e7d1436 100644 --- a/NEWS +++ b/NEWS @@ -153,6 +153,8 @@ PHP NEWS . Fixed bug GH-21221 (Prevent closing of innerstream of php://temp stream). (ilutov) . Improved stream_socket_server() bind failure error reporting. (ilutov) + . Fixed bug #49874 (ftell() and fseek() inconsistency when using stream + filters). (Jakub Zelenka) - Zip: . Fixed ZipArchive callback being called after executor has shut down. diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index 57bef65c25ee9..1072d822ee492 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -74,6 +74,15 @@ PHP 8.6 INTERNALS UPGRADE NOTES longer is a pointer, but a directly embedded HashTable struct. . Added a C23_ENUM() helper macro to define forward-compatible fixed-size enums. + . Extended php_stream_filter_ops with seek method. + . The INI_STR(), INI_INT(), INI_FLT(), and INI_BOOL() macros have been + removed. Instead new zend_ini_{bool|long|double|str|string}_literal() + macros have been added. This fixes an internal naming inconsistency as + "str" usually means zend_string*, and "string" means char*. + However INI_STR() returned a char* + . The INI_ORIG_{INT|STR|FLT|BOOL}() macros have been removed as they are + unused. If this behaviour is required fall back to the zend_ini_* + functions. ======================== 2. Build system changes diff --git a/Zend/zend_fibers.c b/Zend/zend_fibers.c index d571a622e476b..248b4f401a71a 100644 --- a/Zend/zend_fibers.c +++ b/Zend/zend_fibers.c @@ -572,9 +572,9 @@ static ZEND_STACK_ALIGNED void zend_fiber_execute(zend_fiber_transfer *transfer) zend_fiber *fiber = EG(active_fiber); /* Determine the current error_reporting ini setting. */ - zend_long error_reporting = INI_INT("error_reporting"); - /* If error_reporting is 0 and not explicitly set to 0, INI_STR returns a null pointer. */ - if (!error_reporting && !INI_STR("error_reporting")) { + zend_long error_reporting = zend_ini_long_literal("error_reporting"); + /* If error_reporting is 0 and not explicitly set to 0, zend_ini_str returns a null pointer. */ + if (!error_reporting && !zend_ini_str_literal("error_reporting")) { error_reporting = E_ALL; } diff --git a/Zend/zend_highlight.c b/Zend/zend_highlight.c index 5c3cd136d5807..c5fc874b9cb5f 100644 --- a/Zend/zend_highlight.c +++ b/Zend/zend_highlight.c @@ -79,8 +79,8 @@ ZEND_API void zend_highlight(zend_syntax_highlighter_ini *syntax_highlighter_ini { zval token; int token_type; - char *last_color = syntax_highlighter_ini->highlight_html; - char *next_color; + const char *last_color = syntax_highlighter_ini->highlight_html; + const char *next_color; zend_printf("
", last_color);
 	/* highlight stuff coming back from zendlex() */
diff --git a/Zend/zend_highlight.h b/Zend/zend_highlight.h
index 04688d65132b2..adc1d3c8c81e0 100644
--- a/Zend/zend_highlight.h
+++ b/Zend/zend_highlight.h
@@ -30,11 +30,11 @@
 
 
 typedef struct _zend_syntax_highlighter_ini {
-	char *highlight_html;
-	char *highlight_comment;
-	char *highlight_default;
-	char *highlight_string;
-	char *highlight_keyword;
+	const char *highlight_html;
+	const char *highlight_comment;
+	const char *highlight_default;
+	const char *highlight_string;
+	const char *highlight_keyword;
 } zend_syntax_highlighter_ini;
 
 
diff --git a/Zend/zend_ini.c b/Zend/zend_ini.c
index d8f1d4f50f95d..85739415feb32 100644
--- a/Zend/zend_ini.c
+++ b/Zend/zend_ini.c
@@ -491,7 +491,7 @@ ZEND_API double zend_ini_double(const char *name, size_t name_length, bool orig)
 }
 /* }}} */
 
-ZEND_API char *zend_ini_string_ex(const char *name, size_t name_length, bool orig, bool *exists) /* {{{ */
+ZEND_API const char *zend_ini_string_ex(const char *name, size_t name_length, bool orig, bool *exists) /* {{{ */
 {
 	zend_string *str = zend_ini_str_ex(name, name_length, orig, exists);
 
@@ -499,7 +499,7 @@ ZEND_API char *zend_ini_string_ex(const char *name, size_t name_length, bool ori
 }
 /* }}} */
 
-ZEND_API char *zend_ini_string(const char *name, size_t name_length, bool orig) /* {{{ */
+ZEND_API const char *zend_ini_string(const char *name, size_t name_length, bool orig) /* {{{ */
 {
 	zend_string *str = zend_ini_str(name, name_length, orig);
 
diff --git a/Zend/zend_ini.h b/Zend/zend_ini.h
index d8d7f599f461e..d2419bb160fda 100644
--- a/Zend/zend_ini.h
+++ b/Zend/zend_ini.h
@@ -88,13 +88,19 @@ ZEND_API void display_ini_entries(zend_module_entry *module);
 
 ZEND_API zend_long zend_ini_long(const char *name, size_t name_length, bool orig);
 ZEND_API double zend_ini_double(const char *name, size_t name_length, bool orig);
-ZEND_API char *zend_ini_string(const char *name, size_t name_length, bool orig);
-ZEND_API char *zend_ini_string_ex(const char *name, size_t name_length, bool orig, bool *exists);
+ZEND_API const char *zend_ini_string(const char *name, size_t name_length, bool orig);
+ZEND_API const char *zend_ini_string_ex(const char *name, size_t name_length, bool orig, bool *exists);
 ZEND_API zend_string *zend_ini_str(const char *name, size_t name_length, bool orig);
 ZEND_API zend_string *zend_ini_str_ex(const char *name, size_t name_length, bool orig, bool *exists);
 ZEND_API zend_string *zend_ini_get_value(zend_string *name);
 ZEND_API bool zend_ini_parse_bool(const zend_string *str);
 
+#define zend_ini_bool_literal(name) zend_ini_parse_bool(zend_ini_str((name), sizeof("" name) - 1, false))
+#define zend_ini_long_literal(name) zend_ini_long((name), sizeof("" name) - 1, false)
+#define zend_ini_double_literal(name) zend_ini_double((name), sizeof("" name) - 1, false)
+#define zend_ini_str_literal(name) zend_ini_str((name), sizeof("" name) - 1, false)
+#define zend_ini_string_literal(name) zend_ini_string((name), sizeof("" name) - 1, false)
+
 /**
  * Parses an ini quantity
  *
@@ -191,16 +197,6 @@ END_EXTERN_C()
 	ZEND_INI_ENTRY3_EX(name, default_value, modifiable, on_modify, (void *) XtOffsetOf(struct_type, property_name), (void *) &struct_ptr, NULL, zend_ini_boolean_displayer_cb)
 #endif
 
-#define INI_INT(name) zend_ini_long((name), strlen(name), 0)
-#define INI_FLT(name) zend_ini_double((name), strlen(name), 0)
-#define INI_STR(name) zend_ini_string_ex((name), strlen(name), 0, NULL)
-#define INI_BOOL(name) ((bool) INI_INT(name))
-
-#define INI_ORIG_INT(name)	zend_ini_long((name), strlen(name), 1)
-#define INI_ORIG_FLT(name)	zend_ini_double((name), strlen(name), 1)
-#define INI_ORIG_STR(name)	zend_ini_string((name), strlen(name), 1)
-#define INI_ORIG_BOOL(name) ((bool) INI_ORIG_INT(name))
-
 #define REGISTER_INI_ENTRIES() zend_register_ini_entries_ex(ini_entries, module_number, type)
 #define UNREGISTER_INI_ENTRIES() zend_unregister_ini_entries_ex(module_number, type)
 #define DISPLAY_INI_ENTRIES() display_ini_entries(zend_module)
diff --git a/Zend/zend_multibyte.c b/Zend/zend_multibyte.c
index f61ed79fd1f7a..fc130162f0837 100644
--- a/Zend/zend_multibyte.c
+++ b/Zend/zend_multibyte.c
@@ -114,8 +114,8 @@ ZEND_API zend_result zend_multibyte_set_functions(const zend_multibyte_functions
 	 * populated, we need to reinitialize script_encoding here.
 	 */
 	{
-		const char *value = zend_ini_string("zend.script_encoding", sizeof("zend.script_encoding") - 1, 0);
-		zend_multibyte_set_script_encoding_by_string(value, strlen(value));
+		const zend_string *value = zend_ini_str_literal("zend.script_encoding");
+		zend_multibyte_set_script_encoding_by_string(ZSTR_VAL(value), ZSTR_LEN(value));
 	}
 	return SUCCESS;
 }
diff --git a/ext/bz2/bz2_filter.c b/ext/bz2/bz2_filter.c
index 27059391dacb9..69ee483d21f12 100644
--- a/ext/bz2/bz2_filter.c
+++ b/ext/bz2/bz2_filter.c
@@ -42,6 +42,10 @@ typedef struct _php_bz2_filter_data {
 	unsigned int is_flushed : 1;          /* only for compression */
 
 	int persistent;
+
+	/* Configuration for reset - immutable */
+	int blockSize100k;  /* compress only */
+	int workFactor;     /* compress only */
 } php_bz2_filter_data;
 
 /* }}} */
@@ -178,6 +182,36 @@ static php_stream_filter_status_t php_bz2_decompress_filter(
 	return exit_status;
 }
 
+static zend_result php_bz2_decompress_seek(
+	php_stream *stream,
+	php_stream_filter *thisfilter,
+	zend_off_t offset,
+	int whence
+	)
+{
+	if (!Z_PTR(thisfilter->abstract)) {
+		return FAILURE;
+	}
+
+	php_bz2_filter_data *data = Z_PTR(thisfilter->abstract);
+
+	/* End current decompression if running */
+	if (data->status == PHP_BZ2_RUNNING) {
+		BZ2_bzDecompressEnd(&(data->strm));
+	}
+
+	/* Reset stream state */
+	data->strm.next_in = data->inbuf;
+	data->strm.avail_in = 0;
+	data->strm.next_out = data->outbuf;
+	data->strm.avail_out = data->outbuf_len;
+	data->status = PHP_BZ2_UNINITIALIZED;
+
+	/* Note: We don't reinitialize here - it will be done on first use in the filter function */
+
+	return SUCCESS;
+}
+
 static void php_bz2_decompress_dtor(php_stream_filter *thisfilter)
 {
 	if (thisfilter && Z_PTR(thisfilter->abstract)) {
@@ -193,6 +227,7 @@ static void php_bz2_decompress_dtor(php_stream_filter *thisfilter)
 
 static const php_stream_filter_ops php_bz2_decompress_ops = {
 	php_bz2_decompress_filter,
+	php_bz2_decompress_seek,
 	php_bz2_decompress_dtor,
 	"bzip2.decompress"
 };
@@ -288,6 +323,41 @@ static php_stream_filter_status_t php_bz2_compress_filter(
 	return exit_status;
 }
 
+static zend_result php_bz2_compress_seek(
+	php_stream *stream,
+	php_stream_filter *thisfilter,
+	zend_off_t offset,
+	int whence
+	)
+{
+	int status;
+
+	if (!Z_PTR(thisfilter->abstract)) {
+		return FAILURE;
+	}
+
+	php_bz2_filter_data *data = Z_PTR(thisfilter->abstract);
+
+	/* End current compression */
+	BZ2_bzCompressEnd(&(data->strm));
+
+	/* Reset stream state */
+	data->strm.next_in = data->inbuf;
+	data->strm.avail_in = 0;
+	data->strm.next_out = data->outbuf;
+	data->strm.avail_out = data->outbuf_len;
+	data->is_flushed = 1;
+
+	/* Reinitialize compression with saved configuration */
+	status = BZ2_bzCompressInit(&(data->strm), data->blockSize100k, 0, data->workFactor);
+	if (status != BZ_OK) {
+		php_error_docref(NULL, E_WARNING, "bzip2.compress: failed to reset compression state");
+		return FAILURE;
+	}
+
+	return SUCCESS;
+}
+
 static void php_bz2_compress_dtor(php_stream_filter *thisfilter)
 {
 	if (Z_PTR(thisfilter->abstract)) {
@@ -301,6 +371,7 @@ static void php_bz2_compress_dtor(php_stream_filter *thisfilter)
 
 static const php_stream_filter_ops php_bz2_compress_ops = {
 	php_bz2_compress_filter,
+	php_bz2_compress_seek,
 	php_bz2_compress_dtor,
 	"bzip2.compress"
 };
@@ -309,7 +380,7 @@ static const php_stream_filter_ops php_bz2_compress_ops = {
 
 /* {{{ bzip2.* common factory */
 
-static php_stream_filter *php_bz2_filter_create(const char *filtername, zval *filterparams, uint8_t persistent)
+static php_stream_filter *php_bz2_filter_create(const char *filtername, zval *filterparams, bool persistent)
 {
 	const php_stream_filter_ops *fops = NULL;
 	php_bz2_filter_data *data;
@@ -388,6 +459,10 @@ static php_stream_filter *php_bz2_filter_create(const char *filtername, zval *fi
 			}
 		}
 
+		/* Save configuration for reset */
+		data->blockSize100k = blockSize100k;
+		data->workFactor = workFactor;
+
 		status = BZ2_bzCompressInit(&(data->strm), blockSize100k, 0, workFactor);
 		data->is_flushed = 1;
 		fops = &php_bz2_compress_ops;
@@ -403,7 +478,7 @@ static php_stream_filter *php_bz2_filter_create(const char *filtername, zval *fi
 		return NULL;
 	}
 
-	return php_stream_filter_alloc(fops, data, persistent);
+	return php_stream_filter_alloc(fops, data, persistent, PSFS_SEEKABLE_START);
 }
 
 const php_stream_filter_factory php_bz2_filter_factory = {
diff --git a/ext/bz2/tests/bz2_filter_seek_compress.phpt b/ext/bz2/tests/bz2_filter_seek_compress.phpt
new file mode 100644
index 0000000000000..0656b244484d9
--- /dev/null
+++ b/ext/bz2/tests/bz2_filter_seek_compress.phpt
@@ -0,0 +1,55 @@
+--TEST--
+bzip2.compress filter with seek to start
+--EXTENSIONS--
+bz2
+--FILE--
+ $size1 ? "YES" : "NO") . "\n";
+
+$result = fseek($fp, 50, SEEK_SET);
+echo "Seek to middle: " . ($result === 0 ? "SUCCESS" : "FAILURE") . "\n";
+
+fclose($fp);
+
+$fp = fopen($file, 'r');
+stream_filter_append($fp, 'bzip2.decompress', STREAM_FILTER_READ);
+$content = stream_get_contents($fp);
+fclose($fp);
+
+echo "Decompressed content matches text2: " . ($content === $text2 ? "YES" : "NO") . "\n";
+?>
+--CLEAN--
+
+--EXPECTF--
+Size after first write: 40
+Seek to start: SUCCESS
+Size after second write: 98
+Second write is larger: YES
+
+Warning: fseek(): Stream filter bzip2.compress is seekable only to start position in %s on line %d
+Seek to middle: FAILURE
+Decompressed content matches text2: YES
diff --git a/ext/bz2/tests/bz2_filter_seek_decompress.phpt b/ext/bz2/tests/bz2_filter_seek_decompress.phpt
new file mode 100644
index 0000000000000..8ccd224b1a406
--- /dev/null
+++ b/ext/bz2/tests/bz2_filter_seek_decompress.phpt
@@ -0,0 +1,43 @@
+--TEST--
+bzip2.decompress filter with seek to start
+--EXTENSIONS--
+bz2
+--FILE--
+
+--CLEAN--
+
+--EXPECTF--
+First read (20 bytes): I am the very model 
+Seek to start: SUCCESS
+Content after seek matches: YES
+
+Warning: fseek(): Stream filter bzip2.decompress is seekable only to start position in %s on line %d
+Seek to middle: FAILURE
diff --git a/ext/com_dotnet/com_dotnet.c b/ext/com_dotnet/com_dotnet.c
index f8b4a828e154a..e4ab984858312 100644
--- a/ext/com_dotnet/com_dotnet.c
+++ b/ext/com_dotnet/com_dotnet.c
@@ -127,7 +127,6 @@ static HRESULT dotnet_bind_runtime(LPVOID FAR *ppv)
 	typedef HRESULT (STDAPICALLTYPE *cbtr_t)(LPCWSTR pwszVersion, LPCWSTR pwszBuildFlavor, REFCLSID rclsid, REFIID riid, LPVOID FAR *ppv);
 	cbtr_t CorBindToRuntime;
 	OLECHAR *oleversion;
-	char *version;
 
 	mscoree = LoadLibraryA("mscoree.dll");
 	if (mscoree == NULL) {
@@ -140,11 +139,11 @@ static HRESULT dotnet_bind_runtime(LPVOID FAR *ppv)
 		return S_FALSE;
 	}
 
-	version = INI_STR("com.dotnet_version");
-	if (version == NULL || *version == '\0') {
+	const zend_string *version = zend_ini_str_literal("com.dotnet_version");
+	if (version == NULL || ZSTR_LEN(version) == 0) {
 		oleversion = NULL;
 	} else {
-		oleversion = php_com_string_to_olestring(version, strlen(version), COMG(code_page));
+		oleversion = php_com_string_to_olestring(ZSTR_VAL(version), ZSTR_LEN(version), COMG(code_page));
 	}
 
 	hr = CorBindToRuntime(oleversion, NULL, &CLSID_CorRuntimeHost, &IID_ICorRuntimeHost, ppv);
diff --git a/ext/curl/interface.c b/ext/curl/interface.c
index 6328538241fe6..57bc2f1ede3b9 100644
--- a/ext/curl/interface.c
+++ b/ext/curl/interface.c
@@ -1100,8 +1100,6 @@ static void create_certinfo(struct curl_certinfo *ci, zval *listcode)
    Set default options for a handle */
 static void _php_curl_set_default_options(php_curl *ch)
 {
-	char *cainfo;
-
 	curl_easy_setopt(ch->cp, CURLOPT_NOPROGRESS,        1L);
 	curl_easy_setopt(ch->cp, CURLOPT_VERBOSE,           0L);
 	curl_easy_setopt(ch->cp, CURLOPT_ERRORBUFFER,       ch->err.str);
@@ -1114,9 +1112,9 @@ static void _php_curl_set_default_options(php_curl *ch)
 	curl_easy_setopt(ch->cp, CURLOPT_DNS_CACHE_TIMEOUT, 120L);
 	curl_easy_setopt(ch->cp, CURLOPT_MAXREDIRS, 20L); /* prevent infinite redirects */
 
-	cainfo = INI_STR("openssl.cafile");
+	const char *cainfo = zend_ini_string_literal("openssl.cafile");
 	if (!(cainfo && cainfo[0] != '\0')) {
-		cainfo = INI_STR("curl.cainfo");
+		cainfo = zend_ini_string_literal("curl.cainfo");
 	}
 	if (cainfo && cainfo[0] != '\0') {
 		curl_easy_setopt(ch->cp, CURLOPT_CAINFO, cainfo);
diff --git a/ext/date/php_date.c b/ext/date/php_date.c
index dd80e15e9cfbe..e5b094acdb3cd 100644
--- a/ext/date/php_date.c
+++ b/ext/date/php_date.c
@@ -5478,18 +5478,18 @@ static void php_do_date_sunrise_sunset(INTERNAL_FUNCTION_PARAMETERS, bool calc_s
 	ZEND_PARSE_PARAMETERS_END();
 
 	if (latitude_is_null) {
-		latitude = INI_FLT("date.default_latitude");
+		latitude = zend_ini_double_literal("date.default_latitude");
 	}
 
 	if (longitude_is_null) {
-		longitude = INI_FLT("date.default_longitude");
+		longitude = zend_ini_double_literal("date.default_longitude");
 	}
 
 	if (zenith_is_null) {
 		if (calc_sunset) {
-			zenith = INI_FLT("date.sunset_zenith");
+			zenith = zend_ini_double_literal("date.sunset_zenith");
 		} else {
-			zenith = INI_FLT("date.sunrise_zenith");
+			zenith = zend_ini_double_literal("date.sunrise_zenith");
 		}
 	}
 
diff --git a/ext/gd/gd.c b/ext/gd/gd.c
index 33b397b4f699b..1bd1a26f05199 100644
--- a/ext/gd/gd.c
+++ b/ext/gd/gd.c
@@ -1595,7 +1595,7 @@ static void _php_image_create_from(INTERNAL_FUNCTION_PARAMETERS, int image_type,
 
 #ifdef HAVE_GD_JPG
 			case PHP_GDIMG_TYPE_JPG:
-				ignore_warning = INI_INT("gd.jpeg_ignore_warning");
+				ignore_warning = zend_ini_bool_literal("gd.jpeg_ignore_warning");
 				im = gdImageCreateFromJpegEx(fp, ignore_warning);
 			break;
 #endif
diff --git a/ext/gd/tests/bug79945.phpt b/ext/gd/tests/bug79945.phpt
index 5db958e36d358..46dac358d8e21 100644
--- a/ext/gd/tests/bug79945.phpt
+++ b/ext/gd/tests/bug79945.phpt
@@ -9,17 +9,17 @@ if (!(imagetypes() & IMG_PNG)) {
 }
 set_error_handler(function($errno, $errstr) {
     if (str_contains($errstr, 'Cannot cast a filtered stream on this system')) {
-        die('skip: fopencookie not support on this system');
+        die('skip: fopencookie not supported on this system');
     }
 });
-imagecreatefrompng('php://filter/read=convert.base64-encode/resource=' . __DIR__ . '/test.png');
+imagecreatefrompng('php://filter/read=string.rot13/resource=' . __DIR__ . '/test.png');
 restore_error_handler();
 ?>
 --FILE--
 
 --CLEAN--
 --EXPECTF--
 
-Warning: imagecreatefrompng(): "php://filter/read=convert.base64-encode/resource=%s" is not a valid PNG file in %s on line %d
+Warning: imagecreatefrompng(): "php://filter/read=string.rot13/resource=%s" is not a valid PNG file in %s on line %d
diff --git a/ext/iconv/iconv.c b/ext/iconv/iconv.c
index ed7d332397dbc..02cffcb4b859b 100644
--- a/ext/iconv/iconv.c
+++ b/ext/iconv/iconv.c
@@ -2585,6 +2585,33 @@ static php_stream_filter_status_t php_iconv_stream_filter_do_filter(
 }
 /* }}} */
 
+/* {{{ php_iconv_stream_filter_seek */
+static zend_result php_iconv_stream_filter_seek(
+		php_stream *stream,
+		php_stream_filter *filter,
+		zend_off_t offset,
+		int whence)
+{
+	php_iconv_stream_filter *self = (php_iconv_stream_filter *)Z_PTR(filter->abstract);
+
+	/* Reset stub buffer */
+	self->stub_len = 0;
+
+	/* Reset iconv conversion state by closing and reopening the converter */
+	iconv_close(self->cd);
+
+	self->cd = iconv_open(self->to_charset, self->from_charset);
+	if ((iconv_t)-1 == self->cd) {
+		php_error_docref(NULL, E_WARNING,
+				"iconv stream filter (\"%s\"=>\"%s\"): failed to reset conversion state",
+				self->from_charset, self->to_charset);
+		return FAILURE;
+	}
+
+	return SUCCESS;
+}
+/* }}} */
+
 /* {{{ php_iconv_stream_filter_cleanup */
 static void php_iconv_stream_filter_cleanup(php_stream_filter *filter)
 {
@@ -2595,12 +2622,13 @@ static void php_iconv_stream_filter_cleanup(php_stream_filter *filter)
 
 static const php_stream_filter_ops php_iconv_stream_filter_ops = {
 	php_iconv_stream_filter_do_filter,
+	php_iconv_stream_filter_seek,
 	php_iconv_stream_filter_cleanup,
 	"convert.iconv.*"
 };
 
 /* {{{ php_iconv_stream_filter_create */
-static php_stream_filter *php_iconv_stream_filter_factory_create(const char *name, zval *params, uint8_t persistent)
+static php_stream_filter *php_iconv_stream_filter_factory_create(const char *name, zval *params, bool persistent)
 {
 	php_iconv_stream_filter *inst;
 	const char *from_charset = NULL, *to_charset = NULL;
@@ -2632,7 +2660,8 @@ static php_stream_filter *php_iconv_stream_filter_factory_create(const char *nam
 		return NULL;
 	}
 
-	return php_stream_filter_alloc(&php_iconv_stream_filter_ops, inst, persistent);
+	return php_stream_filter_alloc(&php_iconv_stream_filter_ops, inst, persistent,
+			PSFS_SEEKABLE_START);
 }
 /* }}} */
 
diff --git a/ext/iconv/tests/iconv_stream_filter_seek.phpt b/ext/iconv/tests/iconv_stream_filter_seek.phpt
new file mode 100644
index 0000000000000..97519ec7853d3
--- /dev/null
+++ b/ext/iconv/tests/iconv_stream_filter_seek.phpt
@@ -0,0 +1,43 @@
+--TEST--
+iconv stream filter with seek to start
+--EXTENSIONS--
+iconv
+--FILE--
+
+--CLEAN--
+
+--EXPECTF--
+First read (20 bytes): Hello, this is a tes
+Seek to start: SUCCESS
+Content after seek matches: YES
+
+Warning: fseek(): Stream filter convert.iconv.* is seekable only to start position in %s on line %d
+Seek to middle: FAILURE
diff --git a/ext/openssl/openssl_backend_common.c b/ext/openssl/openssl_backend_common.c
index 6f257af49819e..533d2f2c61f1b 100644
--- a/ext/openssl/openssl_backend_common.c
+++ b/ext/openssl/openssl_backend_common.c
@@ -504,10 +504,8 @@ void php_openssl_set_cert_locations(zval *return_value)
 	add_assoc_string(return_value, "default_cert_dir_env", (char *) X509_get_default_cert_dir_env());
 	add_assoc_string(return_value, "default_private_dir", (char *) X509_get_default_private_dir());
 	add_assoc_string(return_value, "default_default_cert_area", (char *) X509_get_default_cert_area());
-	add_assoc_string(return_value, "ini_cafile",
-		zend_ini_string("openssl.cafile", sizeof("openssl.cafile")-1, 0));
-	add_assoc_string(return_value, "ini_capath",
-		zend_ini_string("openssl.capath", sizeof("openssl.capath")-1, 0));
+	add_assoc_str(return_value, "ini_cafile", zend_string_copy(zend_ini_str_literal("openssl.cafile")));
+	add_assoc_str(return_value, "ini_capath", zend_string_copy(zend_ini_str_literal("openssl.capath")));
 }
 
 X509 *php_openssl_x509_from_str(
diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c
index 34b4acd9f1c74..8f9395e460223 100644
--- a/ext/openssl/xp_ssl.c
+++ b/ext/openssl/xp_ssl.c
@@ -880,16 +880,16 @@ static long php_openssl_load_stream_cafile(X509_STORE *cert_store, const char *c
 static zend_result php_openssl_enable_peer_verification(SSL_CTX *ctx, php_stream *stream) /* {{{ */
 {
 	zval *val = NULL;
-	char *cafile = NULL;
-	char *capath = NULL;
+	const char *cafile = NULL;
+	const char *capath = NULL;
 	php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
 
 	GET_VER_OPT_STRING("cafile", cafile);
 	GET_VER_OPT_STRING("capath", capath);
 
 	if (cafile == NULL) {
-		cafile = zend_ini_string("openssl.cafile", sizeof("openssl.cafile")-1, 0);
-		cafile = strlen(cafile) ? cafile : NULL;
+		const zend_string *cafile_str = zend_ini_str_literal("openssl.cafile");
+		cafile = ZSTR_LEN(cafile_str) ? ZSTR_VAL(cafile_str) : NULL;
 	} else if (!sslsock->is_client) {
 		/* Servers need to load and assign CA names from the cafile */
 		STACK_OF(X509_NAME) *cert_names = SSL_load_client_CA_file(cafile);
@@ -902,8 +902,8 @@ static zend_result php_openssl_enable_peer_verification(SSL_CTX *ctx, php_stream
 	}
 
 	if (capath == NULL) {
-		capath = zend_ini_string("openssl.capath", sizeof("openssl.capath")-1, 0);
-		capath = strlen(capath) ? capath : NULL;
+		const zend_string *capath_str = zend_ini_str_literal("openssl.capath");
+		capath = ZSTR_LEN(capath_str) ? ZSTR_VAL(capath_str) : NULL;
 	}
 
 	if (cafile || capath) {
diff --git a/ext/session/session.c b/ext/session/session.c
index c04e19e25edc7..51ab3922e0bdf 100644
--- a/ext/session/session.c
+++ b/ext/session/session.c
@@ -1654,7 +1654,7 @@ PHPAPI zend_result php_session_start(void)
 			return FAILURE;
 
 		case php_session_disabled: {
-			const char *value = zend_ini_string(ZEND_STRL("session.save_handler"), false);
+			const char *value = zend_ini_string_literal("session.save_handler");
 			if (!PS(mod) && value) {
 				PS(mod) = _php_find_ps_module(value);
 				if (!PS(mod)) {
@@ -1662,7 +1662,7 @@ PHPAPI zend_result php_session_start(void)
 					return FAILURE;
 				}
 			}
-			value = zend_ini_string(ZEND_STRL("session.serialize_handler"), false);
+			value = zend_ini_string_literal("session.serialize_handler");
 			if (!PS(serializer) && value) {
 				PS(serializer) = _php_find_ps_serializer(value);
 				if (!PS(serializer)) {
@@ -2768,14 +2768,14 @@ static zend_result php_rinit_session(bool auto_start)
 
 	PS(mod) = NULL;
 	{
-		const char *value = zend_ini_string(ZEND_STRL("session.save_handler"), false);
+		const char *value = zend_ini_string_literal("session.save_handler");
 		if (value) {
 			PS(mod) = _php_find_ps_module(value);
 		}
 	}
 
 	if (PS(serializer) == NULL) {
-		const char *value = zend_ini_string(ZEND_STRL("session.serialize_handler"), false);
+		const char *value = zend_ini_string_literal("session.serialize_handler");
 		if (value) {
 			PS(serializer) = _php_find_ps_serializer(value);
 		}
diff --git a/ext/soap/soap.c b/ext/soap/soap.c
index 178660ec4cd62..59e2bd4168052 100644
--- a/ext/soap/soap.c
+++ b/ext/soap/soap.c
@@ -1695,7 +1695,7 @@ PHP_METHOD(SoapServer, handle)
 			sapi_add_header("Content-Type: text/xml; charset=utf-8", sizeof("Content-Type: text/xml; charset=utf-8")-1, 1);
 		}
 
-		if (INI_INT("zlib.output_compression")) {
+		if (zend_ini_long_literal("zlib.output_compression")) {
 			sapi_add_header("Connection: close", sizeof("Connection: close")-1, 1);
 		} else {
 			snprintf(cont_len, sizeof(cont_len), "Content-Length: %d", size);
@@ -1857,7 +1857,7 @@ static void soap_server_fault_ex(sdlFunctionPtr function, zval* fault, soapHeade
 	if (use_http_error_status) {
 		sapi_add_header("HTTP/1.1 500 Internal Server Error", sizeof("HTTP/1.1 500 Internal Server Error")-1, 1);
 	}
-	if (INI_INT("zlib.output_compression")) {
+	if (zend_ini_long_literal("zlib.output_compression")) {
 		sapi_add_header("Connection: close", sizeof("Connection: close")-1, 1);
 	} else {
 		snprintf(cont_len, sizeof(cont_len), "Content-Length: %d", size);
diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c
index fbbeeb0b433f4..a7417f2a39036 100644
--- a/ext/standard/basic_functions.c
+++ b/ext/standard/basic_functions.c
@@ -1717,11 +1717,11 @@ PHPAPI bool append_user_shutdown_function(php_shutdown_function_entry *shutdown_
 
 ZEND_API void php_get_highlight_struct(zend_syntax_highlighter_ini *syntax_highlighter_ini) /* {{{ */
 {
-	syntax_highlighter_ini->highlight_comment = INI_STR("highlight.comment");
-	syntax_highlighter_ini->highlight_default = INI_STR("highlight.default");
-	syntax_highlighter_ini->highlight_html    = INI_STR("highlight.html");
-	syntax_highlighter_ini->highlight_keyword = INI_STR("highlight.keyword");
-	syntax_highlighter_ini->highlight_string  = INI_STR("highlight.string");
+	syntax_highlighter_ini->highlight_comment = zend_ini_string_literal("highlight.comment");
+	syntax_highlighter_ini->highlight_default = zend_ini_string_literal("highlight.default");
+	syntax_highlighter_ini->highlight_html    = zend_ini_string_literal("highlight.html");
+	syntax_highlighter_ini->highlight_keyword = zend_ini_string_literal("highlight.keyword");
+	syntax_highlighter_ini->highlight_string  = zend_ini_string_literal("highlight.string");
 }
 /* }}} */
 
@@ -2029,17 +2029,16 @@ PHP_FUNCTION(ini_restore)
 PHP_FUNCTION(set_include_path)
 {
 	zend_string *new_value;
-	char *old_value;
 	zend_string *key;
 
 	ZEND_PARSE_PARAMETERS_START(1, 1)
 		Z_PARAM_PATH_STR(new_value)
 	ZEND_PARSE_PARAMETERS_END();
 
-	old_value = zend_ini_string("include_path", sizeof("include_path") - 1, 0);
+	zend_string *old_value = zend_ini_str_literal("include_path");
 	/* copy to return here, because alter might free it! */
 	if (old_value) {
-		RETVAL_STRING(old_value);
+		RETVAL_STR_COPY(old_value);
 	} else {
 		RETVAL_FALSE;
 	}
@@ -2059,7 +2058,7 @@ PHP_FUNCTION(get_include_path)
 {
 	ZEND_PARSE_PARAMETERS_NONE();
 
-	zend_string *str = zend_ini_str("include_path", sizeof("include_path") - 1, 0);
+	zend_string *str = zend_ini_str_literal("include_path");
 
 	if (str == NULL) {
 		RETURN_FALSE;
diff --git a/ext/standard/browscap.c b/ext/standard/browscap.c
index 5009c7793688c..ec9e3a59e4b85 100644
--- a/ext/standard/browscap.c
+++ b/ext/standard/browscap.c
@@ -337,7 +337,7 @@ static void php_browscap_parser_cb(zval *arg1, zval *arg2, zval *arg3, int callb
 					) {
 						zend_error(E_CORE_ERROR, "Invalid browscap ini file: "
 							"'Parent' value cannot be same as the section name: %s "
-							"(in file %s)", ZSTR_VAL(ctx->current_section_name), INI_STR("browscap"));
+							"(in file %s)", ZSTR_VAL(ctx->current_section_name), zend_ini_string_literal("browscap"));
 						return;
 					}
 
@@ -399,7 +399,7 @@ static void php_browscap_parser_cb(zval *arg1, zval *arg2, zval *arg3, int callb
 }
 /* }}} */
 
-static zend_result browscap_read_file(char *filename, browser_data *browdata, bool persistent) /* {{{ */
+static zend_result browscap_read_file(const char *filename, browser_data *browdata, bool persistent) /* {{{ */
 {
 	zend_file_handle fh;
 	browscap_parser_ctx ctx = {0};
@@ -499,7 +499,7 @@ PHP_INI_MH(OnChangeBrowscap)
 
 PHP_MINIT_FUNCTION(browscap) /* {{{ */
 {
-	char *browscap = INI_STR("browscap");
+	const char *browscap = zend_ini_string_literal("browscap");
 
 #ifdef ZTS
 	ts_allocate_id(&browscap_globals_id, sizeof(browser_data), (ts_allocate_ctor) browscap_globals_ctor, NULL);
diff --git a/ext/standard/config.m4 b/ext/standard/config.m4
index ef6b3c5a01018..1d64c7ff69681 100644
--- a/ext/standard/config.m4
+++ b/ext/standard/config.m4
@@ -328,7 +328,7 @@ dnl
 
 PHP_CHECK_FUNC(res_search, resolv, socket)
 
-AC_CHECK_FUNCS([posix_spawn_file_actions_addchdir_np elf_aux_info])
+AC_CHECK_FUNCS([posix_spawn_file_actions_addchdir posix_spawn_file_actions_addchdir_np elf_aux_info])
 
 dnl
 dnl Obsolete check for strptime() declaration. The strptime, where available,
diff --git a/ext/standard/dl.c b/ext/standard/dl.c
index 31adbceac8c29..209b0c1d3b01c 100644
--- a/ext/standard/dl.c
+++ b/ext/standard/dl.c
@@ -114,11 +114,11 @@ PHPAPI int php_load_extension(const char *filename, int type, int start_now)
 	zend_module_entry *module_entry;
 	zend_module_entry *(*get_module)(void);
 	int error_type, slash_suffix = 0;
-	char *extension_dir;
+	const char *extension_dir;
 	char *err1, *err2;
 
 	if (type == MODULE_PERSISTENT) {
-		extension_dir = INI_STR("extension_dir");
+		extension_dir = zend_ini_string_literal("extension_dir");
 	} else {
 		extension_dir = PG(extension_dir);
 	}
diff --git a/ext/standard/filters.c b/ext/standard/filters.c
index 0faa0eda6863a..c909211788b03 100644
--- a/ext/standard/filters.c
+++ b/ext/standard/filters.c
@@ -59,12 +59,13 @@ static php_stream_filter_status_t strfilter_rot13_filter(
 static const php_stream_filter_ops strfilter_rot13_ops = {
 	strfilter_rot13_filter,
 	NULL,
+	NULL,
 	"string.rot13"
 };
 
-static php_stream_filter *strfilter_rot13_create(const char *filtername, zval *filterparams, uint8_t persistent)
+static php_stream_filter *strfilter_rot13_create(const char *filtername, zval *filterparams, bool persistent)
 {
-	return php_stream_filter_alloc(&strfilter_rot13_ops, NULL, persistent);
+	return php_stream_filter_alloc(&strfilter_rot13_ops, NULL, persistent, PSFS_SEEKABLE_ALWAYS);
 }
 
 static const php_stream_filter_factory strfilter_rot13_factory = {
@@ -135,23 +136,25 @@ static php_stream_filter_status_t strfilter_tolower_filter(
 static const php_stream_filter_ops strfilter_toupper_ops = {
 	strfilter_toupper_filter,
 	NULL,
+	NULL,
 	"string.toupper"
 };
 
 static const php_stream_filter_ops strfilter_tolower_ops = {
 	strfilter_tolower_filter,
 	NULL,
+	NULL,
 	"string.tolower"
 };
 
-static php_stream_filter *strfilter_toupper_create(const char *filtername, zval *filterparams, uint8_t persistent)
+static php_stream_filter *strfilter_toupper_create(const char *filtername, zval *filterparams, bool persistent)
 {
-	return php_stream_filter_alloc(&strfilter_toupper_ops, NULL, persistent);
+	return php_stream_filter_alloc(&strfilter_toupper_ops, NULL, persistent, PSFS_SEEKABLE_ALWAYS);
 }
 
-static php_stream_filter *strfilter_tolower_create(const char *filtername, zval *filterparams, uint8_t persistent)
+static php_stream_filter *strfilter_tolower_create(const char *filtername, zval *filterparams, bool persistent)
 {
-	return php_stream_filter_alloc(&strfilter_tolower_ops, NULL, persistent);
+	return php_stream_filter_alloc(&strfilter_tolower_ops, NULL, persistent, PSFS_SEEKABLE_ALWAYS);
 }
 
 static const php_stream_filter_factory strfilter_toupper_factory = {
@@ -181,14 +184,17 @@ typedef struct _php_conv php_conv;
 
 typedef php_conv_err_t (*php_conv_convert_func)(php_conv *, const char **, size_t *, char **, size_t *);
 typedef void (*php_conv_dtor_func)(php_conv *);
+typedef php_conv_err_t (*php_conv_reset_func)(php_conv *);
 
 struct _php_conv {
 	php_conv_convert_func convert_op;
 	php_conv_dtor_func dtor;
+	php_conv_reset_func reset_op;
 };
 
 #define php_conv_convert(a, b, c, d, e) ((php_conv *)(a))->convert_op((php_conv *)(a), (b), (c), (d), (e))
 #define php_conv_dtor(a) ((php_conv *)a)->dtor((a))
+#define php_conv_reset(a) (((php_conv *)(a))->reset_op ? ((php_conv *)(a))->reset_op((php_conv *)(a)) : PHP_CONV_ERR_SUCCESS)
 
 /* {{{ php_conv_base64_encode */
 typedef struct _php_conv_base64_encode {
@@ -206,6 +212,7 @@ typedef struct _php_conv_base64_encode {
 
 static php_conv_err_t php_conv_base64_encode_convert(php_conv_base64_encode *inst, const char **in_p, size_t *in_left, char **out_p, size_t *out_left);
 static void php_conv_base64_encode_dtor(php_conv_base64_encode *inst);
+static php_conv_err_t php_conv_base64_encode_reset(php_conv_base64_encode *inst);
 
 static const unsigned char b64_tbl_enc[256] = {
 	'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
@@ -226,10 +233,19 @@ static const unsigned char b64_tbl_enc[256] = {
 	'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
 };
 
+static php_conv_err_t php_conv_base64_encode_reset(php_conv_base64_encode *inst)
+{
+	/* Reset only mutable state, preserve configuration (lbchars, line_len, etc.) */
+	inst->erem_len = 0;
+	inst->line_ccnt = inst->line_len;
+	return PHP_CONV_ERR_SUCCESS;
+}
+
 static php_conv_err_t php_conv_base64_encode_ctor(php_conv_base64_encode *inst, unsigned int line_len, const char *lbchars, size_t lbchars_len, int lbchars_dup, bool persistent)
 {
 	inst->_super.convert_op = (php_conv_convert_func) php_conv_base64_encode_convert;
 	inst->_super.dtor = (php_conv_dtor_func) php_conv_base64_encode_dtor;
+	inst->_super.reset_op = (php_conv_reset_func) php_conv_base64_encode_reset;
 	inst->erem_len = 0;
 	inst->line_ccnt = line_len;
 	inst->line_len = line_len;
@@ -449,6 +465,7 @@ typedef struct _php_conv_base64_decode {
 
 static php_conv_err_t php_conv_base64_decode_convert(php_conv_base64_decode *inst, const char **in_p, size_t *in_left, char **out_p, size_t *out_left);
 static void php_conv_base64_decode_dtor(php_conv_base64_decode *inst);
+static php_conv_err_t php_conv_base64_decode_reset(php_conv_base64_decode *inst);
 
 static unsigned int b64_tbl_dec[256] = {
 	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
@@ -469,10 +486,21 @@ static unsigned int b64_tbl_dec[256] = {
 	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
 };
 
+static php_conv_err_t php_conv_base64_decode_reset(php_conv_base64_decode *inst)
+{
+	/* Reset only mutable state */
+	inst->urem = 0;
+	inst->urem_nbits = 0;
+	inst->ustat = 0;
+	inst->eos = 0;
+	return PHP_CONV_ERR_SUCCESS;
+}
+
 static void php_conv_base64_decode_ctor(php_conv_base64_decode *inst)
 {
 	inst->_super.convert_op = (php_conv_convert_func) php_conv_base64_decode_convert;
 	inst->_super.dtor = (php_conv_dtor_func) php_conv_base64_decode_dtor;
+	inst->_super.reset_op = (php_conv_reset_func) php_conv_base64_decode_reset;
 
 	inst->urem = 0;
 	inst->urem_nbits = 0;
@@ -618,6 +646,7 @@ typedef struct _php_conv_qprint_encode {
 
 static void php_conv_qprint_encode_dtor(php_conv_qprint_encode *inst);
 static php_conv_err_t php_conv_qprint_encode_convert(php_conv_qprint_encode *inst, const char **in_pp, size_t *in_left_p, char **out_pp, size_t *out_left_p);
+static php_conv_err_t php_conv_qprint_encode_reset(php_conv_qprint_encode *inst);
 
 static void php_conv_qprint_encode_dtor(php_conv_qprint_encode *inst)
 {
@@ -627,6 +656,15 @@ static void php_conv_qprint_encode_dtor(php_conv_qprint_encode *inst)
 	}
 }
 
+static php_conv_err_t php_conv_qprint_encode_reset(php_conv_qprint_encode *inst)
+{
+	/* Reset only mutable state, preserve configuration */
+	inst->line_ccnt = inst->line_len;
+	inst->lb_ptr = 0;
+	inst->lb_cnt = 0;
+	return PHP_CONV_ERR_SUCCESS;
+}
+
 #define NEXT_CHAR(ps, icnt, lb_ptr, lb_cnt, lbchars) \
 	((lb_ptr) < (lb_cnt) ? (lbchars)[(lb_ptr)] : *(ps))
 
@@ -832,6 +870,7 @@ static php_conv_err_t php_conv_qprint_encode_ctor(php_conv_qprint_encode *inst,
 	}
 	inst->_super.convert_op = (php_conv_convert_func) php_conv_qprint_encode_convert;
 	inst->_super.dtor = (php_conv_dtor_func) php_conv_qprint_encode_dtor;
+	inst->_super.reset_op = (php_conv_reset_func) php_conv_qprint_encode_reset;
 	inst->line_ccnt = line_len;
 	inst->line_len = line_len;
 	if (lbchars != NULL) {
@@ -862,6 +901,10 @@ typedef struct _php_conv_qprint_decode {
 	unsigned int lb_cnt;
 } php_conv_qprint_decode;
 
+static void php_conv_qprint_decode_dtor(php_conv_qprint_decode *inst);
+static php_conv_err_t php_conv_qprint_decode_convert(php_conv_qprint_decode *inst, const char **in_pp, size_t *in_left_p, char **out_pp, size_t *out_left_p);
+static php_conv_err_t php_conv_qprint_decode_reset(php_conv_qprint_decode *inst);
+
 static void php_conv_qprint_decode_dtor(php_conv_qprint_decode *inst)
 {
 	assert(inst != NULL);
@@ -870,6 +913,16 @@ static void php_conv_qprint_decode_dtor(php_conv_qprint_decode *inst)
 	}
 }
 
+static php_conv_err_t php_conv_qprint_decode_reset(php_conv_qprint_decode *inst)
+{
+	/* Reset only mutable state, preserve configuration */
+	inst->scan_stat = 0;
+	inst->next_char = 0;
+	inst->lb_ptr = 0;
+	inst->lb_cnt = 0;
+	return PHP_CONV_ERR_SUCCESS;
+}
+
 static php_conv_err_t php_conv_qprint_decode_convert(php_conv_qprint_decode *inst, const char **in_pp, size_t *in_left_p, char **out_pp, size_t *out_left_p)
 {
 	php_conv_err_t err = PHP_CONV_ERR_SUCCESS;
@@ -1040,10 +1093,12 @@ static php_conv_err_t php_conv_qprint_decode_convert(php_conv_qprint_decode *ins
 
 	return err;
 }
+
 static php_conv_err_t php_conv_qprint_decode_ctor(php_conv_qprint_decode *inst, const char *lbchars, size_t lbchars_len, int lbchars_dup, bool persistent)
 {
 	inst->_super.convert_op = (php_conv_convert_func) php_conv_qprint_decode_convert;
 	inst->_super.dtor = (php_conv_dtor_func) php_conv_qprint_decode_dtor;
+	inst->_super.reset_op = (php_conv_reset_func) php_conv_qprint_decode_reset;
 	inst->scan_stat = 0;
 	inst->next_char = 0;
 	inst->lb_ptr = inst->lb_cnt = 0;
@@ -1540,6 +1595,29 @@ static php_stream_filter_status_t strfilter_convert_filter(
 	return PSFS_ERR_FATAL;
 }
 
+static zend_result strfilter_convert_seek(
+	php_stream *stream,
+	php_stream_filter *thisfilter,
+	zend_off_t offset,
+	int whence
+	)
+{
+	php_convert_filter *inst = (php_convert_filter *)Z_PTR(thisfilter->abstract);
+	
+	/* Reset stub buffer */
+	inst->stub_len = 0;
+	
+	/* Reset the converter state - preserves all configuration/options */
+	if (inst->cd != NULL) {
+		php_conv_err_t err = php_conv_reset(inst->cd);
+		if (err != PHP_CONV_ERR_SUCCESS) {
+			return FAILURE;
+		}
+	}
+	
+	return SUCCESS;
+}
+
 static void strfilter_convert_dtor(php_stream_filter *thisfilter)
 {
 	assert(Z_PTR(thisfilter->abstract) != NULL);
@@ -1550,11 +1628,12 @@ static void strfilter_convert_dtor(php_stream_filter *thisfilter)
 
 static const php_stream_filter_ops strfilter_convert_ops = {
 	strfilter_convert_filter,
+	strfilter_convert_seek,
 	strfilter_convert_dtor,
 	"convert.*"
 };
 
-static php_stream_filter *strfilter_convert_create(const char *filtername, zval *filterparams, uint8_t persistent)
+static php_stream_filter *strfilter_convert_create(const char *filtername, zval *filterparams, bool persistent)
 {
 	php_convert_filter *inst;
 
@@ -1590,7 +1669,7 @@ static php_stream_filter *strfilter_convert_create(const char *filtername, zval
 		return NULL;
 	}
 
-	return php_stream_filter_alloc(&strfilter_convert_ops, inst, persistent);
+	return php_stream_filter_alloc(&strfilter_convert_ops, inst, persistent, PSFS_SEEKABLE_START);
 }
 
 static const php_stream_filter_factory strfilter_convert_factory = {
@@ -1637,6 +1716,22 @@ static php_stream_filter_status_t consumed_filter_filter(
 	return PSFS_PASS_ON;
 }
 
+static zend_result consumed_filter_seek(
+	php_stream *stream,
+	php_stream_filter *thisfilter,
+	zend_off_t offset,
+	int whence
+	)
+{
+	php_consumed_filter_data *data = (php_consumed_filter_data *)Z_PTR(thisfilter->abstract);
+	
+	/* Reset consumed state */
+	data->consumed = 0;
+	data->offset = ~0;
+	
+	return SUCCESS;
+}
+
 static void consumed_filter_dtor(php_stream_filter *thisfilter)
 {
 	if (thisfilter && Z_PTR(thisfilter->abstract)) {
@@ -1647,11 +1742,12 @@ static void consumed_filter_dtor(php_stream_filter *thisfilter)
 
 static const php_stream_filter_ops consumed_filter_ops = {
 	consumed_filter_filter,
+	consumed_filter_seek,
 	consumed_filter_dtor,
 	"consumed"
 };
 
-static php_stream_filter *consumed_filter_create(const char *filtername, zval *filterparams, uint8_t persistent)
+static php_stream_filter *consumed_filter_create(const char *filtername, zval *filterparams, bool persistent)
 {
 	const php_stream_filter_ops *fops = NULL;
 	php_consumed_filter_data *data;
@@ -1667,7 +1763,7 @@ static php_stream_filter *consumed_filter_create(const char *filtername, zval *f
 	data->offset = ~0;
 	fops = &consumed_filter_ops;
 
-	return php_stream_filter_alloc(fops, data, persistent);
+	return php_stream_filter_alloc(fops, data, persistent, PSFS_SEEKABLE_START);
 }
 
 static const php_stream_filter_factory consumed_filter_factory = {
@@ -1851,6 +1947,22 @@ static php_stream_filter_status_t php_chunked_filter(
 	return PSFS_PASS_ON;
 }
 
+static zend_result php_chunked_seek(
+	php_stream *stream,
+	php_stream_filter *thisfilter,
+	zend_off_t offset,
+	int whence
+	)
+{
+	php_chunked_filter_data *data = (php_chunked_filter_data *)Z_PTR(thisfilter->abstract);
+	
+	/* Reset chunked decoder state */
+	data->state = CHUNK_SIZE_START;
+	data->chunk_size = 0;
+	
+	return SUCCESS;
+}
+
 static void php_chunked_dtor(php_stream_filter *thisfilter)
 {
 	if (thisfilter && Z_PTR(thisfilter->abstract)) {
@@ -1861,11 +1973,12 @@ static void php_chunked_dtor(php_stream_filter *thisfilter)
 
 static const php_stream_filter_ops chunked_filter_ops = {
 	php_chunked_filter,
+	php_chunked_seek,
 	php_chunked_dtor,
 	"dechunk"
 };
 
-static php_stream_filter *chunked_filter_create(const char *filtername, zval *filterparams, uint8_t persistent)
+static php_stream_filter *chunked_filter_create(const char *filtername, zval *filterparams, bool persistent)
 {
 	const php_stream_filter_ops *fops = NULL;
 	php_chunked_filter_data *data;
@@ -1881,7 +1994,7 @@ static php_stream_filter *chunked_filter_create(const char *filtername, zval *fi
 	data->persistent = persistent;
 	fops = &chunked_filter_ops;
 
-	return php_stream_filter_alloc(fops, data, persistent);
+	return php_stream_filter_alloc(fops, data, persistent, PSFS_SEEKABLE_START);
 }
 
 static const php_stream_filter_factory chunked_filter_factory = {
diff --git a/ext/standard/mail.c b/ext/standard/mail.c
index 395c7bb81d4f9..c909c0ed55dd7 100644
--- a/ext/standard/mail.c
+++ b/ext/standard/mail.c
@@ -336,8 +336,8 @@ PHP_FUNCTION(mail)
 		subject_r = subject;
 	}
 
-	zend_string *force_extra_parameters = zend_ini_str_ex("mail.force_extra_parameters", strlen("mail.force_extra_parameters"), false, NULL);
-	if (force_extra_parameters) {
+	zend_string *force_extra_parameters = zend_ini_str_literal("mail.force_extra_parameters");
+	if (force_extra_parameters && ZSTR_LEN(force_extra_parameters) > 0) {
 		extra_cmd = php_escape_shell_cmd(force_extra_parameters);
 	} else if (extra_cmd) {
 		extra_cmd = php_escape_shell_cmd(extra_cmd);
@@ -437,9 +437,9 @@ static int php_mail_detect_multiple_crlf(const char *hdr) {
 PHPAPI bool php_mail(const char *to, const char *subject, const char *message, const char *headers, const zend_string *extra_cmd)
 {
 	FILE *sendmail;
-	char *sendmail_path = INI_STR("sendmail_path");
+	const char *sendmail_path = zend_ini_string_literal("sendmail_path");
 	char *sendmail_cmd = NULL;
-	const zend_string *mail_log = zend_ini_str(ZEND_STRL("mail.log"), false);
+	const zend_string *mail_log = zend_ini_str_literal("mail.log");
 	const char *hdr = headers;
 	char *ahdr = NULL;
 #if PHP_SIGCHILD
@@ -536,7 +536,7 @@ PHPAPI bool php_mail(const char *to, const char *subject, const char *message, c
 		char *tsm_errmsg = NULL;
 
 		/* handle old style win smtp sending */
-		if (TSendMail(INI_STR("SMTP"), &tsm_err, &tsm_errmsg, hdr, subject, to, message) == FAILURE) {
+		if (TSendMail(zend_ini_string_literal("SMTP"), &tsm_err, &tsm_errmsg, hdr, subject, to, message) == FAILURE) {
 			if (tsm_errmsg) {
 				php_error_docref(NULL, E_WARNING, "%s", tsm_errmsg);
 				efree(tsm_errmsg);
@@ -553,7 +553,7 @@ PHPAPI bool php_mail(const char *to, const char *subject, const char *message, c
 	if (extra_cmd != NULL) {
 		spprintf(&sendmail_cmd, 0, "%s %s", sendmail_path, ZSTR_VAL(extra_cmd));
 	} else {
-		sendmail_cmd = sendmail_path;
+		sendmail_cmd = (char*)sendmail_path;
 	}
 
 #if PHP_SIGCHILD
@@ -576,7 +576,7 @@ PHPAPI bool php_mail(const char *to, const char *subject, const char *message, c
 	sendmail = popen(sendmail_cmd, "w");
 #endif
 	if (extra_cmd != NULL) {
-		efree (sendmail_cmd);
+		efree(sendmail_cmd);
 	}
 
 	if (sendmail) {
@@ -701,7 +701,7 @@ PHPAPI bool php_mail(const char *to, const char *subject, const char *message, c
 /* {{{ PHP_MINFO_FUNCTION */
 PHP_MINFO_FUNCTION(mail)
 {
-	char *sendmail_path = INI_STR("sendmail_path");
+	const char *sendmail_path = zend_ini_string_literal("sendmail_path");
 
 #ifdef PHP_WIN32
 	if (!sendmail_path) {
diff --git a/ext/standard/proc_open.c b/ext/standard/proc_open.c
index 55f4dd4849587..a393a2a1f8848 100644
--- a/ext/standard/proc_open.c
+++ b/ext/standard/proc_open.c
@@ -35,7 +35,7 @@
 #include 
 #endif
 
-#ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP
+#if defined(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP) || defined(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR)
 /* Only defined on glibc >= 2.29, FreeBSD CURRENT, musl >= 1.1.24,
  * MacOS Catalina or later..
  * It should be possible to modify this so it is also
@@ -45,6 +45,13 @@
  */
 #include 
 #define USE_POSIX_SPAWN
+
+/* The non-_np variant is in macOS 26 (and _np deprecated) */
+#ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR
+#define POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR posix_spawn_file_actions_addchdir
+#else
+#define POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR posix_spawn_file_actions_addchdir_np
+#endif
 #endif
 
 /* This symbol is defined in ext/standard/config.m4.
@@ -1394,9 +1401,9 @@ PHP_FUNCTION(proc_open)
 	}
 
 	if (cwd) {
-		r = posix_spawn_file_actions_addchdir_np(&factions, cwd);
+		r = POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR(&factions, cwd);
 		if (r != 0) {
-			php_error_docref(NULL, E_WARNING, "posix_spawn_file_actions_addchdir_np() failed: %s", strerror(r));
+			php_error_docref(NULL, E_WARNING, ZEND_TOSTR(POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR) "() failed: %s", strerror(r));
 		}
 	}
 
diff --git a/ext/standard/tests/filters/convert_filter_seek.phpt b/ext/standard/tests/filters/convert_filter_seek.phpt
new file mode 100644
index 0000000000000..6cbcd7b843f35
--- /dev/null
+++ b/ext/standard/tests/filters/convert_filter_seek.phpt
@@ -0,0 +1,75 @@
+--TEST--
+convert filters (base64, quoted-printable) with seek to start only
+--FILE--
+
+--CLEAN--
+
+--EXPECTF--
+Testing convert.base64-encode/decode
+First read (20 bytes): Hello World! This is
+Seek to start: SUCCESS
+Content matches: YES
+
+Warning: fseek(): Stream filter convert.* is seekable only to start position in %s on line %d
+Seek to middle: FAILURE
+
+Testing convert.quoted-printable-encode/decode
+First read (10 bytes): 4c696e65310d0a4c696e
+Seek to start: SUCCESS
+Content matches: YES
+
+Warning: fseek(): Stream filter convert.* is seekable only to start position in %s on line %d
+Seek to middle: FAILURE
diff --git a/ext/standard/tests/filters/php_user_filter_04.phpt b/ext/standard/tests/filters/php_user_filter_04.phpt
new file mode 100644
index 0000000000000..72f874f21ad40
--- /dev/null
+++ b/ext/standard/tests/filters/php_user_filter_04.phpt
@@ -0,0 +1,28 @@
+--TEST--
+php_user_filter with invalid seek signature
+--FILE--
+
+--EXPECTF--
+Fatal error: Declaration of InvalidSeekFilter::seek($offset): bool must be compatible with php_user_filter::seek(int $offset, int $whence): bool in %s on line %d
diff --git a/ext/standard/tests/filters/string_filters_seek.phpt b/ext/standard/tests/filters/string_filters_seek.phpt
new file mode 100644
index 0000000000000..60eab2ccb84ae
--- /dev/null
+++ b/ext/standard/tests/filters/string_filters_seek.phpt
@@ -0,0 +1,68 @@
+--TEST--
+string filters (rot13, toupper, tolower) with seek - fully seekable
+--FILE--
+ 'Uryyb Jbeyq! Guvf vf n grfg sbe fgevat svygre frrxvat.',
+    'string.toupper' => 'HELLO WORLD! THIS IS A TEST FOR STRING FILTER SEEKING.',
+    'string.tolower' => 'hello world! this is a test for string filter seeking.'
+];
+
+foreach ($filters as $filter) {
+    echo "Testing filter: $filter\n";
+    
+    file_put_contents($file, $text);
+    
+    $fp = fopen($file, 'r');
+    stream_filter_append($fp, $filter, STREAM_FILTER_READ);
+    
+    $partial = fread($fp, 20);
+    echo "First read (20 bytes): $partial\n";
+    
+    $result = fseek($fp, 0, SEEK_SET);
+    echo "Seek to start: " . ($result === 0 ? "SUCCESS" : "FAILURE") . "\n";
+    
+    $full = fread($fp, strlen($text));
+    echo "Content matches: " . ($full === $expected[$filter] ? "YES" : "NO") . "\n";
+    
+    $result = fseek($fp, 13, SEEK_SET);
+    echo "Seek to middle: " . ($result === 0 ? "SUCCESS" : "FAILURE") . "\n";
+    
+    $from_middle = fread($fp, 10);
+    $expected_middle = substr($expected[$filter], 13, 10);
+    echo "Read from middle matches: " . ($from_middle === $expected_middle ? "YES" : "NO") . "\n";
+    
+    fclose($fp);
+    echo "\n";
+}
+?>
+--CLEAN--
+
+--EXPECT--
+Testing filter: string.rot13
+First read (20 bytes): Uryyb Jbeyq! Guvf vf
+Seek to start: SUCCESS
+Content matches: YES
+Seek to middle: SUCCESS
+Read from middle matches: YES
+
+Testing filter: string.toupper
+First read (20 bytes): HELLO WORLD! THIS IS
+Seek to start: SUCCESS
+Content matches: YES
+Seek to middle: SUCCESS
+Read from middle matches: YES
+
+Testing filter: string.tolower
+First read (20 bytes): hello world! this is
+Seek to start: SUCCESS
+Content matches: YES
+Seek to middle: SUCCESS
+Read from middle matches: YES
diff --git a/ext/standard/tests/filters/user_filter_seek_01.phpt b/ext/standard/tests/filters/user_filter_seek_01.phpt
new file mode 100644
index 0000000000000..cb4e9fe72267f
--- /dev/null
+++ b/ext/standard/tests/filters/user_filter_seek_01.phpt
@@ -0,0 +1,81 @@
+--TEST--
+php_user_filter with seek method - always seekable (stateless filter)
+--FILE--
+data); $i++) {
+                $char = $bucket->data[$i];
+                if (ctype_alpha($char)) {
+                    $base = ctype_upper($char) ? ord('A') : ord('a');
+                    $rotated .= chr($base + (ord($char) - $base + $this->rotation) % 26);
+                } else {
+                    $rotated .= $char;
+                }
+            }
+            $bucket->data = $rotated;
+            $consumed += $bucket->datalen;
+            stream_bucket_append($out, $bucket);
+        }
+        return PSFS_PASS_ON;
+    }
+    
+    public function onCreate(): bool
+    {
+        if (isset($this->params['rotation'])) {
+            $this->rotation = (int)$this->params['rotation'];
+        }
+        return true;
+    }
+    
+    public function onClose(): void {}
+    
+    public function seek(int $offset, int $whence): bool
+    {
+        // Stateless filter - always seekable to any position
+        return true;
+    }
+}
+
+stream_filter_register('test.rotate', 'RotateFilter');
+
+$file = __DIR__ . '/user_filter_seek_001.txt';
+$text = 'Hello World';
+
+file_put_contents($file, $text);
+
+$fp = fopen($file, 'r');
+stream_filter_append($fp, 'test.rotate', STREAM_FILTER_READ, ['rotation' => 13]);
+
+$partial = fread($fp, 5);
+echo "First read: $partial\n";
+
+$result = fseek($fp, 0, SEEK_SET);
+echo "Seek to start: " . ($result === 0 ? "SUCCESS" : "FAILURE") . "\n";
+
+$full = fread($fp, strlen($text));
+echo "Full content: $full\n";
+
+$result = fseek($fp, 6, SEEK_SET);
+echo "Seek to middle: " . ($result === 0 ? "SUCCESS" : "FAILURE") . "\n";
+
+$from_middle = fread($fp, 5);
+echo "Read from middle: $from_middle\n";
+
+fclose($fp);
+unlink($file);
+
+?>
+--EXPECT--
+First read: Uryyb
+Seek to start: SUCCESS
+Full content: Uryyb Jbeyq
+Seek to middle: SUCCESS
+Read from middle: Jbeyq
diff --git a/ext/standard/tests/filters/user_filter_seek_02.phpt b/ext/standard/tests/filters/user_filter_seek_02.phpt
new file mode 100644
index 0000000000000..39f4c3c66243e
--- /dev/null
+++ b/ext/standard/tests/filters/user_filter_seek_02.phpt
@@ -0,0 +1,75 @@
+--TEST--
+php_user_filter with seek method - stateful filter
+--FILE--
+data); $i++) {
+                $modified .= $bucket->data[$i] . $this->count++;
+            }
+            $bucket->data = $modified;
+            $consumed += $bucket->datalen;
+            stream_bucket_append($out, $bucket);
+        }
+        return PSFS_PASS_ON;
+    }
+    
+    public function onCreate(): bool
+    {
+        return true;
+    }
+    
+    public function onClose(): void {}
+    
+    public function seek(int $offset, int $whence): bool
+    {
+        if ($offset === 0 && $whence === SEEK_SET) {
+            $this->count = 0;
+            return true;
+        }
+        return false;
+    }
+}
+
+stream_filter_register('test.counting', 'CountingFilter');
+
+$file = __DIR__ . '/user_filter_seek_002.txt';
+$text = 'ABC';
+
+file_put_contents($file, $text);
+
+$fp = fopen($file, 'r');
+stream_filter_append($fp, 'test.counting', STREAM_FILTER_READ);
+
+$first = fread($fp, 10);
+echo "First read: $first\n";
+
+$result = fseek($fp, 0, SEEK_SET);
+echo "Seek to start: " . ($result === 0 ? "SUCCESS" : "FAILURE") . "\n";
+
+$second = fread($fp, 10);
+echo "Second read after seek: $second\n";
+echo "Counts reset: " . ($first === $second ? "YES" : "NO") . "\n";
+
+$result = fseek($fp, 1, SEEK_SET);
+echo "Seek to middle: " . ($result === 0 ? "SUCCESS" : "FAILURE") . "\n";
+
+fclose($fp);
+unlink($file);
+
+?>
+--EXPECTF--
+First read: A0B1C2
+Seek to start: SUCCESS
+Second read after seek: A0B1C2
+Counts reset: YES
+
+Warning: fseek(): Stream filter seeking for user-filter failed in %s on line %d
+Seek to middle: FAILURE
diff --git a/ext/standard/tests/mail/mail_basic2.phpt b/ext/standard/tests/mail/mail_basic2.phpt
index 44d8d86310b01..96a5a4a10a113 100644
--- a/ext/standard/tests/mail/mail_basic2.phpt
+++ b/ext/standard/tests/mail/mail_basic2.phpt
@@ -20,14 +20,17 @@ $message = 'A Message';
 $additional_headers = 'KHeaders';
 $additional_parameters = "-n";
 $outFile = "/tmp/php_test_mailBasic2.out";
-@unlink($outFile);
 
 echo "-- extra parameters --\n";
 // Calling mail() with all possible arguments
 var_dump( mail($to, $subject, $message, $additional_headers, $additional_parameters) );
 
 echo file_get_contents($outFile);
-unlink($outFile);
+
+?>
+--CLEAN--
+
 --EXPECTF--
 *** Testing mail() : basic functionality ***
diff --git a/ext/standard/user_filters.c b/ext/standard/user_filters.c
index 610784e547e45..ac21e083efbb0 100644
--- a/ext/standard/user_filters.c
+++ b/ext/standard/user_filters.c
@@ -48,6 +48,16 @@ PHP_METHOD(php_user_filter, filter)
 	RETURN_LONG(PSFS_ERR_FATAL);
 }
 
+PHP_METHOD(php_user_filter, seek)
+{
+	zend_long offset, whence;
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll", &offset, &whence) == FAILURE) {
+		RETURN_THROWS();
+	}
+
+	RETURN_TRUE;
+}
+
 PHP_METHOD(php_user_filter, onCreate)
 {
 	ZEND_PARSE_PARAMETERS_NONE();
@@ -123,6 +133,36 @@ static void userfilter_dtor(php_stream_filter *thisfilter)
 	zval_ptr_dtor(obj);
 }
 
+static zend_result userfilter_assign_stream(php_stream *stream, zval *obj,
+		zend_string **stream_name_p, uint32_t orig_no_fclose)
+{
+	/* Give the userfilter class a hook back to the stream */
+	const zend_class_entry *old_scope = EG(fake_scope);
+	EG(fake_scope) = Z_OBJCE_P(obj);
+
+	zend_string *stream_name = ZSTR_INIT_LITERAL("stream", false);
+	bool stream_property_exists = Z_OBJ_HT_P(obj)->has_property(Z_OBJ_P(obj), stream_name, ZEND_PROPERTY_EXISTS, NULL);
+	if (stream_property_exists) {
+		zval stream_zval;
+		php_stream_to_zval(stream, &stream_zval);
+		zend_update_property_ex(Z_OBJCE_P(obj), Z_OBJ_P(obj), stream_name, &stream_zval);
+		/* If property update threw an exception, skip filter execution */
+		if (EG(exception)) {
+			EG(fake_scope) = old_scope;
+			zend_string_release(stream_name);
+			stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
+			stream->flags |= orig_no_fclose;
+			return FAILURE;
+		}
+		*stream_name_p = stream_name;
+	} else {
+		zend_string_release(stream_name);
+	}
+	EG(fake_scope) = old_scope;
+
+	return SUCCESS;
+}
+
 static php_stream_filter_status_t userfilter_filter(
 			php_stream *stream,
 			php_stream_filter *thisfilter,
@@ -148,31 +188,14 @@ static php_stream_filter_status_t userfilter_filter(
 	uint32_t orig_no_fclose = stream->flags & PHP_STREAM_FLAG_NO_FCLOSE;
 	stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
 
-	/* Give the userfilter class a hook back to the stream */
-	const zend_class_entry *old_scope = EG(fake_scope);
-	EG(fake_scope) = Z_OBJCE_P(obj);
-
-	zend_string *stream_name = ZSTR_INIT_LITERAL("stream", 0);
-	bool stream_property_exists = Z_OBJ_HT_P(obj)->has_property(Z_OBJ_P(obj), stream_name, ZEND_PROPERTY_EXISTS, NULL);
-	if (stream_property_exists) {
-		zval stream_zval;
-		php_stream_to_zval(stream, &stream_zval);
-		zend_update_property_ex(Z_OBJCE_P(obj), Z_OBJ_P(obj), stream_name, &stream_zval);
-		/* If property update threw an exception, skip filter execution */
-		if (EG(exception)) {
-			EG(fake_scope) = old_scope;
-			if (buckets_in->head) {
-				php_error_docref(NULL, E_WARNING, "Unprocessed filter buckets remaining on input brigade");
-			}
-			zend_string_release(stream_name);
-			stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
-			stream->flags |= orig_no_fclose;
-			return PSFS_ERR_FATAL;
+	zend_string *stream_name = NULL;
+	if (userfilter_assign_stream(stream, obj, &stream_name, orig_no_fclose) == FAILURE) {
+		if (buckets_in->head) {
+			php_error_docref(NULL, E_WARNING, "Unprocessed filter buckets remaining on input brigade");
 		}
+		return PSFS_ERR_FATAL;
 	}
 
-	EG(fake_scope) = old_scope;
-
 	ZVAL_STRINGL(&func_name, "filter", sizeof("filter")-1);
 
 	/* Setup calling arguments */
@@ -217,12 +240,11 @@ static php_stream_filter_status_t userfilter_filter(
 	 * Since the property accepted a resource assignment above, it must have
 	 * no type hint or be typed as mixed, so we can safely assign null.
 	 */
-	if (stream_property_exists) {
+	if (stream_name != NULL) {
 		zend_update_property_null(Z_OBJCE_P(obj), Z_OBJ_P(obj), ZSTR_VAL(stream_name), ZSTR_LEN(stream_name));
+		zend_string_release(stream_name);
 	}
 
-	zend_string_release(stream_name);
-
 	zval_ptr_dtor(&args[3]);
 	zval_ptr_dtor(&args[2]);
 	zval_ptr_dtor(&args[1]);
@@ -234,14 +256,73 @@ static php_stream_filter_status_t userfilter_filter(
 	return ret;
 }
 
+static zend_result userfilter_seek(
+			php_stream *stream,
+			php_stream_filter *thisfilter,
+			zend_off_t offset,
+			int whence
+			)
+{
+	zval *obj = &thisfilter->abstract;
+	zval retval;
+	zval args[2];
+
+	/* the userfilter object probably doesn't exist anymore */
+	if (CG(unclean_shutdown)) {
+		return FAILURE;
+	}
+
+	/* Check if the seek method exists */
+	zend_function *seek_method = zend_hash_str_find_ptr(&Z_OBJCE_P(obj)->function_table, ZEND_STRL("seek"));
+	if (seek_method == NULL) {
+		/* Method doesn't exist - consider this a successful seek for BC */
+		return SUCCESS;
+	}
+
+	/* Make sure the stream is not closed while the filter callback executes. */
+	uint32_t orig_no_fclose = stream->flags & PHP_STREAM_FLAG_NO_FCLOSE;
+	stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
+
+	zend_string *stream_name = NULL;
+	if (userfilter_assign_stream(stream, obj, &stream_name, orig_no_fclose) == FAILURE) {
+		return FAILURE;
+	}
+
+	/* Setup calling arguments */
+	ZVAL_LONG(&args[0], offset);
+	ZVAL_LONG(&args[1], whence);
+
+	zend_call_known_function(seek_method, Z_OBJ_P(obj), Z_OBJCE_P(obj), &retval, 2, args, NULL);
+
+	zend_result ret = FAILURE;
+	if (Z_TYPE(retval) != IS_UNDEF) {
+		ret = zend_is_true(&retval) ? SUCCESS : FAILURE;
+		zval_ptr_dtor(&retval);
+	}
+
+	/* filter resources are cleaned up by the stream destructor,
+	 * keeping a reference to the stream resource here would prevent it
+	 * from being destroyed properly */
+	if (stream_name != NULL) {
+		zend_update_property_null(Z_OBJCE_P(obj), Z_OBJ_P(obj), ZSTR_VAL(stream_name), ZSTR_LEN(stream_name));
+		zend_string_release(stream_name);
+	}
+
+	stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
+	stream->flags |= orig_no_fclose;
+
+	return ret;
+}
+
 static const php_stream_filter_ops userfilter_ops = {
 	userfilter_filter,
+	userfilter_seek,
 	userfilter_dtor,
 	"user-filter"
 };
 
 static php_stream_filter *user_filter_factory_create(const char *filtername,
-		zval *filterparams, uint8_t persistent)
+		zval *filterparams, bool persistent)
 {
 	struct php_user_filter_data *fdat = NULL;
 	php_stream_filter *filter;
@@ -304,7 +385,7 @@ static php_stream_filter *user_filter_factory_create(const char *filtername,
 		return NULL;
 	}
 
-	filter = php_stream_filter_alloc(&userfilter_ops, NULL, 0);
+	filter = php_stream_filter_alloc(&userfilter_ops, NULL, false, PSFS_SEEKABLE_CHECK);
 
 	/* filtername */
 	add_property_string(&obj, "filtername", filtername);
diff --git a/ext/standard/user_filters.stub.php b/ext/standard/user_filters.stub.php
index acaa42bb33c84..475ec58e79e78 100644
--- a/ext/standard/user_filters.stub.php
+++ b/ext/standard/user_filters.stub.php
@@ -49,6 +49,9 @@ class php_user_filter
      */
     public function filter($in, $out, &$consumed, bool $closing): int {}
 
+    /** @tentative-return-type */
+    public function seek(int $offset, int $whence): bool {}
+
     /** @tentative-return-type */
     public function onCreate(): bool {}
 
diff --git a/ext/standard/user_filters_arginfo.h b/ext/standard/user_filters_arginfo.h
index 1d3c7bb53586b..d530a5c000662 100644
--- a/ext/standard/user_filters_arginfo.h
+++ b/ext/standard/user_filters_arginfo.h
@@ -1,5 +1,5 @@
 /* This is a generated file, edit user_filters.stub.php instead.
- * Stub hash: 33264435fe01a2cc9aa21a4a087dbbf3c4007206 */
+ * Stub hash: 593afbcb51bb35207b93ac7556b92ac845043116 */
 
 ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_php_user_filter_filter, 0, 4, IS_LONG, 0)
 	ZEND_ARG_INFO(0, in)
@@ -8,6 +8,11 @@ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_php_user_filter_
 	ZEND_ARG_TYPE_INFO(0, closing, _IS_BOOL, 0)
 ZEND_END_ARG_INFO()
 
+ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_php_user_filter_seek, 0, 2, _IS_BOOL, 0)
+	ZEND_ARG_TYPE_INFO(0, offset, IS_LONG, 0)
+	ZEND_ARG_TYPE_INFO(0, whence, IS_LONG, 0)
+ZEND_END_ARG_INFO()
+
 ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_php_user_filter_onCreate, 0, 0, _IS_BOOL, 0)
 ZEND_END_ARG_INFO()
 
@@ -15,11 +20,13 @@ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_php_user_filter_
 ZEND_END_ARG_INFO()
 
 ZEND_METHOD(php_user_filter, filter);
+ZEND_METHOD(php_user_filter, seek);
 ZEND_METHOD(php_user_filter, onCreate);
 ZEND_METHOD(php_user_filter, onClose);
 
 static const zend_function_entry class_php_user_filter_methods[] = {
 	ZEND_ME(php_user_filter, filter, arginfo_class_php_user_filter_filter, ZEND_ACC_PUBLIC)
+	ZEND_ME(php_user_filter, seek, arginfo_class_php_user_filter_seek, ZEND_ACC_PUBLIC)
 	ZEND_ME(php_user_filter, onCreate, arginfo_class_php_user_filter_onCreate, ZEND_ACC_PUBLIC)
 	ZEND_ME(php_user_filter, onClose, arginfo_class_php_user_filter_onClose, ZEND_ACC_PUBLIC)
 	ZEND_FE_END
diff --git a/ext/tidy/tidy.c b/ext/tidy/tidy.c
index 9dcfb733a591f..6b5d1061d5124 100644
--- a/ext/tidy/tidy.c
+++ b/ext/tidy/tidy.c
@@ -873,7 +873,7 @@ static PHP_RINIT_FUNCTION(tidy)
 
 static PHP_RSHUTDOWN_FUNCTION(tidy)
 {
-	TG(clean_output) = INI_ORIG_BOOL("tidy.clean_output");
+	TG(clean_output) = zend_ini_parse_bool(zend_ini_str(ZEND_STRL("tidy.clean_output"), /* orig */ true));
 
 	return SUCCESS;
 }
diff --git a/ext/zlib/tests/zlib_filter_seek_deflate.phpt b/ext/zlib/tests/zlib_filter_seek_deflate.phpt
new file mode 100644
index 0000000000000..6acee8e4e8c81
--- /dev/null
+++ b/ext/zlib/tests/zlib_filter_seek_deflate.phpt
@@ -0,0 +1,55 @@
+--TEST--
+zlib.deflate filter with seek to start
+--EXTENSIONS--
+zlib
+--FILE--
+ $size1 ? "YES" : "NO") . "\n";
+
+$result = fseek($fp, 50, SEEK_SET);
+echo "Seek to middle: " . ($result === 0 ? "SUCCESS" : "FAILURE") . "\n";
+
+fclose($fp);
+
+$fp = fopen($file, 'r');
+stream_filter_append($fp, 'zlib.inflate', STREAM_FILTER_READ);
+$content = stream_get_contents($fp);
+fclose($fp);
+
+echo "Decompressed content matches text2: " . ($content === $text2 ? "YES" : "NO") . "\n";
+?>
+--CLEAN--
+
+--EXPECTF--
+Size after first write: %d
+Seek to start: SUCCESS
+Size after second write: %d
+Second write is larger: YES
+
+Warning: fseek(): Stream filter zlib.deflate is seekable only to start position in %s on line %d
+Seek to middle: FAILURE
+Decompressed content matches text2: YES
diff --git a/ext/zlib/tests/zlib_filter_seek_inflate.phpt b/ext/zlib/tests/zlib_filter_seek_inflate.phpt
new file mode 100644
index 0000000000000..2ca7508941ccc
--- /dev/null
+++ b/ext/zlib/tests/zlib_filter_seek_inflate.phpt
@@ -0,0 +1,45 @@
+--TEST--
+zlib.inflate filter with seek to start
+--EXTENSIONS--
+zlib
+--FILE--
+
+--CLEAN--
+
+--EXPECTF--
+First read (20 bytes): I am the very model 
+Seek to start: SUCCESS
+Position after seek: 0
+Content after seek matches: YES
+
+Warning: fseek(): Stream filter zlib.inflate is seekable only to start position in %s on line %d
+Seek to middle: FAILURE
diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c
index ad2c9edb9ddaf..ef792b374b11d 100644
--- a/ext/zlib/zlib.c
+++ b/ext/zlib/zlib.c
@@ -1263,7 +1263,6 @@ ZEND_GET_MODULE(php_zlib)
 static PHP_INI_MH(OnUpdate_zlib_output_compression)
 {
 	int int_value;
-	char *ini_value;
 	if (new_value == NULL) {
 		return FAILURE;
 	}
@@ -1275,9 +1274,9 @@ static PHP_INI_MH(OnUpdate_zlib_output_compression)
 	} else {
 		int_value = (int) zend_ini_parse_quantity_warn(new_value, entry->name);
 	}
-	ini_value = zend_ini_string("output_handler", sizeof("output_handler") - 1, 0);
+	const zend_string *ini_value = zend_ini_str_literal("output_handler");
 
-	if (ini_value && *ini_value && int_value) {
+	if (ini_value && ZSTR_LEN(ini_value) > 0 && int_value) {
 		php_error_docref("ref.outcontrol", E_CORE_ERROR, "Cannot use both zlib.output_compression and output_handler together!!");
 		return FAILURE;
 	}
diff --git a/ext/zlib/zlib_filter.c b/ext/zlib/zlib_filter.c
index e42132fd0008c..69c9a85465e8e 100644
--- a/ext/zlib/zlib_filter.c
+++ b/ext/zlib/zlib_filter.c
@@ -28,6 +28,7 @@ typedef struct _php_zlib_filter_data {
 	size_t outbuf_len;
 	int persistent;
 	bool finished; /* for zlib.deflate: signals that no flush is pending */
+	int windowBits;
 } php_zlib_filter_data;
 
 /* }}} */
@@ -143,6 +144,47 @@ static php_stream_filter_status_t php_zlib_inflate_filter(
 	return exit_status;
 }
 
+static zend_result php_zlib_inflate_seek(
+	php_stream *stream,
+	php_stream_filter *thisfilter,
+	zend_off_t offset,
+	int whence
+	)
+{
+	int status;
+
+	if (!thisfilter || !Z_PTR(thisfilter->abstract)) {
+		return FAILURE;
+	}
+
+	php_zlib_filter_data *data = Z_PTR(thisfilter->abstract);
+
+	if (data->finished) {
+		/* Stream was ended, need to reinitialize */
+		status = inflateInit2(&(data->strm), data->windowBits);
+		if (status != Z_OK) {
+			php_error_docref(NULL, E_WARNING, "zlib.inflate: failed to reinitialize inflation state");
+			return FAILURE;
+		}
+	} else {
+		/* Stream is active, just reset it */
+		status = inflateReset(&(data->strm));
+		if (status != Z_OK) {
+			php_error_docref(NULL, E_WARNING, "zlib.inflate: failed to reset inflation state");
+			return FAILURE;
+		}
+	}
+
+	/* Reset our own state */
+	data->strm.next_in = data->inbuf;
+	data->strm.avail_in = 0;
+	data->strm.next_out = data->outbuf;
+	data->strm.avail_out = data->outbuf_len;
+	data->finished = false;
+
+	return SUCCESS;
+}
+
 static void php_zlib_inflate_dtor(php_stream_filter *thisfilter)
 {
 	if (thisfilter && Z_PTR(thisfilter->abstract)) {
@@ -158,6 +200,7 @@ static void php_zlib_inflate_dtor(php_stream_filter *thisfilter)
 
 static const php_stream_filter_ops php_zlib_inflate_ops = {
 	php_zlib_inflate_filter,
+	php_zlib_inflate_seek,
 	php_zlib_inflate_dtor,
 	"zlib.inflate"
 };
@@ -259,6 +302,38 @@ static php_stream_filter_status_t php_zlib_deflate_filter(
 	return exit_status;
 }
 
+static zend_result php_zlib_deflate_seek(
+	php_stream *stream,
+	php_stream_filter *thisfilter,
+	zend_off_t offset,
+	int whence
+	)
+{
+	int status;
+
+	if (!thisfilter || !Z_PTR(thisfilter->abstract)) {
+		return FAILURE;
+	}
+
+	php_zlib_filter_data *data = Z_PTR(thisfilter->abstract);
+
+	/* Reset zlib deflation state */
+	status = deflateReset(&(data->strm));
+	if (status != Z_OK) {
+		php_error_docref(NULL, E_WARNING, "zlib.deflate: failed to reset deflation state");
+		return FAILURE;
+	}
+
+	/* Reset our own state */
+	data->strm.next_in = data->inbuf;
+	data->strm.avail_in = 0;
+	data->strm.next_out = data->outbuf;
+	data->strm.avail_out = data->outbuf_len;
+	data->finished = true;
+
+	return SUCCESS;
+}
+
 static void php_zlib_deflate_dtor(php_stream_filter *thisfilter)
 {
 	if (thisfilter && Z_PTR(thisfilter->abstract)) {
@@ -272,6 +347,7 @@ static void php_zlib_deflate_dtor(php_stream_filter *thisfilter)
 
 static const php_stream_filter_ops php_zlib_deflate_ops = {
 	php_zlib_deflate_filter,
+	php_zlib_deflate_seek,
 	php_zlib_deflate_dtor,
 	"zlib.deflate"
 };
@@ -280,7 +356,7 @@ static const php_stream_filter_ops php_zlib_deflate_ops = {
 
 /* {{{ zlib.* common factory */
 
-static php_stream_filter *php_zlib_filter_create(const char *filtername, zval *filterparams, uint8_t persistent)
+static php_stream_filter *php_zlib_filter_create(const char *filtername, zval *filterparams, bool persistent)
 {
 	const php_stream_filter_ops *fops = NULL;
 	php_zlib_filter_data *data;
@@ -315,6 +391,7 @@ static php_stream_filter *php_zlib_filter_create(const char *filtername, zval *f
 	}
 
 	data->strm.data_type = Z_ASCII;
+	data->persistent = persistent;
 
 	if (strcasecmp(filtername, "zlib.inflate") == 0) {
 		int windowBits = -MAX_WBITS;
@@ -334,6 +411,9 @@ static php_stream_filter *php_zlib_filter_create(const char *filtername, zval *f
 			}
 		}
 
+		/* Save configuration for reset */
+		data->windowBits = windowBits;
+
 		/* RFC 1951 Inflate */
 		data->finished = false;
 		status = inflateInit2(&(data->strm), windowBits);
@@ -401,6 +481,10 @@ static php_stream_filter *php_zlib_filter_create(const char *filtername, zval *f
 					php_error_docref(NULL, E_WARNING, "Invalid filter parameter, ignored");
 			}
 		}
+
+		/* Save configuration for reset */
+		data->windowBits = windowBits;
+
 		status = deflateInit2(&(data->strm), level, Z_DEFLATED, windowBits, memLevel, 0);
 		data->finished = true;
 		fops = &php_zlib_deflate_ops;
@@ -416,7 +500,7 @@ static php_stream_filter *php_zlib_filter_create(const char *filtername, zval *f
 		return NULL;
 	}
 
-	return php_stream_filter_alloc(fops, data, persistent);
+	return php_stream_filter_alloc(fops, data, persistent, PSFS_SEEKABLE_START);
 }
 
 const php_stream_filter_factory php_zlib_filter_factory = {
diff --git a/main/main.c b/main/main.c
index bbd2d18ba187b..bb5b700fe7ae1 100644
--- a/main/main.c
+++ b/main/main.c
@@ -1471,8 +1471,8 @@ static ZEND_COLD void php_error_cb(int orig_type, zend_string *error_filename, c
 			if (PG(xmlrpc_errors)) {
 				php_printf("faultCode" ZEND_LONG_FMT "faultString%s:%s in %s on line %" PRIu32 "%s%s", PG(xmlrpc_error_number), error_type_str, ZSTR_VAL(message), ZSTR_VAL(error_filename), error_lineno, ZSTR_LEN(backtrace) ? "\nStack trace:\n" : "", ZSTR_VAL(backtrace));
 			} else {
-				char *prepend_string = INI_STR("error_prepend_string");
-				char *append_string = INI_STR("error_append_string");
+				const char *prepend_string = zend_ini_string_literal("error_prepend_string");
+				const char *append_string = zend_ini_string_literal("error_append_string");
 
 				if (PG(html_errors)) {
 					if (type == E_ERROR || type == E_PARSE) {
@@ -2374,7 +2374,7 @@ zend_result php_module_startup(sapi_module_struct *sf, zend_module_entry *additi
 	}
 
 	/* disable certain functions as requested by php.ini */
-	zend_disable_functions(INI_STR("disable_functions"));
+	zend_disable_functions(zend_ini_string_literal("disable_functions"));
 
 	/* make core report what it should */
 	if ((module = zend_hash_str_find_ptr(&module_registry, "core", sizeof("core")-1)) != NULL) {
@@ -2638,7 +2638,7 @@ PHPAPI bool php_execute_script_ex(zend_file_handle *primary_file, zval *retval)
 #ifdef PHP_WIN32
 			zend_unset_timeout();
 #endif
-			zend_set_timeout(INI_INT("max_execution_time"), 0);
+			zend_set_timeout(zend_ini_long_literal("max_execution_time"), false);
 		}
 
 		if (prepend_file_p && result) {
diff --git a/main/php_ini.c b/main/php_ini.c
index e464c05d1fcc1..4bac70e88778f 100644
--- a/main/php_ini.c
+++ b/main/php_ini.c
@@ -334,7 +334,7 @@ static void php_load_zend_extension_cb(void *arg)
 	} else {
 		DL_HANDLE handle;
 		char *libpath;
-		char *extension_dir = INI_STR("extension_dir");
+		const char *extension_dir = zend_ini_string_literal("extension_dir");
 		int slash_suffix = 0;
 		char *err1, *err2;
 
diff --git a/main/rfc1867.c b/main/rfc1867.c
index f6ffb6fabc7f1..161a0e4e487f5 100644
--- a/main/rfc1867.c
+++ b/main/rfc1867.c
@@ -665,8 +665,8 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler)
 	zend_llist header;
 	void *event_extra_data = NULL;
 	unsigned int llen = 0;
-	zend_long upload_cnt = REQUEST_PARSE_BODY_OPTION_GET(max_file_uploads, INI_INT("max_file_uploads"));
-	zend_long body_parts_cnt = REQUEST_PARSE_BODY_OPTION_GET(max_multipart_body_parts, INI_INT("max_multipart_body_parts"));
+	zend_long upload_cnt = REQUEST_PARSE_BODY_OPTION_GET(max_file_uploads, zend_ini_long_literal("max_file_uploads"));
+	zend_long body_parts_cnt = REQUEST_PARSE_BODY_OPTION_GET(max_multipart_body_parts, zend_ini_long_literal("max_multipart_body_parts"));
 	zend_long post_max_size = REQUEST_PARSE_BODY_OPTION_GET(post_max_size, SG(post_max_size));
 	zend_long max_input_vars = REQUEST_PARSE_BODY_OPTION_GET(max_input_vars, PG(max_input_vars));
 	zend_long upload_max_filesize = REQUEST_PARSE_BODY_OPTION_GET(upload_max_filesize, PG(upload_max_filesize));
diff --git a/main/streams/filter.c b/main/streams/filter.c
index d0c1fdc8e788c..2260bae82335e 100644
--- a/main/streams/filter.c
+++ b/main/streams/filter.c
@@ -216,7 +216,7 @@ PHPAPI void php_stream_bucket_unlink(php_stream_bucket *bucket)
  * match. If that fails, we try "convert.charset.*", then "convert.*"
  * This means that we don't need to clog up the hashtable with a zillion
  * charsets (for example) but still be able to provide them all as filters */
-PHPAPI php_stream_filter *php_stream_filter_create(const char *filtername, zval *filterparams, uint8_t persistent)
+PHPAPI php_stream_filter *php_stream_filter_create(const char *filtername, zval *filterparams, bool persistent)
 {
 	HashTable *filter_hash = (FG(stream_filters) ? FG(stream_filters) : &stream_filters_hash);
 	const php_stream_filter_factory *factory = NULL;
@@ -260,7 +260,8 @@ PHPAPI php_stream_filter *php_stream_filter_create(const char *filtername, zval
 	return filter;
 }
 
-PHPAPI php_stream_filter *_php_stream_filter_alloc(const php_stream_filter_ops *fops, void *abstract, uint8_t persistent STREAMS_DC)
+PHPAPI php_stream_filter *_php_stream_filter_alloc(const php_stream_filter_ops *fops,
+		void *abstract, bool persistent, php_stream_filter_seekable_t seekable STREAMS_DC)
 {
 	php_stream_filter *filter;
 
@@ -268,6 +269,7 @@ PHPAPI php_stream_filter *_php_stream_filter_alloc(const php_stream_filter_ops *
 	memset(filter, 0, sizeof(php_stream_filter));
 
 	filter->fops = fops;
+	filter->seekable = seekable;
 	Z_PTR(filter->abstract) = abstract;
 	filter->is_persistent = persistent;
 
diff --git a/main/streams/php_stream_filter_api.h b/main/streams/php_stream_filter_api.h
index e224b85b2d9a2..cb4fdb5fd7f79 100644
--- a/main/streams/php_stream_filter_api.h
+++ b/main/streams/php_stream_filter_api.h
@@ -61,6 +61,13 @@ typedef enum {
 	PSFS_PASS_ON	/* filter generated output buckets; pass them on to next in chain */
 } php_stream_filter_status_t;
 
+typedef enum {
+	PSFS_SEEKABLE_NEVER, /* seeking filter never possible */
+	PSFS_SEEKABLE_START, /* seeking possible only to start (position 0) */
+	PSFS_SEEKABLE_CHECK, /* seeking possible but it is always checked if callback function set */
+	PSFS_SEEKABLE_ALWAYS, /* seeking is always possible */
+} php_stream_filter_seekable_t;
+
 /* Buckets API. */
 BEGIN_EXTERN_C()
 PHPAPI php_stream_bucket *php_stream_bucket_new(const php_stream *stream, char *buf, size_t buflen, uint8_t own_buf, uint8_t buf_persistent);
@@ -88,6 +95,13 @@ typedef struct _php_stream_filter_ops {
 			int flags
 			);
 
+	zend_result (*seek)(
+			php_stream *stream,
+			php_stream_filter *thisfilter,
+			zend_off_t offset,
+			int whence
+	);
+
 	void (*dtor)(php_stream_filter *thisfilter);
 
 	const char *label;
@@ -106,7 +120,8 @@ struct _php_stream_filter {
 	zval abstract; /* for use by filter implementation */
 	php_stream_filter *next;
 	php_stream_filter *prev;
-	int is_persistent;
+	php_stream_filter_seekable_t seekable;
+	bool is_persistent;
 
 	/* link into stream and chain */
 	php_stream_filter_chain *chain;
@@ -127,10 +142,14 @@ PHPAPI zend_result php_stream_filter_append_ex(php_stream_filter_chain *chain, p
 PHPAPI zend_result _php_stream_filter_flush(php_stream_filter *filter, bool finish);
 PHPAPI php_stream_filter *php_stream_filter_remove(php_stream_filter *filter, bool call_dtor);
 PHPAPI void php_stream_filter_free(php_stream_filter *filter);
-PHPAPI php_stream_filter *_php_stream_filter_alloc(const php_stream_filter_ops *fops, void *abstract, uint8_t persistent STREAMS_DC);
+PHPAPI php_stream_filter *_php_stream_filter_alloc(const php_stream_filter_ops *fops,
+		void *abstract, bool persistent, php_stream_filter_seekable_t seekable STREAMS_DC);
+
 END_EXTERN_C()
-#define php_stream_filter_alloc(fops, thisptr, persistent) _php_stream_filter_alloc((fops), (thisptr), (persistent) STREAMS_CC)
-#define php_stream_filter_alloc_rel(fops, thisptr, persistent) _php_stream_filter_alloc((fops), (thisptr), (persistent) STREAMS_REL_CC)
+#define php_stream_filter_alloc(fops, thisptr, persistent, seekable) \
+		_php_stream_filter_alloc((fops), (thisptr), (persistent), (seekable) STREAMS_CC)
+#define php_stream_filter_alloc_rel(fops, thisptr, persistent, seekable) \
+	_php_stream_filter_alloc((fops), (thisptr), (persistent), (seekable) STREAMS_REL_CC)
 #define php_stream_filter_prepend(chain, filter) _php_stream_filter_prepend((chain), (filter))
 #define php_stream_filter_append(chain, filter) _php_stream_filter_append((chain), (filter))
 #define php_stream_filter_flush(filter, finish) _php_stream_filter_flush((filter), (finish))
@@ -138,12 +157,12 @@ END_EXTERN_C()
 #define php_stream_is_filtered(stream)	((stream)->readfilters.head || (stream)->writefilters.head)
 
 typedef struct _php_stream_filter_factory {
-	php_stream_filter *(*create_filter)(const char *filtername, zval *filterparams, uint8_t persistent);
+	php_stream_filter *(*create_filter)(const char *filtername, zval *filterparams, bool persistent);
 } php_stream_filter_factory;
 
 BEGIN_EXTERN_C()
 PHPAPI zend_result php_stream_filter_register_factory(const char *filterpattern, const php_stream_filter_factory *factory);
 PHPAPI zend_result php_stream_filter_unregister_factory(const char *filterpattern);
 PHPAPI zend_result php_stream_filter_register_factory_volatile(zend_string *filterpattern, const php_stream_filter_factory *factory);
-PHPAPI php_stream_filter *php_stream_filter_create(const char *filtername, zval *filterparams, uint8_t persistent);
+PHPAPI php_stream_filter *php_stream_filter_create(const char *filtername, zval *filterparams, bool persistent);
 END_EXTERN_C()
diff --git a/main/streams/streams.c b/main/streams/streams.c
index 32c7ba99f58c2..26f147632cef7 100644
--- a/main/streams/streams.c
+++ b/main/streams/streams.c
@@ -1362,6 +1362,52 @@ PHPAPI zend_off_t _php_stream_tell(const php_stream *stream)
 	return stream->position;
 }
 
+static bool php_stream_are_filters_seekable(php_stream_filter *filter, bool is_start_seeking)
+{
+	while (filter) {
+		if (filter->seekable == PSFS_SEEKABLE_NEVER) {
+			php_error_docref(NULL, E_WARNING, "Stream filter %s is never seekable", filter->fops->label);
+			return false;
+		}
+		if (!is_start_seeking && filter->seekable == PSFS_SEEKABLE_START) {
+			php_error_docref(NULL, E_WARNING, "Stream filter %s is seekable only to start position", filter->fops->label);
+			return false;
+		}
+		filter = filter->next;
+	}
+	return true;
+}
+
+static zend_result php_stream_filters_seek(php_stream *stream, php_stream_filter *filter,
+		bool is_start_seeking, zend_off_t offset, int whence)
+{
+	while (filter) {
+		if (((filter->seekable == PSFS_SEEKABLE_START && is_start_seeking) ||
+				filter->seekable == PSFS_SEEKABLE_CHECK) &&
+				filter->fops->seek(stream, filter, offset, whence) == FAILURE) {
+			php_error_docref(NULL, E_WARNING, "Stream filter seeking for %s failed", filter->fops->label);
+			return FAILURE;
+		}
+		filter = filter->next;
+	}
+	return SUCCESS;
+}
+
+static zend_result php_stream_filters_seek_all(php_stream *stream, bool is_start_seeking,
+		zend_off_t offset, int whence)
+{
+	if (php_stream_filters_seek(stream, stream->writefilters.head, is_start_seeking, offset, whence) == FAILURE) {
+		return FAILURE;
+	}
+	if (php_stream_filters_seek(stream, stream->readfilters.head, is_start_seeking, offset, whence) == FAILURE) {
+		return FAILURE;
+	}
+
+	return SUCCESS;
+}
+
+
+
 PHPAPI int _php_stream_seek(php_stream *stream, zend_off_t offset, int whence)
 {
 	if (stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FOPENCOOKIE) {
@@ -1374,6 +1420,18 @@ PHPAPI int _php_stream_seek(php_stream *stream, zend_off_t offset, int whence)
 		}
 	}
 
+	bool is_start_seeking = whence == SEEK_SET && offset == 0;
+
+	if (stream->writefilters.head) {
+		_php_stream_flush(stream, 0);
+		if (!php_stream_are_filters_seekable(stream->writefilters.head, is_start_seeking)) {
+			return -1;
+		}
+	}
+	if (stream->readfilters.head && !php_stream_are_filters_seekable(stream->readfilters.head, is_start_seeking)) {
+		return -1;
+	}
+
 	/* handle the case where we are in the buffer */
 	if ((stream->flags & PHP_STREAM_FLAG_NO_BUFFER) == 0) {
 		switch(whence) {
@@ -1383,7 +1441,7 @@ PHPAPI int _php_stream_seek(php_stream *stream, zend_off_t offset, int whence)
 					stream->position += offset;
 					stream->eof = 0;
 					stream->fatal_error = 0;
-					return 0;
+					return php_stream_filters_seek_all(stream, is_start_seeking, offset, whence) == SUCCESS ? 0 : -1;
 				}
 				break;
 			case SEEK_SET:
@@ -1393,7 +1451,7 @@ PHPAPI int _php_stream_seek(php_stream *stream, zend_off_t offset, int whence)
 					stream->position = offset;
 					stream->eof = 0;
 					stream->fatal_error = 0;
-					return 0;
+					return php_stream_filters_seek_all(stream, is_start_seeking, offset, whence) == SUCCESS ? 0 : -1;
 				}
 				break;
 		}
@@ -1402,11 +1460,6 @@ PHPAPI int _php_stream_seek(php_stream *stream, zend_off_t offset, int whence)
 
 	if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) {
 		int ret;
-
-		if (stream->writefilters.head) {
-			_php_stream_flush(stream, 0);
-		}
-
 		switch(whence) {
 			case SEEK_CUR:
 				ZEND_ASSERT(stream->position >= 0);
@@ -1433,7 +1486,7 @@ PHPAPI int _php_stream_seek(php_stream *stream, zend_off_t offset, int whence)
 			/* invalidate the buffer contents */
 			stream->readpos = stream->writepos = 0;
 
-			return ret;
+			return php_stream_filters_seek_all(stream, is_start_seeking, offset, whence) == SUCCESS ? ret : -1;
 		}
 		/* else the stream has decided that it can't support seeking after all;
 		 * fall through to attempt emulation */
diff --git a/sapi/phpdbg/phpdbg_prompt.c b/sapi/phpdbg/phpdbg_prompt.c
index 9566c1abd4eec..f3b4ff6b9ddbe 100644
--- a/sapi/phpdbg/phpdbg_prompt.c
+++ b/sapi/phpdbg/phpdbg_prompt.c
@@ -1190,19 +1190,20 @@ static void add_zendext_info(zend_extension *ext) /* {{{ */ {
 #ifdef HAVE_LIBDL
 PHPDBG_API const char *phpdbg_load_module_or_extension(char **path, const char **name) /* {{{ */ {
 	DL_HANDLE handle;
-	char *extension_dir;
-
-	extension_dir = INI_STR("extension_dir");
+	zend_string *extension_dir = zend_ini_str_literal("extension_dir");
 
+	if (UNEXPECTED(zend_str_has_nul_byte(extension_dir))) {
+		phpdbg_error("extension_dir ini setting contains a nul byte");
+		return NULL;
+	}
 	if (strchr(*path, '/') != NULL || strchr(*path, DEFAULT_SLASH) != NULL) {
 		/* path is fine */
-	} else if (extension_dir && extension_dir[0]) {
+	} else if (extension_dir && ZSTR_LEN(extension_dir) > 0) {
 		char *libpath;
-		int extension_dir_len = strlen(extension_dir);
-		if (IS_SLASH(extension_dir[extension_dir_len-1])) {
-			spprintf(&libpath, 0, "%s%s", extension_dir, *path); /* SAFE */
+		if (IS_SLASH(ZSTR_VAL(extension_dir)[ZSTR_LEN(extension_dir)-1])) {
+			spprintf(&libpath, 0, "%s%s", ZSTR_VAL(extension_dir), *path); /* SAFE */
 		} else {
-			spprintf(&libpath, 0, "%s%c%s", extension_dir, DEFAULT_SLASH, *path); /* SAFE */
+			spprintf(&libpath, 0, "%s%c%s", ZSTR_VAL(extension_dir), DEFAULT_SLASH, *path); /* SAFE */
 		}
 		efree(*path);
 		*path = libpath;
diff --git a/win32/sendmail.c b/win32/sendmail.c
index f267d1fae85ab..939106f31b00e 100644
--- a/win32/sendmail.c
+++ b/win32/sendmail.c
@@ -216,8 +216,8 @@ PHPAPI int TSendMail(const char *host, int *error, char **error_message,
 	}
 
 	/* Fall back to sendmail_from php.ini setting */
-	if (INI_STR("sendmail_from")) {
-		RPath = estrdup(INI_STR("sendmail_from"));
+	if (zend_ini_string_literal("sendmail_from")) {
+		RPath = estrdup(zend_ini_string_literal("sendmail_from"));
 	} else if (headers_lc) {
 		int found = 0;
 		const char *lookup = ZSTR_VAL(headers_lc);
@@ -276,7 +276,7 @@ PHPAPI int TSendMail(const char *host, int *error, char **error_message,
 		snprintf(*error_message, HOST_NAME_LEN + 128,
 			"Failed to connect to mailserver at \"%s\" port " ZEND_ULONG_FMT ", verify your \"SMTP\" "
 			"and \"smtp_port\" setting in php.ini or use ini_set()",
-			PW32G(mail_host), !INI_INT("smtp_port") ? 25 : INI_INT("smtp_port"));
+			PW32G(mail_host), !zend_ini_long_literal("smtp_port") ? 25 : zend_ini_long_literal("smtp_port"));
 		return FAILURE;
 	} else {
 		ret = SendText(RPath, Subject, mailTo, data, headers_trim, headers_lc, error_message);
@@ -789,7 +789,7 @@ return 0;
 	}
 	*/
 
-	portnum = (short) INI_INT("smtp_port");
+	portnum = (short) zend_ini_long_literal("smtp_port");
 	if (!portnum) {
 		portnum = 25;
 	}