Commit 02229e2337a for php.net
commit 02229e2337aa6188d2eaa01d586b33a69fd39218
Author: Alexandre Daubois <2144837+alexandre-daubois@users.noreply.github.com>
Date: Mon Aug 24 17:21:03 2026 +0200
standard: Add support for endianness modifier in pack()/unpack() format codes (#21074)
RFC (integer format codes): https://wiki.php.net/rfc/pack-unpack-endianness-signed-integers-support
RFC (float format codes): https://wiki.php.net/rfc/pack-unpack-float-endianness-modifier
diff --git a/NEWS b/NEWS
index ccc3291c018..022d4897360 100644
--- a/NEWS
+++ b/NEWS
@@ -110,6 +110,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 14cea21664c..ad76c2c2e29 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/ext/standard/pack.c b/ext/standard/pack.c
index 1ab5d4858cb..1da93228f71 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 <formatlen> 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 00000000000..0ab0a4b2126
--- /dev/null
+++ b/ext/standard/tests/strings/pack_endian_modifiers.phpt
@@ -0,0 +1,182 @@
+--TEST--
+pack()/unpack() endianness modifiers
+--FILE--
+<?php
+
+var_dump(bin2hex(pack("s<", 0x0102)));
+var_dump(bin2hex(pack("S<", 0x0102)));
+
+var_dump(bin2hex(pack("s>", 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("s<value/L>count", "\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 00000000000..13c2a3a7302
--- /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--
+<?php
+if (PHP_INT_SIZE > 4) die("skip 32bit test only");
+?>
+--FILE--
+<?php
+
+$formats = ['q<', 'q>', '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 00000000000..61a8f3f95f1
--- /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--
+<?php
+if (PHP_INT_SIZE < 8) die("skip 64bit test only");
+?>
+--FILE--
+<?php
+
+var_dump(bin2hex(pack("q<", 0x0102030405060708)));
+var_dump(bin2hex(pack("Q<", 0x0102030405060708)));
+
+var_dump(bin2hex(pack("q>", 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 00000000000..685d21c7c00
--- /dev/null
+++ b/ext/standard/tests/strings/pack_endian_modifiers_error.phpt
@@ -0,0 +1,105 @@
+--TEST--
+pack()/unpack() endianness modifiers, invalid combinations
+--FILE--
+<?php
+
+$inherent_formats = ['n<', 'v>', '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 '@'