diff --git a/NEWS b/NEWS index 380c7aba24c09..9ecf5e1a46424 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,12 @@ PHP NEWS . Fixed bug GH-17927 (Reflection: have some indication of property hooks in `_property_string()`). (DanielEScherzer) +- Standard: + . Added pack()/unpack() support for signed integers with specific endianness. + New format codes: m/y for signed 2-byte (little/big endian), M/Y for signed + 4-byte (little/big endian), p/j for signed 8-byte (little/big endian). + Fixes GH-17068. (alexandre-daubois) + 31 Jul 2025, PHP 8.5.0alpha4 - Core: diff --git a/UPGRADING b/UPGRADING index 65e164fe01a07..4da3d00051858 100644 --- a/UPGRADING +++ b/UPGRADING @@ -263,6 +263,9 @@ PHP 8.5 UPGRADE NOTES and the function returns false. Previously, these errors were silently ignored. This change affects only the sendmail transport. . getimagesize() now supports HEIF/HEIC images. + . pack() and unpack() now support signed integers with specific endianness. + New format codes: m/y for signed 2-byte (little/big endian), M/Y for signed + 4-byte (little/big endian), p/j for signed 8-byte (little/big endian). - Standard: . getimagesize() now supports SVG images when ext-libxml is also loaded. diff --git a/ext/standard/pack.c b/ext/standard/pack.c index 55da64897a2e7..486eee6cda2d8 100644 --- a/ext/standard/pack.c +++ b/ext/standard/pack.c @@ -196,6 +196,7 @@ static double php_pack_parse_double(int is_little_endian, void * src) /* pack() idea stolen from Perl (implemented formats behave the same as there except J and P) * Implemented formats are Z, A, a, h, H, c, C, s, S, i, I, l, L, n, N, q, Q, J, P, f, d, x, X, @. * Added g, G for little endian float and big endian float, added e, E for little endian double and big endian double. + * Added m, y for little/big endian signed 2-byte, M, Y for little/big endian signed 4-byte, p, j for little/big endian signed 8-byte. */ /* {{{ Takes one or more arguments and packs them into a binary string according to the format argument */ PHP_FUNCTION(pack) @@ -293,6 +294,8 @@ PHP_FUNCTION(pack) case 'Q': case 'J': case 'P': + case 'j': + case 'p': #if SIZEOF_ZEND_LONG < 8 efree(formatcodes); efree(formatargs); @@ -317,6 +320,10 @@ PHP_FUNCTION(pack) case 'd': /* double */ case 'e': /* little endian double */ case 'E': /* big endian double */ + case 'm': /* little endian signed 2-byte */ + case 'y': /* big endian signed 2-byte */ + case 'M': /* little endian signed 4-byte */ + case 'Y': /* big endian signed 4-byte */ if (arg < 0) { arg = num_args - currentarg; } @@ -373,6 +380,8 @@ PHP_FUNCTION(pack) case 'S': case 'n': case 'v': + case 'm': /* little endian signed 2-byte */ + case 'y': /* big endian signed 2-byte */ INC_OUTPUTPOS(arg,2) /* 16 bit per arg */ break; @@ -385,6 +394,8 @@ PHP_FUNCTION(pack) case 'L': case 'N': case 'V': + case 'M': /* little endian signed 4-byte */ + case 'Y': /* big endian signed 4-byte */ INC_OUTPUTPOS(arg,4) /* 32 bit per arg */ break; @@ -393,7 +404,9 @@ PHP_FUNCTION(pack) case 'Q': case 'J': case 'P': - INC_OUTPUTPOS(arg,8) /* 32 bit per arg */ + case 'j': + case 'p': + INC_OUTPUTPOS(arg,8) /* 64 bit per arg */ break; #endif @@ -508,12 +521,14 @@ PHP_FUNCTION(pack) case 's': case 'S': case 'n': - case 'v': { + case 'v': + case 'm': + case 'y': { php_pack_endianness endianness = PHP_MACHINE_ENDIAN; - if (code == 'n') { + if (code == 'n' || code == 'y') { endianness = PHP_BIG_ENDIAN; - } else if (code == 'v') { + } else if (code == 'v' || code == 'm') { endianness = PHP_LITTLE_ENDIAN; } @@ -535,12 +550,14 @@ PHP_FUNCTION(pack) case 'l': case 'L': case 'N': - case 'V': { + case 'V': + case 'M': + case 'Y': { php_pack_endianness endianness = PHP_MACHINE_ENDIAN; - if (code == 'N') { + if (code == 'N' || code == 'Y') { endianness = PHP_BIG_ENDIAN; - } else if (code == 'V') { + } else if (code == 'V' || code == 'M') { endianness = PHP_LITTLE_ENDIAN; } @@ -555,12 +572,14 @@ PHP_FUNCTION(pack) case 'q': case 'Q': case 'J': - case 'P': { + case 'P': + case 'j': + case 'p': { php_pack_endianness endianness = PHP_MACHINE_ENDIAN; - if (code == 'J') { + if (code == 'J' || code == 'j') { endianness = PHP_BIG_ENDIAN; - } else if (code == 'P') { + } else if (code == 'P' || code == 'p') { endianness = PHP_LITTLE_ENDIAN; } @@ -672,6 +691,7 @@ PHP_FUNCTION(pack) * f and d will return doubles. * Implemented formats are Z, A, a, h, H, c, C, s, S, i, I, l, L, n, N, q, Q, J, P, f, d, x, X, @. * Added g, G for little endian float and big endian float, added e, E for little endian double and big endian double. + * Added m, y for little/big endian signed 2-byte, M, Y for little/big endian signed 4-byte, p, j for little/big endian signed 8-byte. */ /* {{{ Unpack binary string into named array elements according to format argument */ PHP_FUNCTION(unpack) @@ -793,6 +813,8 @@ PHP_FUNCTION(unpack) case 'S': case 'n': case 'v': + case 'm': + case 'y': size = 2; break; @@ -807,6 +829,8 @@ PHP_FUNCTION(unpack) case 'L': case 'N': case 'V': + case 'M': + case 'Y': size = 4; break; @@ -815,6 +839,8 @@ PHP_FUNCTION(unpack) case 'Q': case 'J': case 'P': + case 'p': + case 'j': #if SIZEOF_ZEND_LONG > 4 size = 8; break; @@ -1017,6 +1043,18 @@ PHP_FUNCTION(unpack) break; } + case 'm': /* signed little endian 2-byte */ + case 'y': { /* signed big endian 2-byte */ + uint16_t x = *((unaligned_uint16_t*) &input[inputpos]); + + if ((type == 'y' && MACHINE_LITTLE_ENDIAN) || (type == 'm' && !MACHINE_LITTLE_ENDIAN)) { + x = php_pack_reverse_int16(x); + } + + ZVAL_LONG(&val, (int16_t) x); + break; + } + case 'i': /* signed integer, machine size, machine endian */ case 'I': { /* unsigned integer, machine size, machine endian */ zend_long v; @@ -1051,6 +1089,18 @@ PHP_FUNCTION(unpack) break; } + case 'M': /* signed little endian 4-byte */ + case 'Y': { /* signed big endian 4-byte */ + uint32_t x = *((unaligned_uint32_t*) &input[inputpos]); + + if ((type == 'Y' && MACHINE_LITTLE_ENDIAN) || (type == 'M' && !MACHINE_LITTLE_ENDIAN)) { + x = php_pack_reverse_int32(x); + } + + ZVAL_LONG(&val, (int32_t) x); + break; + } + #if SIZEOF_ZEND_LONG > 4 case 'q': /* signed machine endian */ case 'Q': /* unsigned machine endian */ @@ -1070,6 +1120,18 @@ PHP_FUNCTION(unpack) ZVAL_LONG(&val, v); break; } + + case 'j': /* signed big endian */ + case 'p': { /* signed little endian */ + uint64_t x = *((unaligned_uint64_t*) &input[inputpos]); + + if ((type == 'j' && MACHINE_LITTLE_ENDIAN) || (type == 'p' && !MACHINE_LITTLE_ENDIAN)) { + x = php_pack_reverse_int64(x); + } + + ZVAL_LONG(&val, (int64_t) x); + break; + } #endif case 'f': /* float */ diff --git a/ext/standard/tests/strings/pack.phpt b/ext/standard/tests/pack/pack.phpt similarity index 100% rename from ext/standard/tests/strings/pack.phpt rename to ext/standard/tests/pack/pack.phpt diff --git a/ext/standard/tests/strings/pack64.phpt b/ext/standard/tests/pack/pack64.phpt similarity index 100% rename from ext/standard/tests/strings/pack64.phpt rename to ext/standard/tests/pack/pack64.phpt diff --git a/ext/standard/tests/strings/pack64_32.phpt b/ext/standard/tests/pack/pack64_32.phpt similarity index 68% rename from ext/standard/tests/strings/pack64_32.phpt rename to ext/standard/tests/pack/pack64_32.phpt index 10a7b94b9c425..5b55a92737a1e 100644 --- a/ext/standard/tests/strings/pack64_32.phpt +++ b/ext/standard/tests/pack/pack64_32.phpt @@ -50,6 +50,28 @@ try { echo $e->getMessage(), "\n"; } +try { + var_dump(pack("p", 0)); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump(pack("j", 0)); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +try { + var_dump(unpack("p", '')); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump(unpack("j", '')); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + ?> --EXPECT-- 64-bit format codes are not available for 32-bit versions of PHP @@ -60,3 +82,7 @@ try { 64-bit format codes are not available for 32-bit versions of PHP 64-bit format codes are not available for 32-bit versions of PHP 64-bit format codes are not available for 32-bit versions of PHP +64-bit format codes are not available for 32-bit versions of PHP +64-bit format codes are not available for 32-bit versions of PHP +64-bit format codes are not available for 32-bit versions of PHP +64-bit format codes are not available for 32-bit versions of PHP diff --git a/ext/standard/tests/strings/pack_A.phpt b/ext/standard/tests/pack/pack_A.phpt similarity index 100% rename from ext/standard/tests/strings/pack_A.phpt rename to ext/standard/tests/pack/pack_A.phpt diff --git a/ext/standard/tests/strings/pack_Z.phpt b/ext/standard/tests/pack/pack_Z.phpt similarity index 100% rename from ext/standard/tests/strings/pack_Z.phpt rename to ext/standard/tests/pack/pack_Z.phpt diff --git a/ext/standard/tests/strings/pack_arrays.phpt b/ext/standard/tests/pack/pack_arrays.phpt similarity index 100% rename from ext/standard/tests/strings/pack_arrays.phpt rename to ext/standard/tests/pack/pack_arrays.phpt diff --git a/ext/standard/tests/strings/pack_float.phpt b/ext/standard/tests/pack/pack_float.phpt similarity index 100% rename from ext/standard/tests/strings/pack_float.phpt rename to ext/standard/tests/pack/pack_float.phpt diff --git a/ext/standard/tests/pack/pack_signed_int_endian_32.phpt b/ext/standard/tests/pack/pack_signed_int_endian_32.phpt new file mode 100644 index 0000000000000..217adac8efe58 --- /dev/null +++ b/ext/standard/tests/pack/pack_signed_int_endian_32.phpt @@ -0,0 +1,198 @@ +--TEST-- +pack()/unpack(): signed integer endianness tests (32-bit) +--SKIPIF-- + 4) { + die("skip 32bit test only"); +} +?> +--FILE-- +getMessage(), "\n"; +} + +try { + pack("j", 0); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +try { + unpack("p", hex2bin('0000000000000000')); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +try { + unpack("j", hex2bin('0000000000000000')); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +string(4) "0000" +string(4) "0100" +string(4) "ffff" +string(4) "ff7f" +string(4) "0080" +string(4) "0000" +string(4) "0001" +string(4) "ffff" +string(4) "7fff" +string(4) "8000" +string(8) "00000000" +string(8) "01000000" +string(8) "ffffffff" +string(8) "ffffff7f" +string(8) "00000080" +string(8) "00000000" +string(8) "00000001" +string(8) "ffffffff" +string(8) "7fffffff" +string(8) "80000000" +array(1) { + [1]=> + int(0) +} +array(1) { + [1]=> + int(1) +} +array(1) { + [1]=> + int(-1) +} +array(1) { + [1]=> + int(32767) +} +array(1) { + [1]=> + int(-32768) +} +array(1) { + [1]=> + int(0) +} +array(1) { + [1]=> + int(1) +} +array(1) { + [1]=> + int(-1) +} +array(1) { + [1]=> + int(32767) +} +array(1) { + [1]=> + int(-32768) +} +array(1) { + [1]=> + int(0) +} +array(1) { + [1]=> + int(1) +} +array(1) { + [1]=> + int(-1) +} +array(1) { + [1]=> + int(2147483647) +} +array(1) { + [1]=> + int(-2147483648) +} +array(1) { + [1]=> + int(0) +} +array(1) { + [1]=> + int(1) +} +array(1) { + [1]=> + int(-1) +} +array(1) { + [1]=> + int(2147483647) +} +array(1) { + [1]=> + int(-2147483648) +} +64-bit format codes are not available for 32-bit versions of PHP +64-bit format codes are not available for 32-bit versions of PHP +64-bit format codes are not available for 32-bit versions of PHP +64-bit format codes are not available for 32-bit versions of PHP diff --git a/ext/standard/tests/pack/pack_signed_int_endian_64.phpt b/ext/standard/tests/pack/pack_signed_int_endian_64.phpt new file mode 100644 index 0000000000000..f323785b2fe0b --- /dev/null +++ b/ext/standard/tests/pack/pack_signed_int_endian_64.phpt @@ -0,0 +1,219 @@ +--TEST-- +pack()/unpack(): signed integer endianness tests (64-bit) +--SKIPIF-- + +--FILE-- + +--EXPECT-- +string(4) "0000" +string(4) "0100" +string(4) "ffff" +string(4) "ff7f" +string(4) "0080" +string(4) "0000" +string(4) "0001" +string(4) "ffff" +string(4) "7fff" +string(4) "8000" +string(8) "00000000" +string(8) "01000000" +string(8) "ffffffff" +string(8) "ffffff7f" +string(8) "00000080" +string(8) "00000000" +string(8) "00000001" +string(8) "ffffffff" +string(8) "7fffffff" +string(8) "80000000" +string(16) "0000000000000000" +string(16) "0100000000000000" +string(16) "ffffffffffffffff" +string(16) "0000000000000000" +string(16) "0000000000000001" +string(16) "ffffffffffffffff" +array(1) { + [1]=> + int(0) +} +array(1) { + [1]=> + int(1) +} +array(1) { + [1]=> + int(-1) +} +array(1) { + [1]=> + int(32767) +} +array(1) { + [1]=> + int(-32768) +} +array(1) { + [1]=> + int(0) +} +array(1) { + [1]=> + int(1) +} +array(1) { + [1]=> + int(-1) +} +array(1) { + [1]=> + int(32767) +} +array(1) { + [1]=> + int(-32768) +} +array(1) { + [1]=> + int(0) +} +array(1) { + [1]=> + int(1) +} +array(1) { + [1]=> + int(-1) +} +array(1) { + [1]=> + int(2147483647) +} +array(1) { + [1]=> + int(-2147483648) +} +array(1) { + [1]=> + int(0) +} +array(1) { + [1]=> + int(1) +} +array(1) { + [1]=> + int(-1) +} +array(1) { + [1]=> + int(2147483647) +} +array(1) { + [1]=> + int(-2147483648) +} +array(1) { + [1]=> + int(0) +} +array(1) { + [1]=> + int(1) +} +array(1) { + [1]=> + int(-1) +} +array(1) { + [1]=> + int(0) +} +array(1) { + [1]=> + int(1) +} +array(1) { + [1]=> + int(-1) +}