diff --git a/NEWS b/NEWS index ccc3291c018a..497293d89e33 100644 --- a/NEWS +++ b/NEWS @@ -75,6 +75,10 @@ PHP NEWS - Readline: . Fixed class constant completion in the interactive shell. (Weilin Du) +- Zip: + . Fixed bug GH-23276 (ZipArchive subclass storing its own stream cannot be + garbage collected). (Weilin Du, ndossche) + - SAPI: . Fixed fuzzer targets failing to build in isolation. (Mrmaxmeier) . Fixed returns uninitialized value on LiteSpeed lsapi SAPI (Go Kudo) @@ -110,6 +114,8 @@ PHP NEWS - Standard: . Fixed incorrect parameter name in convert_uudecode() warning. (lacatoire) + . Added support for the "<" and ">" endianness modifiers in pack() and + unpack() format codes. (alexandre-daubois) - Zip: . Fixed bug GH-17787 (ZipArchive stream stops reading early when the archive diff --git a/UPGRADING b/UPGRADING index 14cea21664c0..ad76c2c2e294 100644 --- a/UPGRADING +++ b/UPGRADING @@ -430,6 +430,14 @@ PHP 8.6 UPGRADE NOTES SNMP::setStringOutputFormat() methods. (eskyuu) RFC: https://wiki.php.net/rfc/snmp_improvements_2026#implement_more_mib_parsing_and_value_output_controls +- Standard: + . pack() and unpack() now accept the "<" and ">" endianness modifiers on + the signed and unsigned integer format codes. + RFC: https://wiki.php.net/rfc/pack-unpack-endianness-signed-integers-support + . pack() and unpack() now accept the "<" and ">" endianness modifiers on + the float and double format codes. + RFC: https://wiki.php.net/rfc/pack-unpack-float-endianness-modifier + - Streams: . Added new stream errors API including new classes, enums, functions and internal API. It is controlled using error_mode, error_store and diff --git a/Zend/tests/partial_application/default_arg_scope.phpt b/Zend/tests/partial_application/default_arg_scope.phpt new file mode 100644 index 000000000000..5cab50c5af26 --- /dev/null +++ b/Zend/tests/partial_application/default_arg_scope.phpt @@ -0,0 +1,47 @@ +--TEST-- +PFA default argument value scope +--ENV-- +A=1 +--FILE-- + +--EXPECT-- +string(1) "C" +string(1) "C" +string(1) "C" +string(1) "C" +string(1) "C" +string(1) "C" diff --git a/Zend/tests/partial_application/magic_001.phpt b/Zend/tests/partial_application/magic_001.phpt index bdcc10675785..a3a7a9673b8f 100644 --- a/Zend/tests/partial_application/magic_001.phpt +++ b/Zend/tests/partial_application/magic_001.phpt @@ -46,7 +46,7 @@ Closure [ public method {closure:%s:%d} ] { Parameter #0 [ mixed $arguments0 ] } } -ArgumentCountError: Too few arguments to function Foo::{closure:%s:%d}(), 0 passed in %s on line %d and exactly 1 expected +ArgumentCountError: Too few arguments to function Closure::{closure:%s:%d}(), 0 passed in %s on line %d and exactly 1 expected Foo::method int(1) Foo::method diff --git a/Zend/tests/partial_application/magic_002.phpt b/Zend/tests/partial_application/magic_002.phpt index 1d5efaea7c63..2771c823e4d4 100644 --- a/Zend/tests/partial_application/magic_002.phpt +++ b/Zend/tests/partial_application/magic_002.phpt @@ -31,7 +31,7 @@ echo (string) new ReflectionFunction($bar); $bar(100); ?> --EXPECTF-- -Closure [ static public method {closure:%s:%d} ] { +Closure [ static function {closure:%s:%d} ] { @@ %s 10 - 10 - Parameters [1] { @@ -42,7 +42,7 @@ Foo::method int(1) Foo::method int(1) -Closure [ static public method {closure:%s:%d} ] { +Closure [ static function {closure:%s:%d} ] { @@ %s 17 - 17 - Parameters [2] { @@ -55,7 +55,7 @@ int(10) Foo::method int(10) int(20) -Closure [ static public method {closure:%s:%d} ] { +Closure [ static function {closure:%s:%d} ] { @@ %s 24 - 24 - Bound Variables [1] { diff --git a/Zend/tests/partial_application/magic_scope.phpt b/Zend/tests/partial_application/magic_scope.phpt new file mode 100644 index 000000000000..82a69f955f96 --- /dev/null +++ b/Zend/tests/partial_application/magic_scope.phpt @@ -0,0 +1,49 @@ +--TEST-- +Magic method scope +--CREDITS-- +Ryan @ Calif.io +--FILE-- +secret('direct'); +StaticTarget::secret('direct'); + +$instancePartial = $instance->secret(?); +$staticPartial = StaticTarget::secret(?); +$instancePartial('controlled'); +$staticPartial('controlled'); + +?> +--EXPECT-- +MAGIC-INSTANCE:secret:direct +MAGIC-STATIC:secret:direct +MAGIC-INSTANCE:secret:controlled +MAGIC-STATIC:secret:controlled diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 4b070d9d5d58..6a71fc5aeca3 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1349,7 +1349,7 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner( if (uses_variadic_placeholder) { flags |= ZEND_PARTIAL_USES_VARIADIC_PLACEHOLDER; } - zend_partial_create(result, &frame->This, fptr, + zend_partial_create(result, scope, &frame->This, fptr, ZEND_CALL_NUM_ARGS(frame), ZEND_CALL_ARG(frame, 1), extra_named_params, named_positions, fcc_ast->filename, &ast->lineno, diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index 643cc634e7e8..de243bd7e65f 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -1126,7 +1126,7 @@ static void zp_bind(zval *result, zend_function *function, uint32_t argc, zval * } } -void zend_partial_create(zval *result, zval *this_ptr, zend_function *function, +void zend_partial_create(zval *result, zend_class_entry *scope, zval *this_ptr, zend_function *function, uint32_t argc, zval *argv, zend_array *extra_named_params, const zend_array *named_positions, zend_string *declaring_filename, @@ -1162,8 +1162,16 @@ void zend_partial_create(zval *result, zval *this_ptr, zend_function *function, object = NULL; } + + /* We conveniently use the function's scope for the scope of the generated closure as this allows const exprs + * referencing self:: or parent:: to behave normally without rewriting them. + * This affects method resolution for magic methods, so use the actual scope for them. */ + if (!(function->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { + scope = function->common.scope; + } + zend_create_partial_closure(result, (zend_function*)op_array, - function->common.scope, called_scope, object, + scope, called_scope, object, (function->common.fn_flags & ZEND_ACC_CLOSURE) != 0); zp_bind(result, function, argc, argv, extra_named_params, const_args); diff --git a/Zend/zend_partial.h b/Zend/zend_partial.h index d3fcdae6afc8..285db1161e78 100644 --- a/Zend/zend_partial.h +++ b/Zend/zend_partial.h @@ -31,7 +31,7 @@ BEGIN_EXTERN_C() * 'declaring_lineno_ptr' should be a pointer the zend_op.lineno or * zend_ast.lineno that declares the PFA. The address is used to build a cache * key. */ -void zend_partial_create(zval *result, zval *this_ptr, zend_function *function, +void zend_partial_create(zval *result, zend_class_entry *scope, zval *this_ptr, zend_function *function, uint32_t argc, zval *argv, zend_array *extra_named_params, const zend_array *named_positions, zend_string *declaring_filename, diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 01131b5d3ae0..0e35b5bb95fa 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -9897,7 +9897,7 @@ ZEND_VM_HANDLER(212, ZEND_CALLABLE_CONVERT_PARTIAL, CONST, CONST|UNUSED, NUM) } zend_partial_create(EX_VAR(opline->result.var), - &call->This, call->func, + EX(func)->common.scope, &call->This, call->func, ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? call->extra_named_params : NULL, diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 5061d772ee82..c6158bd507d9 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -8654,7 +8654,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALLABLE_CONV } zend_partial_create(EX_VAR(opline->result.var), - &call->This, call->func, + EX(func)->common.scope, &call->This, call->func, ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? call->extra_named_params : NULL, @@ -12027,7 +12027,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALLABLE_CONV } zend_partial_create(EX_VAR(opline->result.var), - &call->This, call->func, + EX(func)->common.scope, &call->This, call->func, ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? call->extra_named_params : NULL, @@ -61499,7 +61499,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALLABLE_CONVERT_P } zend_partial_create(EX_VAR(opline->result.var), - &call->This, call->func, + EX(func)->common.scope, &call->This, call->func, ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? call->extra_named_params : NULL, @@ -64770,7 +64770,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALLABLE_CONVERT_P } zend_partial_create(EX_VAR(opline->result.var), - &call->This, call->func, + EX(func)->common.scope, &call->This, call->func, ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? call->extra_named_params : NULL, diff --git a/ext/standard/pack.c b/ext/standard/pack.c index 1ab5d4858cbe..1da93228f71f 100644 --- a/ext/standard/pack.c +++ b/ext/standard/pack.c @@ -22,6 +22,7 @@ if ((a) < 0 || ((INT_MAX - outputpos)/((int)b)) < (a)) { \ efree(formatcodes); \ efree(formatargs); \ + efree(formatendian); \ zend_value_error("Type %c: integer overflow in format string", code); \ RETURN_THROWS(); \ } \ @@ -30,6 +31,7 @@ typedef enum { PHP_LITTLE_ENDIAN, PHP_BIG_ENDIAN, + PHP_NO_ENDIAN_MODIFIER, } php_pack_endianness; #ifdef WORDS_BIGENDIAN @@ -218,12 +220,27 @@ PHP_FUNCTION(pack) /* We have a maximum of format codes to deal with */ formatcodes = safe_emalloc(formatlen, sizeof(*formatcodes), 0); formatargs = safe_emalloc(formatlen, sizeof(*formatargs), 0); + php_pack_endianness *formatendian = safe_emalloc(formatlen, sizeof(*formatendian), 0); currentarg = 0; /* Preprocess format into formatcodes and formatargs */ for (i = 0; i < formatlen; formatcount++) { char code = format[i++]; int arg = 1; + php_pack_endianness endian = PHP_NO_ENDIAN_MODIFIER; + + /* Handle endianness modifier if any */ + if (i < formatlen) { + char c = format[i]; + + if (c == '<') { + endian = PHP_LITTLE_ENDIAN; + i++; + } else if (c == '>') { + endian = PHP_BIG_ENDIAN; + i++; + } + } /* Handle format arguments if any */ if (i < formatlen) { @@ -248,6 +265,13 @@ PHP_FUNCTION(pack) case 'x': case 'X': case '@': + if (endian != PHP_NO_ENDIAN_MODIFIER) { + efree(formatcodes); + efree(formatargs); + efree(formatendian); + zend_value_error("Endianness modifier is not supported for format code '%c'", code); + RETURN_THROWS(); + } if (arg < 0) { php_error_docref(NULL, E_WARNING, "Type %c: '*' ignored", code); arg = 1; @@ -260,9 +284,17 @@ PHP_FUNCTION(pack) case 'Z': case 'h': case 'H': + if (endian != PHP_NO_ENDIAN_MODIFIER) { + efree(formatcodes); + efree(formatargs); + efree(formatendian); + zend_value_error("Endianness modifier is not supported for format code '%c'", code); + RETURN_THROWS(); + } if (currentarg >= num_args) { efree(formatcodes); efree(formatargs); + efree(formatendian); zend_value_error("Type %c: not enough arguments", code); RETURN_THROWS(); } @@ -271,6 +303,7 @@ PHP_FUNCTION(pack) if (!try_convert_to_string(&argv[currentarg])) { efree(formatcodes); efree(formatargs); + efree(formatendian); RETURN_THROWS(); } @@ -286,35 +319,115 @@ PHP_FUNCTION(pack) currentarg++; break; - /* Use as many args as specified */ - case 'q': - case 'Q': + /* 64-bit codes with explicit endianness, endianness modifiers not allowed */ case 'J': case 'P': -#if SIZEOF_ZEND_LONG < 8 + if (endian != PHP_NO_ENDIAN_MODIFIER) { efree(formatcodes); efree(formatargs); - zend_value_error("64-bit format codes are not available for 32-bit versions of PHP"); + efree(formatendian); + zend_value_error("Endianness modifier '%c' cannot be applied to format code '%c' which already has inherent endianness", (endian == PHP_LITTLE_ENDIAN) ? '<' : '>', code); RETURN_THROWS(); + } + ZEND_FALLTHROUGH; + + /* 64-bit codes that support endianness modifiers */ + case 'q': + case 'Q': +#if SIZEOF_ZEND_LONG < 8 + efree(formatcodes); + efree(formatargs); + efree(formatendian); + zend_value_error("64-bit format codes are not available for 32-bit versions of PHP"); + RETURN_THROWS(); +#else + if (arg < 0) { + arg = num_args - currentarg; + } + if (currentarg > INT_MAX - arg) { + goto too_few_args; + } + currentarg += arg; + + if (currentarg > num_args) { + goto too_few_args; + } + break; #endif - case 'c': - case 'C': - case 's': - case 'S': - case 'i': - case 'I': - case 'l': - case 'L': + + /* Codes with explicit endianness, endianness modifiers not allowed */ case 'n': case 'N': case 'v': case 'V': - case 'f': /* float */ + if (endian != PHP_NO_ENDIAN_MODIFIER) { + efree(formatcodes); + efree(formatargs); + efree(formatendian); + zend_value_error("Endianness modifier '%c' cannot be applied to format code '%c' which already has inherent endianness", (endian == PHP_LITTLE_ENDIAN) ? '<' : '>', code); + RETURN_THROWS(); + } + ZEND_FALLTHROUGH; + + /* Codes that support endianness modifiers */ + case 's': + case 'S': + case 'l': + case 'L': + if (arg < 0) { + arg = num_args - currentarg; + } + if (currentarg > INT_MAX - arg) { + goto too_few_args; + } + currentarg += arg; + + if (currentarg > num_args) { + goto too_few_args; + } + break; + + case 'c': + case 'C': + case 'i': + case 'I': + if (endian != PHP_NO_ENDIAN_MODIFIER) { + efree(formatcodes); + efree(formatargs); + efree(formatendian); + zend_value_error("Endianness modifier is not supported for format code '%c'", code); + RETURN_THROWS(); + } + if (arg < 0) { + arg = num_args - currentarg; + } + if (currentarg > INT_MAX - arg) { + goto too_few_args; + } + currentarg += arg; + + if (currentarg > num_args) { + goto too_few_args; + } + break; + + /* Codes with explicit endianness, endianness modifiers not allowed */ case 'g': /* little endian float */ case 'G': /* big endian float */ - case 'd': /* double */ case 'e': /* little endian double */ case 'E': /* big endian double */ + if (endian != PHP_NO_ENDIAN_MODIFIER) { + efree(formatcodes); + efree(formatargs); + efree(formatendian); + zend_value_error("Endianness modifier '%c' cannot be applied to format code '%c' which already has inherent endianness", (endian == PHP_LITTLE_ENDIAN) ? '<' : '>', code); + RETURN_THROWS(); + } + ZEND_FALLTHROUGH; + + /* Codes that support endianness modifiers */ + case 'f': /* float */ + case 'd': /* double */ if (arg < 0) { arg = num_args - currentarg; } @@ -327,6 +440,7 @@ PHP_FUNCTION(pack) too_few_args: efree(formatcodes); efree(formatargs); + efree(formatendian); zend_value_error("Type %c: too few arguments", code); RETURN_THROWS(); } @@ -335,12 +449,14 @@ PHP_FUNCTION(pack) default: efree(formatcodes); efree(formatargs); + efree(formatendian); zend_value_error("Type %c: unknown format code", code); RETURN_THROWS(); } formatcodes[formatcount] = code; formatargs[formatcount] = arg; + formatendian[formatcount] = endian; } if (currentarg < num_args) { @@ -507,12 +623,16 @@ PHP_FUNCTION(pack) case 'S': case 'n': case 'v': { - php_pack_endianness endianness = PHP_MACHINE_ENDIAN; + php_pack_endianness endianness; if (code == 'n') { endianness = PHP_BIG_ENDIAN; } else if (code == 'v') { endianness = PHP_LITTLE_ENDIAN; + } else if (formatendian[i] != PHP_NO_ENDIAN_MODIFIER) { + endianness = formatendian[i]; + } else { + endianness = PHP_MACHINE_ENDIAN; } while (arg-- > 0) { @@ -534,12 +654,16 @@ PHP_FUNCTION(pack) case 'L': case 'N': case 'V': { - php_pack_endianness endianness = PHP_MACHINE_ENDIAN; + php_pack_endianness endianness; if (code == 'N') { endianness = PHP_BIG_ENDIAN; } else if (code == 'V') { endianness = PHP_LITTLE_ENDIAN; + } else if (formatendian[i] != PHP_NO_ENDIAN_MODIFIER) { + endianness = formatendian[i]; + } else { + endianness = PHP_MACHINE_ENDIAN; } while (arg-- > 0) { @@ -554,12 +678,16 @@ PHP_FUNCTION(pack) case 'Q': case 'J': case 'P': { - php_pack_endianness endianness = PHP_MACHINE_ENDIAN; + php_pack_endianness endianness; if (code == 'J') { endianness = PHP_BIG_ENDIAN; } else if (code == 'P') { endianness = PHP_LITTLE_ENDIAN; + } else if (formatendian[i] != PHP_NO_ENDIAN_MODIFIER) { + endianness = formatendian[i]; + } else { + endianness = PHP_MACHINE_ENDIAN; } while (arg-- > 0) { @@ -570,59 +698,35 @@ PHP_FUNCTION(pack) } #endif - case 'f': { - while (arg-- > 0) { - float v = (float) zval_get_double(&argv[currentarg++]); - memcpy(&ZSTR_VAL(output)[outputpos], &v, sizeof(v)); - outputpos += sizeof(v); - } - break; - } - - case 'g': { - /* pack little endian float */ - while (arg-- > 0) { - float v = (float) zval_get_double(&argv[currentarg++]); - php_pack_copy_float(1, &ZSTR_VAL(output)[outputpos], v); - outputpos += sizeof(v); - } - - break; - } + case 'f': + case 'g': case 'G': { - /* pack big endian float */ while (arg-- > 0) { float v = (float) zval_get_double(&argv[currentarg++]); - php_pack_copy_float(0, &ZSTR_VAL(output)[outputpos], v); - outputpos += sizeof(v); - } - break; - } - - case 'd': { - while (arg-- > 0) { - double v = zval_get_double(&argv[currentarg++]); - memcpy(&ZSTR_VAL(output)[outputpos], &v, sizeof(v)); - outputpos += sizeof(v); - } - break; - } - - case 'e': { - /* pack little endian double */ - while (arg-- > 0) { - double v = zval_get_double(&argv[currentarg++]); - php_pack_copy_double(1, &ZSTR_VAL(output)[outputpos], v); + if (code == 'g' || formatendian[i] == PHP_LITTLE_ENDIAN) { + php_pack_copy_float(1, &ZSTR_VAL(output)[outputpos], v); + } else if (code == 'G' || formatendian[i] == PHP_BIG_ENDIAN) { + php_pack_copy_float(0, &ZSTR_VAL(output)[outputpos], v); + } else { + memcpy(&ZSTR_VAL(output)[outputpos], &v, sizeof(v)); + } outputpos += sizeof(v); } break; } + case 'd': + case 'e': case 'E': { - /* pack big endian double */ while (arg-- > 0) { double v = zval_get_double(&argv[currentarg++]); - php_pack_copy_double(0, &ZSTR_VAL(output)[outputpos], v); + if (code == 'e' || formatendian[i] == PHP_LITTLE_ENDIAN) { + php_pack_copy_double(1, &ZSTR_VAL(output)[outputpos], v); + } else if (code == 'E' || formatendian[i] == PHP_BIG_ENDIAN) { + php_pack_copy_double(0, &ZSTR_VAL(output)[outputpos], v); + } else { + memcpy(&ZSTR_VAL(output)[outputpos], &v, sizeof(v)); + } outputpos += sizeof(v); } break; @@ -652,6 +756,7 @@ PHP_FUNCTION(pack) efree(formatcodes); efree(formatargs); + efree(formatendian); ZSTR_VAL(output)[outputpos] = '\0'; ZSTR_LEN(output) = outputpos; RETURN_NEW_STR(output); @@ -710,6 +815,21 @@ PHP_FUNCTION(unpack) char *name; int namelen; int size = 0; + php_pack_endianness endian = PHP_NO_ENDIAN_MODIFIER; + + if (formatlen > 0) { + char c = *format; + + if (c == '<') { + endian = PHP_LITTLE_ENDIAN; + format++; + formatlen--; + } else if (c == '>') { + endian = PHP_BIG_ENDIAN; + format++; + formatlen--; + } + } /* Handle format arguments if any */ if (formatlen > 0) { @@ -755,6 +875,10 @@ PHP_FUNCTION(unpack) switch (type) { /* Never use any input */ case 'X': + if (endian != PHP_NO_ENDIAN_MODIFIER) { + zend_value_error("Endianness modifier is not supported for format code '%c'", type); + RETURN_THROWS(); + } size = -1; if (repetitions < 0) { php_error_docref(NULL, E_WARNING, "Type %c: '*' ignored", type); @@ -763,18 +887,30 @@ PHP_FUNCTION(unpack) break; case '@': + if (endian != PHP_NO_ENDIAN_MODIFIER) { + zend_value_error("Endianness modifier is not supported for format code '%c'", type); + RETURN_THROWS(); + } size = 0; break; case 'a': case 'A': case 'Z': + if (endian != PHP_NO_ENDIAN_MODIFIER) { + zend_value_error("Endianness modifier is not supported for format code '%c'", type); + RETURN_THROWS(); + } size = repetitions; repetitions = 1; break; case 'h': case 'H': + if (endian != PHP_NO_ENDIAN_MODIFIER) { + zend_value_error("Endianness modifier is not supported for format code '%c'", type); + RETURN_THROWS(); + } size = (repetitions > 0) ? ((unsigned int) repetitions + 1) / 2 : repetitions; repetitions = 1; break; @@ -783,36 +919,73 @@ PHP_FUNCTION(unpack) case 'c': case 'C': case 'x': + if (endian != PHP_NO_ENDIAN_MODIFIER) { + zend_value_error("Endianness modifier is not supported for format code '%c'", type); + RETURN_THROWS(); + } size = 1; break; - /* Use 2 bytes of input */ + /* Use 2 bytes of input, endianness modifiers allowed */ case 's': case 'S': + size = 2; + break; + + /* Use 2 bytes of input with inherent endianness */ case 'n': case 'v': + if (endian != PHP_NO_ENDIAN_MODIFIER) { + zend_value_error("Endianness modifier '%c' cannot be applied to format code '%c' which already has inherent endianness", (endian == PHP_LITTLE_ENDIAN) ? '<' : '>', type); + RETURN_THROWS(); + } size = 2; break; /* Use sizeof(int) bytes of input */ case 'i': case 'I': + if (endian != PHP_NO_ENDIAN_MODIFIER) { + zend_value_error("Endianness modifier is not supported for format code '%c'", type); + RETURN_THROWS(); + } size = sizeof(int); break; - /* Use 4 bytes of input */ + /* Use 4 bytes of input, endianness modifiers allowed */ case 'l': case 'L': + size = 4; + break; + + /* Use 4 bytes of input with inherent endianness */ case 'N': case 'V': + if (endian != PHP_NO_ENDIAN_MODIFIER) { + zend_value_error("Endianness modifier '%c' cannot be applied to format code '%c' which already has inherent endianness", (endian == PHP_LITTLE_ENDIAN) ? '<' : '>', type); + RETURN_THROWS(); + } size = 4; break; - /* Use 8 bytes of input */ + /* Use 8 bytes of input, endianness modifiers allowed */ case 'q': case 'Q': +#if SIZEOF_ZEND_LONG > 4 + size = 8; + break; +#else + zend_value_error("64-bit format codes are not available for 32-bit versions of PHP"); + RETURN_THROWS(); +#endif + + /* Use 8 bytes of input with inherent endianness */ case 'J': case 'P': + if (endian != PHP_NO_ENDIAN_MODIFIER) { + zend_value_error("Endianness modifier '%c' cannot be applied to format code '%c' which already has inherent endianness", (endian == PHP_LITTLE_ENDIAN) ? '<' : '>', type); + RETURN_THROWS(); + } #if SIZEOF_ZEND_LONG > 4 size = 8; break; @@ -821,17 +994,33 @@ PHP_FUNCTION(unpack) RETURN_THROWS(); #endif - /* Use sizeof(float) bytes of input */ + /* Use sizeof(float) bytes of input, endianness modifiers allowed */ case 'f': + size = sizeof(float); + break; + + /* Use sizeof(float) bytes of input with inherent endianness */ case 'g': case 'G': + if (endian != PHP_NO_ENDIAN_MODIFIER) { + zend_value_error("Endianness modifier '%c' cannot be applied to format code '%c' which already has inherent endianness", (endian == PHP_LITTLE_ENDIAN) ? '<' : '>', type); + RETURN_THROWS(); + } size = sizeof(float); break; - /* Use sizeof(double) bytes of input */ + /* Use sizeof(double) bytes of input, endianness modifiers allowed */ case 'd': + size = sizeof(double); + break; + + /* Use sizeof(double) bytes of input with inherent endianness */ case 'e': case 'E': + if (endian != PHP_NO_ENDIAN_MODIFIER) { + zend_value_error("Endianness modifier '%c' cannot be applied to format code '%c' which already has inherent endianness", (endian == PHP_LITTLE_ENDIAN) ? '<' : '>', type); + RETURN_THROWS(); + } size = sizeof(double); break; @@ -996,17 +1185,30 @@ PHP_FUNCTION(unpack) break; } - case 's': /* signed machine endian */ - case 'S': /* unsigned machine endian */ + case 's': /* signed, machine endian or explicit */ + case 'S': /* unsigned, machine endian or explicit */ case 'n': /* unsigned big endian */ case 'v': { /* unsigned little endian */ zend_long v = 0; uint16_t x = *((unaligned_uint16_t*) &input[inputpos]); + bool need_swap = false; + if (type == 'n') { + need_swap = MACHINE_LITTLE_ENDIAN; + } else if (type == 'v') { + need_swap = !MACHINE_LITTLE_ENDIAN; + } else if (endian == PHP_LITTLE_ENDIAN) { + need_swap = !MACHINE_LITTLE_ENDIAN; + } else if (endian == PHP_BIG_ENDIAN) { + need_swap = MACHINE_LITTLE_ENDIAN; + } + + if (need_swap) { + x = php_pack_reverse_int16(x); + } + if (type == 's') { v = (int16_t) x; - } else if ((type == 'n' && MACHINE_LITTLE_ENDIAN) || (type == 'v' && !MACHINE_LITTLE_ENDIAN)) { - v = php_pack_reverse_int16(x); } else { v = x; } @@ -1030,17 +1232,30 @@ PHP_FUNCTION(unpack) break; } - case 'l': /* signed machine endian */ - case 'L': /* unsigned machine endian */ + case 'l': /* signed, machine endian or explicit */ + case 'L': /* unsigned, machine endian or explicit */ case 'N': /* unsigned big endian */ case 'V': { /* unsigned little endian */ zend_long v = 0; uint32_t x = *((unaligned_uint32_t*) &input[inputpos]); + bool need_swap = false; + if (type == 'N') { + need_swap = MACHINE_LITTLE_ENDIAN; + } else if (type == 'V') { + need_swap = !MACHINE_LITTLE_ENDIAN; + } else if (endian == PHP_LITTLE_ENDIAN) { + need_swap = !MACHINE_LITTLE_ENDIAN; + } else if (endian == PHP_BIG_ENDIAN) { + need_swap = MACHINE_LITTLE_ENDIAN; + } + + if (need_swap) { + x = php_pack_reverse_int32(x); + } + if (type == 'l') { v = (int32_t) x; - } else if ((type == 'N' && MACHINE_LITTLE_ENDIAN) || (type == 'V' && !MACHINE_LITTLE_ENDIAN)) { - v = php_pack_reverse_int32(x); } else { v = x; } @@ -1050,17 +1265,30 @@ PHP_FUNCTION(unpack) } #if SIZEOF_ZEND_LONG > 4 - case 'q': /* signed machine endian */ - case 'Q': /* unsigned machine endian */ + case 'q': /* signed, machine endian or explicit */ + case 'Q': /* unsigned, machine endian or explicit */ case 'J': /* unsigned big endian */ case 'P': { /* unsigned little endian */ zend_long v = 0; uint64_t x = *((unaligned_uint64_t*) &input[inputpos]); + bool need_swap = false; + if (type == 'J') { + need_swap = MACHINE_LITTLE_ENDIAN; + } else if (type == 'P') { + need_swap = !MACHINE_LITTLE_ENDIAN; + } else if (endian == PHP_LITTLE_ENDIAN) { + need_swap = !MACHINE_LITTLE_ENDIAN; + } else if (endian == PHP_BIG_ENDIAN) { + need_swap = MACHINE_LITTLE_ENDIAN; + } + + if (need_swap) { + x = php_pack_reverse_int64(x); + } + if (type == 'q') { v = (int64_t) x; - } else if ((type == 'J' && MACHINE_LITTLE_ENDIAN) || (type == 'P' && !MACHINE_LITTLE_ENDIAN)) { - v = php_pack_reverse_int64(x); } else { v = x; } @@ -1076,9 +1304,9 @@ PHP_FUNCTION(unpack) { float v; - if (type == 'g') { + if (type == 'g' || endian == PHP_LITTLE_ENDIAN) { v = php_pack_parse_float(1, &input[inputpos]); - } else if (type == 'G') { + } else if (type == 'G' || endian == PHP_BIG_ENDIAN) { v = php_pack_parse_float(0, &input[inputpos]); } else { memcpy(&v, &input[inputpos], sizeof(float)); @@ -1094,9 +1322,9 @@ PHP_FUNCTION(unpack) case 'E': /* big endian float */ { double v; - if (type == 'e') { + if (type == 'e' || endian == PHP_LITTLE_ENDIAN) { v = php_pack_parse_double(1, &input[inputpos]); - } else if (type == 'E') { + } else if (type == 'E' || endian == PHP_BIG_ENDIAN) { v = php_pack_parse_double(0, &input[inputpos]); } else { memcpy(&v, &input[inputpos], sizeof(double)); diff --git a/ext/standard/tests/strings/pack_endian_modifiers.phpt b/ext/standard/tests/strings/pack_endian_modifiers.phpt new file mode 100644 index 000000000000..0ab0a4b2126c --- /dev/null +++ b/ext/standard/tests/strings/pack_endian_modifiers.phpt @@ -0,0 +1,182 @@ +--TEST-- +pack()/unpack() endianness modifiers +--FILE-- +", 0x0102))); +var_dump(bin2hex(pack("S>", 0x0102))); + +var_dump(pack("s<", 0x0102) === pack("v", 0x0102)); +var_dump(pack("S<", 0x0102) === pack("v", 0x0102)); + +var_dump(pack("s>", 0x0102) === pack("n", 0x0102)); +var_dump(pack("S>", 0x0102) === pack("n", 0x0102)); + +var_dump(bin2hex(pack("l<", 0x01020304))); +var_dump(bin2hex(pack("L<", 0x01020304))); + +var_dump(bin2hex(pack("l>", 0x01020304))); +var_dump(bin2hex(pack("L>", 0x01020304))); + +var_dump(pack("l<", 0x01020304) === pack("V", 0x01020304)); +var_dump(pack("L<", 0x01020304) === pack("V", 0x01020304)); + +var_dump(pack("l>", 0x01020304) === pack("N", 0x01020304)); +var_dump(pack("L>", 0x01020304) === pack("N", 0x01020304)); + +// === Integer unpack with endianness modifiers === + +var_dump(unpack("s<", "\x02\x01")); +var_dump(unpack("S<", "\x02\x01")); + +var_dump(unpack("s>", "\x01\x02")); +var_dump(unpack("S>", "\x01\x02")); + +var_dump(unpack("s<", "\xfe\xff")); // -2 in little-endian +var_dump(unpack("s>", "\xff\xfe")); // -2 in big-endian + +var_dump(unpack("l<", "\x04\x03\x02\x01")); +var_dump(unpack("L<", "\x04\x03\x02\x01")); + +var_dump(unpack("l>", "\x01\x02\x03\x04")); +var_dump(unpack("L>", "\x01\x02\x03\x04")); + +var_dump(unpack("l<", "\xfe\xff\xff\xff")); // -2 in little-endian +var_dump(unpack("l>", "\xff\xff\xff\xfe")); // -2 in big-endian + +var_dump(bin2hex(pack("s<2", 0x0102, 0x0304))); +var_dump(bin2hex(pack("s>2", 0x0102, 0x0304))); + +var_dump(unpack("s<2", "\x02\x01\x04\x03")); +var_dump(unpack("s>2", "\x01\x02\x03\x04")); + +var_dump(unpack("scount", "\x02\x01\x00\x00\x00\x05")); + +var_dump(pack("f<", 3.14) === pack("g", 3.14)); +var_dump(pack("f>", 3.14) === pack("G", 3.14)); + +var_dump(pack("d<", 3.14) === pack("e", 3.14)); +var_dump(pack("d>", 3.14) === pack("E", 3.14)); + +$packed_le = pack("g", 3.14); +$packed_be = pack("G", 3.14); +$unpacked_le = unpack("f<", $packed_le); +$unpacked_be = unpack("f>", $packed_be); +$unpacked_g = unpack("g", $packed_le); +$unpacked_G = unpack("G", $packed_be); +var_dump($unpacked_le[1] === $unpacked_g[1]); +var_dump($unpacked_be[1] === $unpacked_G[1]); + +$packed_le = pack("e", 3.14); +$packed_be = pack("E", 3.14); +$unpacked_le = unpack("d<", $packed_le); +$unpacked_be = unpack("d>", $packed_be); +$unpacked_e = unpack("e", $packed_le); +$unpacked_E = unpack("E", $packed_be); +var_dump($unpacked_le[1] === $unpacked_e[1]); +var_dump($unpacked_be[1] === $unpacked_E[1]); + +$machine_float = pack("f", 1.5); +var_dump(unpack("f", $machine_float)[1] === 1.5); + +$machine_double = pack("d", 1.5); +var_dump(unpack("d", $machine_double)[1] === 1.5); +?> +--EXPECT-- +string(4) "0201" +string(4) "0201" +string(4) "0102" +string(4) "0102" +bool(true) +bool(true) +bool(true) +bool(true) +string(8) "04030201" +string(8) "04030201" +string(8) "01020304" +string(8) "01020304" +bool(true) +bool(true) +bool(true) +bool(true) +array(1) { + [1]=> + int(258) +} +array(1) { + [1]=> + int(258) +} +array(1) { + [1]=> + int(258) +} +array(1) { + [1]=> + int(258) +} +array(1) { + [1]=> + int(-2) +} +array(1) { + [1]=> + int(-2) +} +array(1) { + [1]=> + int(16909060) +} +array(1) { + [1]=> + int(16909060) +} +array(1) { + [1]=> + int(16909060) +} +array(1) { + [1]=> + int(16909060) +} +array(1) { + [1]=> + int(-2) +} +array(1) { + [1]=> + int(-2) +} +string(8) "02010403" +string(8) "01020304" +array(2) { + [1]=> + int(258) + [2]=> + int(772) +} +array(2) { + [1]=> + int(258) + [2]=> + int(772) +} +array(2) { + ["value"]=> + int(258) + ["count"]=> + int(5) +} +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/ext/standard/tests/strings/pack_endian_modifiers_32.phpt b/ext/standard/tests/strings/pack_endian_modifiers_32.phpt new file mode 100644 index 000000000000..13c2a3a73025 --- /dev/null +++ b/ext/standard/tests/strings/pack_endian_modifiers_32.phpt @@ -0,0 +1,37 @@ +--TEST-- +pack()/unpack() endianness modifiers on 64-bit format codes, 32-bit systems +--SKIPIF-- + 4) die("skip 32bit test only"); +?> +--FILE-- +', 'Q<', 'Q>']; +foreach ($formats as $fmt) { + try { + pack($fmt, 0); + echo "FAIL: Expected ValueError for pack('$fmt', 0)\n"; + } catch (ValueError $e) { + echo "pack('$fmt'): " . $e->getMessage() . "\n"; + } +} + +foreach ($formats as $fmt) { + try { + unpack($fmt, "\x00\x00\x00\x00\x00\x00\x00\x00"); + echo "FAIL: Expected ValueError for unpack('$fmt', ...)\n"; + } catch (ValueError $e) { + echo "unpack('$fmt'): " . $e->getMessage() . "\n"; + } +} +?> +--EXPECT-- +pack('q<'): 64-bit format codes are not available for 32-bit versions of PHP +pack('q>'): 64-bit format codes are not available for 32-bit versions of PHP +pack('Q<'): 64-bit format codes are not available for 32-bit versions of PHP +pack('Q>'): 64-bit format codes are not available for 32-bit versions of PHP +unpack('q<'): 64-bit format codes are not available for 32-bit versions of PHP +unpack('q>'): 64-bit format codes are not available for 32-bit versions of PHP +unpack('Q<'): 64-bit format codes are not available for 32-bit versions of PHP +unpack('Q>'): 64-bit format codes are not available for 32-bit versions of PHP diff --git a/ext/standard/tests/strings/pack_endian_modifiers_64.phpt b/ext/standard/tests/strings/pack_endian_modifiers_64.phpt new file mode 100644 index 000000000000..61a8f3f95f1c --- /dev/null +++ b/ext/standard/tests/strings/pack_endian_modifiers_64.phpt @@ -0,0 +1,63 @@ +--TEST-- +pack()/unpack() endianness modifiers on 64-bit format codes +--SKIPIF-- + +--FILE-- +", 0x0102030405060708))); +var_dump(bin2hex(pack("Q>", 0x0102030405060708))); + +var_dump(pack("q<", 0x0102030405060708) === pack("P", 0x0102030405060708)); +var_dump(pack("Q<", 0x0102030405060708) === pack("P", 0x0102030405060708)); + +var_dump(pack("q>", 0x0102030405060708) === pack("J", 0x0102030405060708)); +var_dump(pack("Q>", 0x0102030405060708) === pack("J", 0x0102030405060708)); + +var_dump(unpack("q<", "\x08\x07\x06\x05\x04\x03\x02\x01")); +var_dump(unpack("Q<", "\x08\x07\x06\x05\x04\x03\x02\x01")); + +var_dump(unpack("q>", "\x01\x02\x03\x04\x05\x06\x07\x08")); +var_dump(unpack("Q>", "\x01\x02\x03\x04\x05\x06\x07\x08")); + +var_dump(unpack("q<", "\xfe\xff\xff\xff\xff\xff\xff\xff")); // -2 in little-endian +var_dump(unpack("q>", "\xff\xff\xff\xff\xff\xff\xff\xfe")); // -2 in big-endian +?> +--EXPECT-- +string(16) "0807060504030201" +string(16) "0807060504030201" +string(16) "0102030405060708" +string(16) "0102030405060708" +bool(true) +bool(true) +bool(true) +bool(true) +array(1) { + [1]=> + int(72623859790382856) +} +array(1) { + [1]=> + int(72623859790382856) +} +array(1) { + [1]=> + int(72623859790382856) +} +array(1) { + [1]=> + int(72623859790382856) +} +array(1) { + [1]=> + int(-2) +} +array(1) { + [1]=> + int(-2) +} diff --git a/ext/standard/tests/strings/pack_endian_modifiers_error.phpt b/ext/standard/tests/strings/pack_endian_modifiers_error.phpt new file mode 100644 index 000000000000..685d21c7c00e --- /dev/null +++ b/ext/standard/tests/strings/pack_endian_modifiers_error.phpt @@ -0,0 +1,105 @@ +--TEST-- +pack()/unpack() endianness modifiers, invalid combinations +--FILE-- +', 'N<', 'V>', 'J<', 'P>']; +foreach ($inherent_formats as $fmt) { + try { + pack($fmt, 1); + echo "FAIL: Expected ValueError for pack('$fmt', 1)\n"; + } catch (ValueError $e) { + echo "pack('$fmt'): " . $e->getMessage() . "\n"; + } +} + +$inherent_float_formats = ['g<', 'G>', 'e<', 'E>']; +foreach ($inherent_float_formats as $fmt) { + try { + pack($fmt, 1.0); + echo "FAIL: Expected ValueError for pack('$fmt', 1.0)\n"; + } catch (ValueError $e) { + echo "pack('$fmt'): " . $e->getMessage() . "\n"; + } +} + +$unsupported_formats = ['c<', 'C>', 'a<', 'A>', 'h<', 'H>', 'i<', 'I>', 'x<', 'X>', '@<']; +foreach ($unsupported_formats as $fmt) { + try { + pack($fmt, 1); + echo "FAIL: Expected ValueError for pack('$fmt', 1)\n"; + } catch (ValueError $e) { + echo "pack('$fmt'): " . $e->getMessage() . "\n"; + } +} + +foreach (['n<', 'v>', 'N<', 'V>', 'J<', 'P>'] as $fmt) { + try { + unpack($fmt, "\x00\x00\x00\x00\x00\x00\x00\x00"); + echo "FAIL: Expected ValueError for unpack('$fmt', ...)\n"; + } catch (ValueError $e) { + echo "unpack('$fmt'): " . $e->getMessage() . "\n"; + } +} + +foreach (['g<', 'G>', 'e<', 'E>'] as $fmt) { + try { + unpack($fmt, "\x00\x00\x00\x00\x00\x00\x00\x00"); + echo "FAIL: Expected ValueError for unpack('$fmt', ...)\n"; + } catch (ValueError $e) { + echo "unpack('$fmt'): " . $e->getMessage() . "\n"; + } +} + +foreach (['c<', 'C>', 'a<', 'A>', 'h<', 'H>', 'i<', 'I>', 'x<', 'X>', '@<'] as $fmt) { + try { + unpack($fmt, "\x00\x00\x00\x00\x00\x00\x00\x00"); + echo "FAIL: Expected ValueError for unpack('$fmt', ...)\n"; + } catch (ValueError $e) { + echo "unpack('$fmt'): " . $e->getMessage() . "\n"; + } +} +?> +--EXPECT-- +pack('n<'): Endianness modifier '<' cannot be applied to format code 'n' which already has inherent endianness +pack('v>'): Endianness modifier '>' cannot be applied to format code 'v' which already has inherent endianness +pack('N<'): Endianness modifier '<' cannot be applied to format code 'N' which already has inherent endianness +pack('V>'): Endianness modifier '>' cannot be applied to format code 'V' which already has inherent endianness +pack('J<'): Endianness modifier '<' cannot be applied to format code 'J' which already has inherent endianness +pack('P>'): Endianness modifier '>' cannot be applied to format code 'P' which already has inherent endianness +pack('g<'): Endianness modifier '<' cannot be applied to format code 'g' which already has inherent endianness +pack('G>'): Endianness modifier '>' cannot be applied to format code 'G' which already has inherent endianness +pack('e<'): Endianness modifier '<' cannot be applied to format code 'e' which already has inherent endianness +pack('E>'): Endianness modifier '>' cannot be applied to format code 'E' which already has inherent endianness +pack('c<'): Endianness modifier is not supported for format code 'c' +pack('C>'): Endianness modifier is not supported for format code 'C' +pack('a<'): Endianness modifier is not supported for format code 'a' +pack('A>'): Endianness modifier is not supported for format code 'A' +pack('h<'): Endianness modifier is not supported for format code 'h' +pack('H>'): Endianness modifier is not supported for format code 'H' +pack('i<'): Endianness modifier is not supported for format code 'i' +pack('I>'): Endianness modifier is not supported for format code 'I' +pack('x<'): Endianness modifier is not supported for format code 'x' +pack('X>'): Endianness modifier is not supported for format code 'X' +pack('@<'): Endianness modifier is not supported for format code '@' +unpack('n<'): Endianness modifier '<' cannot be applied to format code 'n' which already has inherent endianness +unpack('v>'): Endianness modifier '>' cannot be applied to format code 'v' which already has inherent endianness +unpack('N<'): Endianness modifier '<' cannot be applied to format code 'N' which already has inherent endianness +unpack('V>'): Endianness modifier '>' cannot be applied to format code 'V' which already has inherent endianness +unpack('J<'): Endianness modifier '<' cannot be applied to format code 'J' which already has inherent endianness +unpack('P>'): Endianness modifier '>' cannot be applied to format code 'P' which already has inherent endianness +unpack('g<'): Endianness modifier '<' cannot be applied to format code 'g' which already has inherent endianness +unpack('G>'): Endianness modifier '>' cannot be applied to format code 'G' which already has inherent endianness +unpack('e<'): Endianness modifier '<' cannot be applied to format code 'e' which already has inherent endianness +unpack('E>'): Endianness modifier '>' cannot be applied to format code 'E' which already has inherent endianness +unpack('c<'): Endianness modifier is not supported for format code 'c' +unpack('C>'): Endianness modifier is not supported for format code 'C' +unpack('a<'): Endianness modifier is not supported for format code 'a' +unpack('A>'): Endianness modifier is not supported for format code 'A' +unpack('h<'): Endianness modifier is not supported for format code 'h' +unpack('H>'): Endianness modifier is not supported for format code 'H' +unpack('i<'): Endianness modifier is not supported for format code 'i' +unpack('I>'): Endianness modifier is not supported for format code 'I' +unpack('x<'): Endianness modifier is not supported for format code 'x' +unpack('X>'): Endianness modifier is not supported for format code 'X' +unpack('@<'): Endianness modifier is not supported for format code '@' diff --git a/ext/zip/php_zip.c b/ext/zip/php_zip.c index f7a294425e6d..a7a3e340ecf1 100644 --- a/ext/zip/php_zip.c +++ b/ext/zip/php_zip.c @@ -339,6 +339,7 @@ static zend_result php_zip_add_file(ze_zip_object *obj, const char *filename, si zip_flags_t flags ) /* {{{ */ { + struct zip *za = php_zip_object_za(obj); struct zip_source *zs; char resolved_path[MAXPATHLEN]; php_stream_statbuf ssb; @@ -364,33 +365,33 @@ static zend_result php_zip_add_file(ze_zip_object *obj, const char *filename, si return FAILURE; } flags ^= ZIP_FL_OPEN_FILE_NOW; - zs = zip_source_filep(obj->za, fd, offset_start, offset_len); + zs = zip_source_filep(za, fd, offset_start, offset_len); if (!zs) { fclose(fd); return FAILURE; } } else { - zs = zip_source_file(obj->za, resolved_path, offset_start, offset_len); + zs = zip_source_file(za, resolved_path, offset_start, offset_len); if (!zs) { return FAILURE; } } /* Replace */ if (replace >= 0) { - if (zip_file_replace(obj->za, replace, zs, flags) < 0) { + if (zip_file_replace(za, replace, zs, flags) < 0) { zip_source_free(zs); return FAILURE; } - zip_error_clear(obj->za); + zip_error_clear(za); return SUCCESS; } /* Add */ - obj->last_id = zip_file_add(obj->za, entry_name, zs, flags); + obj->last_id = zip_file_add(za, entry_name, zs, flags); if (obj->last_id < 0) { zip_source_free(zs); return FAILURE; } - zip_error_clear(obj->za); + zip_error_clear(za); return SUCCESS; } /* }}} */ @@ -526,7 +527,7 @@ static zend_result php_zip_parse_options(HashTable *options, zip_options *opts) #define ZIP_FROM_OBJECT(intern, object) \ { \ ze_zip_object *obj = Z_ZIP_P(object); \ - intern = obj->za; \ + intern = php_zip_object_za(obj); \ if (!intern) { \ zend_value_error("Invalid or uninitialized Zip object"); \ RETURN_THROWS(); \ @@ -565,12 +566,13 @@ static zend_result php_zip_parse_options(HashTable *options, zip_options *opts) static zend_long php_zip_status(ze_zip_object *obj) /* {{{ */ { + struct zip *za = php_zip_object_za(obj); zend_long zep = (zend_long)obj->err_zip; /* saved err if closed */ - if (obj->za) { + if (za) { zip_error_t *err; - err = zip_get_error(obj->za); + err = zip_get_error(za); zep = (zend_long)zip_error_code_zip(err); zip_error_fini(err); } @@ -586,12 +588,13 @@ static zend_long php_zip_last_id(ze_zip_object *obj) /* {{{ */ static zend_long php_zip_status_sys(ze_zip_object *obj) /* {{{ */ { + struct zip *za = php_zip_object_za(obj); zend_long syp = (zend_long)obj->err_sys; /* saved err if closed */ - if (obj->za) { + if (za) { zip_error_t *err; - err = zip_get_error(obj->za); + err = zip_get_error(za); syp = (zend_long)zip_error_code_system(err); zip_error_fini(err); } @@ -601,8 +604,10 @@ static zend_long php_zip_status_sys(ze_zip_object *obj) /* {{{ */ static zend_long php_zip_get_num_files(ze_zip_object *obj) /* {{{ */ { - if (obj->za) { - zip_int64_t num = zip_get_num_entries(obj->za, 0); + struct zip *za = php_zip_object_za(obj); + + if (za) { + zip_int64_t num = zip_get_num_entries(za, 0); return MIN(num, ZEND_LONG_MAX); } return 0; @@ -621,8 +626,10 @@ static char * php_zipobj_get_filename(ze_zip_object *obj, int *len) /* {{{ */ static char * php_zipobj_get_zip_comment(ze_zip_object *obj, int *len) /* {{{ */ { - if (obj->za) { - return (char *)zip_get_archive_comment(obj->za, len, 0); + struct zip *za = php_zip_object_za(obj); + + if (za) { + return (char *)zip_get_archive_comment(za, len, 0); } return NULL; } @@ -635,7 +642,9 @@ static char * php_zipobj_get_zip_comment(ze_zip_object *obj, int *len) /* {{{ */ * If out_str is NULL, the final string contents, if any, will be discarded. */ static bool php_zipobj_close(ze_zip_object *obj, zend_string **out_str) /* {{{ */ { - struct zip *intern = obj->za; + php_zip_archive *archive = obj->archive; + struct zip *intern = archive ? archive->za : NULL; + bool bailout = false; bool success = false; if (intern) { @@ -665,22 +674,26 @@ static bool php_zipobj_close(ze_zip_object *obj, zend_string **out_str) /* {{{ * obj->filename_len = 0; } - if (obj->out_str) { + if (archive && archive->out_str) { if (out_str) { - *out_str = obj->out_str; + *out_str = archive->out_str; } else { - zend_string_release(obj->out_str); + zend_string_release(archive->out_str); } - obj->out_str = NULL; + archive->out_str = NULL; } else { ZEND_ASSERT(!out_str); } - obj->za = NULL; - obj->from_string = false; + if (archive) { + archive->za = NULL; + bailout = archive->bailout_callback; + archive->bailout_callback = false; + obj->archive = NULL; + bailout |= php_zip_archive_release(archive); + } - if (obj->bailout_callback) { - obj->bailout_callback = false; + if (bailout) { zend_bailout(); } @@ -1102,10 +1115,10 @@ static HashTable *php_zip_get_properties(zend_object *object)/* {{{ */ #ifdef HAVE_PROGRESS_CALLBACK static void php_zip_progress_callback_free(void *ptr) { - ze_zip_object *obj = ptr; + php_zip_archive *archive = ptr; - if (ZEND_FCC_INITIALIZED(obj->progress_callback)) { - zend_fcc_dtor(&obj->progress_callback); + if (ZEND_FCC_INITIALIZED(archive->progress_callback)) { + zend_fcc_dtor(&archive->progress_callback); } } #endif @@ -1113,30 +1126,76 @@ static void php_zip_progress_callback_free(void *ptr) #ifdef HAVE_CANCEL_CALLBACK static void php_zip_cancel_callback_free(void *ptr) { - ze_zip_object *obj = ptr; + php_zip_archive *archive = ptr; - if (ZEND_FCC_INITIALIZED(obj->cancel_callback)) { - zend_fcc_dtor(&obj->cancel_callback); + if (ZEND_FCC_INITIALIZED(archive->cancel_callback)) { + zend_fcc_dtor(&archive->cancel_callback); } } #endif +static php_zip_archive *php_zip_archive_create(struct zip *za) +{ + php_zip_archive *archive = ecalloc(1, sizeof(php_zip_archive)); + + archive->za = za; + archive->refcount = 1; + + return archive; +} + +void php_zip_archive_addref(php_zip_archive *archive) +{ + ZEND_ASSERT(archive->refcount > 0); + archive->refcount++; +} + +bool php_zip_archive_release(php_zip_archive *archive) +{ + ZEND_ASSERT(archive->refcount > 0); + if (--archive->refcount != 0) { + return false; + } + + if (archive->za) { + if (zip_close(archive->za) != 0) { + if (!archive->bailout_callback) { + php_error_docref(NULL, E_WARNING, "Cannot destroy the zip context: %s", zip_strerror(archive->za)); + } + zip_discard(archive->za); + } + archive->za = NULL; + } + +#ifdef HAVE_PROGRESS_CALLBACK + /* In case libzip did not invoke the callback state destructor. */ + php_zip_progress_callback_free(archive); +#endif + +#ifdef HAVE_CANCEL_CALLBACK + /* In case libzip did not invoke the callback state destructor. */ + php_zip_cancel_callback_free(archive); +#endif + + if (archive->out_str) { + zend_string_release(archive->out_str); + } + + bool bailout = archive->bailout_callback; + efree(archive); + return bailout; +} + static void php_zip_object_dtor(zend_object *object) { zend_objects_destroy_object(object); ze_zip_object *intern = php_zip_fetch_object(object); - if (intern->za) { - if (zip_close(intern->za) != 0) { - if (!intern->bailout_callback) { - php_error_docref(NULL, E_WARNING, "Cannot destroy the zip context: %s", zip_strerror(intern->za)); - } - zip_discard(intern->za); - } - intern->za = NULL; - if (intern->bailout_callback) { - intern->bailout_callback = false; + if (intern->archive) { + bool bailout = php_zip_archive_release(intern->archive); + intern->archive = NULL; + if (bailout) { zend_bailout(); } } @@ -1147,17 +1206,6 @@ static void php_zip_object_free_storage(zend_object *object) /* {{{ */ ze_zip_object * intern = php_zip_fetch_object(object); php_zipobj_close(intern, NULL); - -#ifdef HAVE_PROGRESS_CALLBACK - /* if not properly called by libzip */ - php_zip_progress_callback_free(intern); -#endif - -#ifdef HAVE_CANCEL_CALLBACK - /* if not properly called by libzip */ - php_zip_cancel_callback_free(intern); -#endif - zend_object_std_dtor(&intern->zo); } /* }}} */ @@ -1576,8 +1624,7 @@ PHP_METHOD(ZipArchive, open) } ze_obj->filename = resolved_path; ze_obj->filename_len = strlen(resolved_path); - ze_obj->za = intern; - ze_obj->from_string = false; + ze_obj->archive = php_zip_archive_create(intern); RETURN_TRUE; } /* }}} */ @@ -1598,18 +1645,21 @@ PHP_METHOD(ZipArchive, openString) } ze_zip_object *ze_obj = Z_ZIP_P(self); + php_zip_archive *archive; php_zipobj_close(ze_obj, NULL); zip_error_t err; zip_error_init(&err); - zip_source_t * zip_source = php_zip_create_string_source(buffer, &ze_obj->out_str, &err); + archive = php_zip_archive_create(NULL); + zip_source_t * zip_source = php_zip_create_string_source(buffer, &archive->out_str, &err); if (!zip_source) { ze_obj->err_zip = zip_error_code_zip(&err); ze_obj->err_sys = zip_error_code_system(&err); zip_error_fini(&err); + php_zip_archive_release(archive); RETURN_LONG(ze_obj->err_zip); } @@ -1619,11 +1669,13 @@ PHP_METHOD(ZipArchive, openString) ze_obj->err_sys = zip_error_code_system(&err); zip_error_fini(&err); zip_source_free(zip_source); + php_zip_archive_release(archive); RETURN_LONG(ze_obj->err_zip); } - ze_obj->from_string = true; - ze_obj->za = intern; + archive->za = intern; + archive->from_string = true; + ze_obj->archive = archive; zip_error_fini(&err); RETURN_TRUE; } @@ -1675,7 +1727,7 @@ PHP_METHOD(ZipArchive, closeString) ZIP_FROM_OBJECT(intern, self); - if (!Z_ZIP_P(self)->from_string) { + if (!Z_ZIP_P(self)->archive->from_string) { zend_throw_error(NULL, "ZipArchive::closeString can only be called on " "an archive opened with ZipArchive::openString"); RETURN_THROWS(); @@ -1737,12 +1789,14 @@ PHP_METHOD(ZipArchive, clearError) { zval *self = ZEND_THIS; ze_zip_object *ze_obj; + struct zip *za; ZEND_PARSE_PARAMETERS_NONE(); ze_obj = Z_ZIP_P(self); /* not ZIP_FROM_OBJECT as we can use saved error after close */ - if (ze_obj->za) { - zip_error_clear(ze_obj->za); + za = php_zip_object_za(ze_obj); + if (za) { + zip_error_clear(za); } else { ze_obj->err_zip = 0; ze_obj->err_sys = 0; @@ -1755,15 +1809,16 @@ PHP_METHOD(ZipArchive, getStatusString) { zval *self = ZEND_THIS; ze_zip_object *ze_obj; + struct zip *za; ZEND_PARSE_PARAMETERS_NONE(); ze_obj = Z_ZIP_P(self); /* not ZIP_FROM_OBJECT as we can use saved error after close */ - - if (ze_obj->za) { + za = php_zip_object_za(ze_obj); + if (za) { zip_error_t *err; - err = zip_get_error(ze_obj->za); + err = zip_get_error(za); RETVAL_STRING(zip_error_strerror(err)); zip_error_fini(err); } else { @@ -1858,6 +1913,7 @@ static void php_zip_add_from_pattern(INTERNAL_FUNCTION_PARAMETERS, int type) /* if (found > 0) { zval *zval_file; ze_zip_object *ze_obj = Z_ZIP_P(self); + struct zip *za = php_zip_object_za(ze_obj); for (int i = 0; i < found; i++) { zend_string *basename = NULL; @@ -1920,14 +1976,14 @@ static void php_zip_add_from_pattern(INTERNAL_FUNCTION_PARAMETERS, int type) /* RETURN_FALSE; } if (opts.comp_method >= 0) { - if (zip_set_file_compression(ze_obj->za, ze_obj->last_id, opts.comp_method, opts.comp_flags)) { + if (zip_set_file_compression(za, ze_obj->last_id, opts.comp_method, opts.comp_flags)) { zend_array_destroy(Z_ARR_P(return_value)); RETURN_FALSE; } } #ifdef HAVE_ENCRYPTION if (opts.enc_method >= 0) { - if (!php_zip_file_set_encryption(ze_obj->za, ze_obj->last_id, opts.enc_method, opts.enc_password)) { + if (!php_zip_file_set_encryption(za, ze_obj->last_id, opts.enc_method, opts.enc_password)) { zend_array_destroy(Z_ARR_P(return_value)); RETURN_FALSE; } @@ -3084,9 +3140,9 @@ PHP_METHOD(ZipArchive, getStream) #ifdef HAVE_PROGRESS_CALLBACK static void php_zip_progress_callback(zip_t *arch, double state, void *ptr) { - ze_zip_object *obj = ptr; + php_zip_archive *archive = ptr; - if (UNEXPECTED(!EG(active) || obj->bailout_callback)) { + if (UNEXPECTED(!EG(active) || archive->bailout_callback)) { return; } @@ -3095,9 +3151,9 @@ static void php_zip_progress_callback(zip_t *arch, double state, void *ptr) ZVAL_DOUBLE(&cb_args[0], state); zend_try { - zend_call_known_fcc(&obj->progress_callback, NULL, 1, cb_args, NULL); + zend_call_known_fcc(&archive->progress_callback, NULL, 1, cb_args, NULL); } zend_catch { - obj->bailout_callback = true; + archive->bailout_callback = true; } zend_end_try(); } @@ -3108,27 +3164,28 @@ PHP_METHOD(ZipArchive, registerProgressCallback) double rate; zend_fcall_info dummy_fci; zend_fcall_info_cache fcc; + php_zip_archive *archive; ze_zip_object *obj; if (zend_parse_parameters(ZEND_NUM_ARGS(), "dF", &rate, &dummy_fci, &fcc) == FAILURE) { RETURN_THROWS(); } - /* Inline ZIP_FROM_OBJECT(intern, self); */ obj = Z_ZIP_P(ZEND_THIS); - intern = obj->za; - if (!intern) { \ + intern = php_zip_object_za(obj); + if (!intern) { zend_value_error("Invalid or uninitialized Zip object"); zend_release_fcall_info_cache(&fcc); RETURN_THROWS(); } + archive = obj->archive; /* register */ - if (zip_register_progress_callback_with_state(intern, rate, php_zip_progress_callback, php_zip_progress_callback_free, obj)) { + if (zip_register_progress_callback_with_state(intern, rate, php_zip_progress_callback, php_zip_progress_callback_free, archive)) { zend_release_fcall_info_cache(&fcc); RETURN_FALSE; } - zend_fcc_dup(&obj->progress_callback, &fcc); + zend_fcc_dup(&archive->progress_callback, &fcc); RETURN_TRUE; } @@ -3139,16 +3196,16 @@ PHP_METHOD(ZipArchive, registerProgressCallback) static int php_zip_cancel_callback(zip_t *arch, void *ptr) { zval cb_retval; - ze_zip_object *obj = ptr; + php_zip_archive *archive = ptr; - if (UNEXPECTED(!EG(active) || obj->bailout_callback)) { + if (UNEXPECTED(!EG(active) || archive->bailout_callback)) { return 0; } zend_try { - zend_call_known_fcc(&obj->cancel_callback, &cb_retval, 0, NULL, NULL); + zend_call_known_fcc(&archive->cancel_callback, &cb_retval, 0, NULL, NULL); } zend_catch { - obj->bailout_callback = true; + archive->bailout_callback = true; /* Cancel if a bailout occurs to allow cleanup to happen */ return -1; } zend_end_try(); @@ -3176,6 +3233,7 @@ PHP_METHOD(ZipArchive, registerCancelCallback) struct zip *intern; zend_fcall_info dummy_fci; zend_fcall_info_cache fcc; + php_zip_archive *archive; ze_zip_object *obj; if (zend_parse_parameters(ZEND_NUM_ARGS(), "F", &dummy_fci, &fcc) == FAILURE) { RETURN_THROWS(); @@ -3183,19 +3241,20 @@ PHP_METHOD(ZipArchive, registerCancelCallback) /* Inline ZIP_FROM_OBJECT(intern, self); */ obj = Z_ZIP_P(ZEND_THIS); - intern = obj->za; - if (!intern) { \ + intern = php_zip_object_za(obj); + if (!intern) { zend_value_error("Invalid or uninitialized Zip object"); zend_release_fcall_info_cache(&fcc); RETURN_THROWS(); } + archive = obj->archive; /* register */ - if (zip_register_cancel_callback_with_state(intern, php_zip_cancel_callback, php_zip_cancel_callback_free, obj)) { + if (zip_register_cancel_callback_with_state(intern, php_zip_cancel_callback, php_zip_cancel_callback_free, archive)) { zend_release_fcall_info_cache(&fcc); RETURN_FALSE; } - zend_fcc_dup(&obj->cancel_callback, &fcc); + zend_fcc_dup(&archive->cancel_callback, &fcc); RETURN_TRUE; } diff --git a/ext/zip/php_zip.h b/ext/zip/php_zip.h index a10b1910f2ad..a67d2042d7ca 100644 --- a/ext/zip/php_zip.h +++ b/ext/zip/php_zip.h @@ -64,17 +64,13 @@ typedef struct _ze_zip_read_rsrc { zend_long zip_rsrc_handle; } zip_read_rsrc; -/* Extends zend object */ -typedef struct _ze_zip_object { +/* Refcounted holder for the native archive state. + * Owned by a ZipArchive object and streams opened from it. */ +typedef struct _php_zip_archive { struct zip *za; - HashTable *prop_handler; - char *filename; - size_t filename_len; + uint32_t refcount; zend_string *out_str; bool from_string; - zip_int64_t last_id; - int err_zip; - int err_sys; bool bailout_callback; #ifdef HAVE_PROGRESS_CALLBACK zend_fcall_info_cache progress_callback; @@ -82,16 +78,37 @@ typedef struct _ze_zip_object { #ifdef HAVE_CANCEL_CALLBACK zend_fcall_info_cache cancel_callback; #endif +} php_zip_archive; + +/* Extends zend object */ +typedef struct _ze_zip_object { + /* NULL when there is no open archive, non-NULL otherwise. + * Owns one ref to the struct. */ + php_zip_archive *archive; + HashTable *prop_handler; + char *filename; + size_t filename_len; + zip_int64_t last_id; + int err_zip; + int err_sys; zend_object zo; } ze_zip_object; #define php_zip_fetch_object(obj) ZEND_CONTAINER_OF(obj, ze_zip_object, zo) +/* The archive an object currently has open, or NULL. */ +static zend_always_inline struct zip *php_zip_object_za(const ze_zip_object *obj) { + return obj->archive ? obj->archive->za : NULL; +} + #define Z_ZIP_P(zv) php_zip_fetch_object(Z_OBJ_P((zv))) php_stream *php_stream_zip_opener(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_DC); php_stream *php_stream_zip_open(ze_zip_object *obj, struct zip_stat *sb, const char *mode, zip_flags_t flags STREAMS_DC); +void php_zip_archive_addref(php_zip_archive *archive); +bool php_zip_archive_release(php_zip_archive *archive); + extern const php_stream_wrapper php_stream_zip_wrapper; zip_source_t * php_zip_create_string_source(zend_string *str, zend_string **dest, zip_error_t *err); diff --git a/ext/zip/tests/gh23276.phpt b/ext/zip/tests/gh23276.phpt new file mode 100644 index 000000000000..ac005cd86354 --- /dev/null +++ b/ext/zip/tests/gh23276.phpt @@ -0,0 +1,95 @@ +--TEST-- +GH-23276 (ZipArchive subclass storing its own stream is collectable) +--CREDITS-- +Eyüp Can Akman +--EXTENSIONS-- +zip +--FILE-- +getStream('entry.txt'); + if (!is_resource($stream)) { + throw new Exception('Failed to open entry stream'); + } + return $stream; +} + +$filename = __DIR__ . '/gh23276.zip'; + +$zip = new Holder; +$zip->open($filename, ZipArchive::CREATE | ZipArchive::OVERWRITE); +$zip->addFromString('entry.txt', 'contents'); +$zip->close(); + +// An archive holding its own stream in a property must be destroyed. +$zip->open($filename, ZipArchive::RDONLY); +$zip->stream = getEntryStream($zip); +$weakRef = WeakReference::create($zip); +unset($zip); +var_dump($weakRef->get()); + +// Same through an indirect edge (property -> array -> resource). +$zip = new Holder; +$zip->open($filename, ZipArchive::RDONLY); +$zip->bag[] = getEntryStream($zip); +$weakRef = WeakReference::create($zip); +unset($zip); +var_dump($weakRef->get()); + +// Two archives cross-holding each other's streams. +$a = new Holder; +$b = new Holder; +$a->open($filename, ZipArchive::RDONLY); +$b->open($filename, ZipArchive::RDONLY); +$a->stream = getEntryStream($b); +$b->stream = getEntryStream($a); +$weakRef = WeakReference::create($a); +unset($a, $b); +var_dump($weakRef->get()); + +// A resurrected object must retain a usable stream. +$zip = new ResurrectingHolder; +$zip->open($filename, ZipArchive::RDONLY); +$zip->stream = getEntryStream($zip); +unset($zip); +var_dump($resurrected instanceof ResurrectingHolder); +var_dump(stream_get_contents($resurrected->stream)); +fclose($resurrected->stream); +unset($resurrected); + +// Externally held streams no longer keep the object alive. Closing one stream +// must not close the archive while another stream still uses it. +$zip = new Holder; +$zip->open($filename, ZipArchive::RDONLY); +$stream1 = getEntryStream($zip); +$stream2 = getEntryStream($zip); +$weakRef = WeakReference::create($zip); +unset($zip); +var_dump($weakRef->get()); +fclose($stream1); +var_dump(stream_get_contents($stream2)); +fclose($stream2); +?> +--CLEAN-- + +--EXPECT-- +NULL +NULL +NULL +bool(true) +string(8) "contents" +NULL +string(8) "contents" diff --git a/ext/zip/tests/gh23276_cancel_callback.phpt b/ext/zip/tests/gh23276_cancel_callback.phpt new file mode 100644 index 000000000000..5aa9057e6f39 --- /dev/null +++ b/ext/zip/tests/gh23276_cancel_callback.phpt @@ -0,0 +1,49 @@ +--TEST-- +GH-23276 (ZipArchive cancel callback outlives the object while a stream holds the archive) +--EXTENSIONS-- +zip +--SKIPIF-- + +--FILE-- +open($filename, ZipArchive::CREATE | ZipArchive::OVERWRITE); +$zip->addFromString('entry.txt', 'contents'); +$zip->close(); + +$zip->open($filename); +$callbackState = new stdClass; +$callbackStateRef = WeakReference::create($callbackState); +var_dump($zip->registerCancelCallback( + static function () use ($callbackState): int { return 0; }, +)); +$zip->addFromString('cancel.txt', 'late'); +$stream = $zip->getStream('entry.txt'); +if (!is_resource($stream)) { + throw new Exception('Failed to open entry stream'); +} +$weakRef = WeakReference::create($zip); +unset($callbackState, $zip); + +var_dump($weakRef->get()); +var_dump($callbackStateRef->get() !== null); +var_dump(stream_get_contents($stream)); +fclose($stream); +var_dump($callbackStateRef->get()); +?> +--CLEAN-- + +--EXPECT-- +bool(true) +NULL +bool(true) +string(8) "contents" +NULL diff --git a/ext/zip/tests/gh23276_close_with_open_stream.phpt b/ext/zip/tests/gh23276_close_with_open_stream.phpt new file mode 100644 index 000000000000..e216c95f4eb9 --- /dev/null +++ b/ext/zip/tests/gh23276_close_with_open_stream.phpt @@ -0,0 +1,47 @@ +--TEST-- +GH-23276 (ZipArchive dropping its archive while a stream is open leaves the object collectable) +--EXTENSIONS-- +zip +--FILE-- +open($filename, ZipArchive::CREATE | ZipArchive::OVERWRITE); +$zip->addFromString('entry.txt', 'contents'); +$zip->close(); + +$zip->open($filename, ZipArchive::RDONLY); +$stream = $zip->getStream('entry.txt'); +var_dump($zip->close()); +$weakRef = WeakReference::create($zip); +unset($zip); +var_dump($weakRef->get()); +var_dump(stream_get_contents($stream)); +fclose($stream); + +$zip = new ZipArchive; +$zip->open($filename, ZipArchive::RDONLY); +$stream = $zip->getStream('entry.txt'); +var_dump($zip->open($filename, ZipArchive::RDONLY)); +$weakRef = WeakReference::create($zip); +unset($zip); +var_dump($weakRef->get()); +var_dump(stream_get_contents($stream)); +fclose($stream); +?> +--CLEAN-- + +--EXPECTF-- +bool(true) +NULL + +Warning: stream_get_contents(): Zip stream error: %s in %s on line %d +string(0) "" +bool(true) +NULL + +Warning: stream_get_contents(): Zip stream error: %s in %s on line %d +string(0) "" diff --git a/ext/zip/tests/gh23276_progress_callback.phpt b/ext/zip/tests/gh23276_progress_callback.phpt new file mode 100644 index 000000000000..ff37b6233e5a --- /dev/null +++ b/ext/zip/tests/gh23276_progress_callback.phpt @@ -0,0 +1,50 @@ +--TEST-- +GH-23276 (ZipArchive progress callback outlives the object while a stream holds the archive) +--EXTENSIONS-- +zip +--SKIPIF-- + +--FILE-- +open($filename, ZipArchive::CREATE | ZipArchive::OVERWRITE); +$zip->addFromString('entry.txt', 'contents'); +$zip->close(); + +$zip->open($filename); +$callbackState = new stdClass; +$callbackStateRef = WeakReference::create($callbackState); +var_dump($zip->registerProgressCallback( + 0.5, + static function (float $rate) use ($callbackState): void {}, +)); +$zip->addFromString('progress.txt', 'late'); +$stream = $zip->getStream('entry.txt'); +if (!is_resource($stream)) { + throw new Exception('Failed to open entry stream'); +} +$weakRef = WeakReference::create($zip); +unset($callbackState, $zip); + +var_dump($weakRef->get()); +var_dump($callbackStateRef->get() !== null); +var_dump(stream_get_contents($stream)); +fclose($stream); +var_dump($callbackStateRef->get()); +?> +--CLEAN-- + +--EXPECT-- +bool(true) +NULL +bool(true) +string(8) "contents" +NULL diff --git a/ext/zip/tests/oo_addfromstring_reopen_memory.phpt b/ext/zip/tests/oo_addfromstring_reopen_memory.phpt new file mode 100644 index 000000000000..fb721c2d6273 --- /dev/null +++ b/ext/zip/tests/oo_addfromstring_reopen_memory.phpt @@ -0,0 +1,26 @@ +--TEST-- +ZipArchive::addFromString() buffers are released when the archive is closed +--EXTENSIONS-- +zip +--FILE-- +open($filename, ZipArchive::CREATE | ZipArchive::OVERWRITE); + $zip->addFromString('entry.txt', $blob); + $zip->close(); +} + +var_dump(memory_get_usage() - $start < 1000000); +?> +--CLEAN-- + +--EXPECT-- +bool(true) diff --git a/ext/zip/zip_stream.c b/ext/zip/zip_stream.c index 89c6a46e653d..a6665630e350 100644 --- a/ext/zip/zip_stream.c +++ b/ext/zip/zip_stream.c @@ -32,7 +32,7 @@ struct php_zip_stream_data_t { struct zip_file *zf; size_t cursor; php_stream *stream; - ze_zip_object *owner; + php_zip_archive *archive; }; #define STREAM_DATA_FROM_STREAM() \ @@ -82,6 +82,7 @@ static ssize_t php_zip_ops_write(php_stream *stream, const char *buf, size_t cou static int php_zip_ops_close(php_stream *stream, int close_handle) { STREAM_DATA_FROM_STREAM(); + bool bailout = false; if (close_handle) { if (self->zf) { zip_fclose(self->zf); @@ -94,13 +95,16 @@ static int php_zip_ops_close(php_stream *stream, int close_handle) } } - /* the pinned object ref is tied to self, so release it regardless of close_handle */ - if (self->owner) { - OBJ_RELEASE(&self->owner->zo); - self->owner = NULL; + /* the archive ref is tied to self, so release it regardless of close_handle */ + if (self->archive) { + bailout = php_zip_archive_release(self->archive); + self->archive = NULL; } efree(self); stream->abstract = NULL; + if (bailout) { + zend_bailout(); + } return EOF; } /* }}} */ @@ -236,7 +240,7 @@ const php_stream_ops php_stream_zipio_ops = { /* {{{ php_stream_zip_open */ php_stream *php_stream_zip_open(ze_zip_object *obj, struct zip_stat *sb, const char *mode, zip_flags_t flags STREAMS_DC) { - struct zip *arch = obj->za; + struct zip *arch = php_zip_object_za(obj); struct zip_file *zf = NULL; php_stream *stream = NULL; @@ -255,9 +259,9 @@ php_stream *php_stream_zip_open(ze_zip_object *obj, struct zip_stat *sb, const c self->zf = zf; self->stream = NULL; self->cursor = 0; - /* keep the archive object alive while the stream borrows its zip_t */ - self->owner = obj; - GC_ADDREF(&obj->zo); + /* keep the zip_t alive while the stream borrows it */ + self->archive = obj->archive; + php_zip_archive_addref(self->archive); #if LIBZIP_ATLEAST(1,9,1) if (zip_file_is_seekable(zf) > 0) { stream = php_stream_alloc(&php_stream_zipio_seek_ops, self, NULL, mode); @@ -343,7 +347,7 @@ php_stream *php_stream_zip_opener(php_stream_wrapper *wrapper, self->zf = zf; self->stream = NULL; self->cursor = 0; - self->owner = NULL; + self->archive = NULL; #if LIBZIP_ATLEAST(1,9,1) if (zip_file_is_seekable(zf) > 0) { stream = php_stream_alloc(&php_stream_zipio_seek_ops, self, NULL, mode); diff --git a/run-tests.php b/run-tests.php index ce6bd94db1e0..8728c0d7524b 100755 --- a/run-tests.php +++ b/run-tests.php @@ -3681,6 +3681,7 @@ public function markTestAs( $time = $time ?? $this->getTimer($file_name); $this->record($suite, 'execution_time', $time); + $formatted_time = number_format($time, 4, '.', ''); $escaped_details = htmlspecialchars($details, ENT_QUOTES, 'UTF-8'); $escaped_details = preg_replace_callback('/[\0-\x08\x0B\x0C\x0E-\x1F]/', function ($c) { @@ -3689,7 +3690,7 @@ public function markTestAs( $escaped_message = htmlspecialchars($message, ENT_QUOTES, 'UTF-8'); $escaped_test_name = htmlspecialchars($file_name . ' (' . $test_name . ')', ENT_QUOTES); - $this->rootSuite['files'][$file_name]['xml'] = "\n"; + $this->rootSuite['files'][$file_name]['xml'] = "\n"; if (is_array($type)) { $output_type = $type[0] . 'ED'; @@ -3734,7 +3735,7 @@ private function getTimer(string $file_name) } if (isset($this->rootSuite['files'][$file_name]['total'])) { - return number_format($this->rootSuite['files'][$file_name]['total'], 4); + return $this->rootSuite['files'][$file_name]['total']; } return 0; diff --git a/sapi/cli/php_cli_server.c b/sapi/cli/php_cli_server.c index 007718e37b9c..5df12648ca84 100644 --- a/sapi/cli/php_cli_server.c +++ b/sapi/cli/php_cli_server.c @@ -175,6 +175,7 @@ typedef struct php_cli_server_client { php_http_parser parser; bool request_read; bool too_large_post; + bool headers_written; zend_string *current_header_name; zend_string *current_header_value; enum { HEADER_NONE=0, HEADER_FIELD, HEADER_VALUE } last_header_element; @@ -544,7 +545,7 @@ static int sapi_cli_server_send_headers(sapi_headers_struct *sapi_headers) /* {{ sapi_header_struct *h; zend_llist_position pos; - if (client == NULL || SG(request_info).no_headers) { + if (client == NULL || SG(request_info).no_headers || client->headers_written) { return SAPI_HEADER_SENT_SUCCESSFULLY; } @@ -567,10 +568,12 @@ static int sapi_cli_server_send_headers(sapi_headers_struct *sapi_headers) /* {{ } smart_str_appendl(&buffer, "\r\n", 2); - php_cli_server_client_send_through(client, ZSTR_VAL(buffer.s), ZSTR_LEN(buffer.s)); + size_t buffer_len = ZSTR_LEN(buffer.s); + bool sent = php_cli_server_client_send_through(client, ZSTR_VAL(buffer.s), buffer_len) == buffer_len; + client->headers_written = true; smart_str_free(&buffer); - return SAPI_HEADER_SENT_SUCCESSFULLY; + return sent ? SAPI_HEADER_SENT_SUCCESSFULLY : SAPI_HEADER_SEND_FAILED; } /* }}} */ @@ -1927,11 +1930,11 @@ static size_t php_cli_server_client_send_through(php_cli_server_client *client, } else { /* error or timeout */ php_handle_aborted_connection(); - return nbytes_left; + return str_len - nbytes_left; } } else { php_handle_aborted_connection(); - return nbytes_left; + return str_len - nbytes_left; } } nbytes_left -= nbytes_sent; @@ -1981,6 +1984,7 @@ static void php_cli_server_client_ctor(php_cli_server_client *client, php_cli_se php_http_parser_init(&client->parser, PHP_HTTP_REQUEST); client->request_read = false; client->too_large_post = false; + client->headers_written = false; client->last_header_element = HEADER_NONE; client->current_header_name = NULL; diff --git a/sapi/cli/tests/gh23425.phpt b/sapi/cli/tests/gh23425.phpt new file mode 100644 index 000000000000..98ed07859174 --- /dev/null +++ b/sapi/cli/tests/gh23425.phpt @@ -0,0 +1,40 @@ +--TEST-- +GH-23425 (sapi_cli_server_send_headers() does not check the return value of php_cli_server_client_send_through()) +--EXTENSIONS-- +sockets +--SKIPIF-- + +--FILE-- + 1, 'l_linger' => 0]); +socket_close($sock); + +$result_file = $info->docRoot . '/result.txt'; +for ($i = 0; $i < 40 && !file_exists($result_file); $i++) { + usleep(50000); +} + +echo file_get_contents($result_file), "\n"; +?> +--EXPECT-- +not-sent