Skip to content

Fix GH-17068: Add pack()/unpack() format for signed integer endianness #19368

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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 @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -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.
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