Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,12 @@ PHP NEWS
- XML:
. The xml_parser_free() function has been deprecated. (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:
Expand Down
3 changes: 3 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,9 @@ PHP 8.5 UPGRADE NOTES
the same (just to give one example: one may be cm and the other may be px).
. setcookie() and setrawcookie() now support the "partitioned" key.
RFC: https://wiki.php.net/rfc/CHIPS
. 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).

- XSL:
. The $namespace argument of XSLTProcessor::getParameter(),
Expand Down
82 changes: 72 additions & 10 deletions ext/standard/pack.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;

Expand All @@ -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;

Expand All @@ -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

Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
}

Expand All @@ -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;
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -793,6 +813,8 @@ PHP_FUNCTION(unpack)
case 'S':
case 'n':
case 'v':
case 'm':
case 'y':
size = 2;
break;

Expand All @@ -807,6 +829,8 @@ PHP_FUNCTION(unpack)
case 'L':
case 'N':
case 'V':
case 'M':
case 'Y':
size = 4;
break;

Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 */
Expand All @@ -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 */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Loading