Skip to content

Commit bc83418

Browse files
Fix GH-17068: Add pack()/unpack() format for signed integer endianness
1 parent 9aa8e88 commit bc83418

File tree

12 files changed

+524
-10
lines changed

12 files changed

+524
-10
lines changed

NEWS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ PHP NEWS
66
. Fixed bug GH-17927 (Reflection: have some indication of property hooks in
77
`_property_string()`). (DanielEScherzer)
88

9+
- Standard:
10+
. Added pack()/unpack() support for signed integers with specific endianness.
11+
New format codes: m/y for signed 2-byte (little/big endian), M/Y for signed
12+
4-byte (little/big endian), p/j for signed 8-byte (little/big endian).
13+
Fixes GH-17068. (alexandre-daubois)
14+
915
31 Jul 2025, PHP 8.5.0alpha4
1016

1117
- Core:

UPGRADING

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,9 @@ PHP 8.5 UPGRADE NOTES
263263
and the function returns false. Previously, these errors were silently
264264
ignored. This change affects only the sendmail transport.
265265
. getimagesize() now supports HEIF/HEIC images.
266+
. pack() and unpack() now support signed integers with specific endianness.
267+
New format codes: m/y for signed 2-byte (little/big endian), M/Y for signed
268+
4-byte (little/big endian), p/j for signed 8-byte (little/big endian).
266269

267270
- Standard:
268271
. getimagesize() now supports SVG images when ext-libxml is also loaded.

ext/standard/pack.c

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ static double php_pack_parse_double(int is_little_endian, void * src)
196196
/* pack() idea stolen from Perl (implemented formats behave the same as there except J and P)
197197
* 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, @.
198198
* Added g, G for little endian float and big endian float, added e, E for little endian double and big endian double.
199+
* 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.
199200
*/
200201
/* {{{ Takes one or more arguments and packs them into a binary string according to the format argument */
201202
PHP_FUNCTION(pack)
@@ -293,6 +294,8 @@ PHP_FUNCTION(pack)
293294
case 'Q':
294295
case 'J':
295296
case 'P':
297+
case 'j':
298+
case 'p':
296299
#if SIZEOF_ZEND_LONG < 8
297300
efree(formatcodes);
298301
efree(formatargs);
@@ -317,6 +320,10 @@ PHP_FUNCTION(pack)
317320
case 'd': /* double */
318321
case 'e': /* little endian double */
319322
case 'E': /* big endian double */
323+
case 'm': /* little endian signed 2-byte */
324+
case 'y': /* big endian signed 2-byte */
325+
case 'M': /* little endian signed 4-byte */
326+
case 'Y': /* big endian signed 4-byte */
320327
if (arg < 0) {
321328
arg = num_args - currentarg;
322329
}
@@ -373,6 +380,8 @@ PHP_FUNCTION(pack)
373380
case 'S':
374381
case 'n':
375382
case 'v':
383+
case 'm': /* little endian signed 2-byte */
384+
case 'y': /* big endian signed 2-byte */
376385
INC_OUTPUTPOS(arg,2) /* 16 bit per arg */
377386
break;
378387

@@ -385,6 +394,8 @@ PHP_FUNCTION(pack)
385394
case 'L':
386395
case 'N':
387396
case 'V':
397+
case 'M': /* little endian signed 4-byte */
398+
case 'Y': /* big endian signed 4-byte */
388399
INC_OUTPUTPOS(arg,4) /* 32 bit per arg */
389400
break;
390401

@@ -393,7 +404,9 @@ PHP_FUNCTION(pack)
393404
case 'Q':
394405
case 'J':
395406
case 'P':
396-
INC_OUTPUTPOS(arg,8) /* 32 bit per arg */
407+
case 'j':
408+
case 'p':
409+
INC_OUTPUTPOS(arg,8) /* 64 bit per arg */
397410
break;
398411
#endif
399412

@@ -508,12 +521,14 @@ PHP_FUNCTION(pack)
508521
case 's':
509522
case 'S':
510523
case 'n':
511-
case 'v': {
524+
case 'v':
525+
case 'm':
526+
case 'y': {
512527
php_pack_endianness endianness = PHP_MACHINE_ENDIAN;
513528

514-
if (code == 'n') {
529+
if (code == 'n' || code == 'y') {
515530
endianness = PHP_BIG_ENDIAN;
516-
} else if (code == 'v') {
531+
} else if (code == 'v' || code == 'm') {
517532
endianness = PHP_LITTLE_ENDIAN;
518533
}
519534

@@ -535,12 +550,14 @@ PHP_FUNCTION(pack)
535550
case 'l':
536551
case 'L':
537552
case 'N':
538-
case 'V': {
553+
case 'V':
554+
case 'M':
555+
case 'Y': {
539556
php_pack_endianness endianness = PHP_MACHINE_ENDIAN;
540557

541-
if (code == 'N') {
558+
if (code == 'N' || code == 'Y') {
542559
endianness = PHP_BIG_ENDIAN;
543-
} else if (code == 'V') {
560+
} else if (code == 'V' || code == 'M') {
544561
endianness = PHP_LITTLE_ENDIAN;
545562
}
546563

@@ -555,12 +572,14 @@ PHP_FUNCTION(pack)
555572
case 'q':
556573
case 'Q':
557574
case 'J':
558-
case 'P': {
575+
case 'P':
576+
case 'j':
577+
case 'p': {
559578
php_pack_endianness endianness = PHP_MACHINE_ENDIAN;
560579

561-
if (code == 'J') {
580+
if (code == 'J' || code == 'j') {
562581
endianness = PHP_BIG_ENDIAN;
563-
} else if (code == 'P') {
582+
} else if (code == 'P' || code == 'p') {
564583
endianness = PHP_LITTLE_ENDIAN;
565584
}
566585

@@ -672,6 +691,7 @@ PHP_FUNCTION(pack)
672691
* f and d will return doubles.
673692
* 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, @.
674693
* Added g, G for little endian float and big endian float, added e, E for little endian double and big endian double.
694+
* 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.
675695
*/
676696
/* {{{ Unpack binary string into named array elements according to format argument */
677697
PHP_FUNCTION(unpack)
@@ -793,6 +813,8 @@ PHP_FUNCTION(unpack)
793813
case 'S':
794814
case 'n':
795815
case 'v':
816+
case 'm':
817+
case 'y':
796818
size = 2;
797819
break;
798820

@@ -807,6 +829,8 @@ PHP_FUNCTION(unpack)
807829
case 'L':
808830
case 'N':
809831
case 'V':
832+
case 'M':
833+
case 'Y':
810834
size = 4;
811835
break;
812836

@@ -815,6 +839,8 @@ PHP_FUNCTION(unpack)
815839
case 'Q':
816840
case 'J':
817841
case 'P':
842+
case 'p':
843+
case 'j':
818844
#if SIZEOF_ZEND_LONG > 4
819845
size = 8;
820846
break;
@@ -1017,6 +1043,18 @@ PHP_FUNCTION(unpack)
10171043
break;
10181044
}
10191045

1046+
case 'm': /* signed little endian 2-byte */
1047+
case 'y': { /* signed big endian 2-byte */
1048+
uint16_t x = *((unaligned_uint16_t*) &input[inputpos]);
1049+
1050+
if ((type == 'y' && MACHINE_LITTLE_ENDIAN) || (type == 'm' && !MACHINE_LITTLE_ENDIAN)) {
1051+
x = php_pack_reverse_int16(x);
1052+
}
1053+
1054+
ZVAL_LONG(&val, (int16_t) x);
1055+
break;
1056+
}
1057+
10201058
case 'i': /* signed integer, machine size, machine endian */
10211059
case 'I': { /* unsigned integer, machine size, machine endian */
10221060
zend_long v;
@@ -1051,6 +1089,18 @@ PHP_FUNCTION(unpack)
10511089
break;
10521090
}
10531091

1092+
case 'M': /* signed little endian 4-byte */
1093+
case 'Y': { /* signed big endian 4-byte */
1094+
uint32_t x = *((unaligned_uint32_t*) &input[inputpos]);
1095+
1096+
if ((type == 'Y' && MACHINE_LITTLE_ENDIAN) || (type == 'M' && !MACHINE_LITTLE_ENDIAN)) {
1097+
x = php_pack_reverse_int32(x);
1098+
}
1099+
1100+
ZVAL_LONG(&val, (int32_t) x);
1101+
break;
1102+
}
1103+
10541104
#if SIZEOF_ZEND_LONG > 4
10551105
case 'q': /* signed machine endian */
10561106
case 'Q': /* unsigned machine endian */
@@ -1070,6 +1120,18 @@ PHP_FUNCTION(unpack)
10701120
ZVAL_LONG(&val, v);
10711121
break;
10721122
}
1123+
1124+
case 'j': /* signed big endian */
1125+
case 'p': { /* signed little endian */
1126+
uint64_t x = *((unaligned_uint64_t*) &input[inputpos]);
1127+
1128+
if ((type == 'j' && MACHINE_LITTLE_ENDIAN) || (type == 'p' && !MACHINE_LITTLE_ENDIAN)) {
1129+
x = php_pack_reverse_int64(x);
1130+
}
1131+
1132+
ZVAL_LONG(&val, (int64_t) x);
1133+
break;
1134+
}
10731135
#endif
10741136

10751137
case 'f': /* float */

ext/standard/tests/strings/pack64_32.phpt renamed to ext/standard/tests/pack/pack64_32.phpt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,28 @@ try {
5050
echo $e->getMessage(), "\n";
5151
}
5252

53+
try {
54+
var_dump(pack("p", 0));
55+
} catch (ValueError $e) {
56+
echo $e->getMessage(), "\n";
57+
}
58+
try {
59+
var_dump(pack("j", 0));
60+
} catch (ValueError $e) {
61+
echo $e->getMessage(), "\n";
62+
}
63+
64+
try {
65+
var_dump(unpack("p", ''));
66+
} catch (ValueError $e) {
67+
echo $e->getMessage(), "\n";
68+
}
69+
try {
70+
var_dump(unpack("j", ''));
71+
} catch (ValueError $e) {
72+
echo $e->getMessage(), "\n";
73+
}
74+
5375
?>
5476
--EXPECT--
5577
64-bit format codes are not available for 32-bit versions of PHP
@@ -60,3 +82,7 @@ try {
6082
64-bit format codes are not available for 32-bit versions of PHP
6183
64-bit format codes are not available for 32-bit versions of PHP
6284
64-bit format codes are not available for 32-bit versions of PHP
85+
64-bit format codes are not available for 32-bit versions of PHP
86+
64-bit format codes are not available for 32-bit versions of PHP
87+
64-bit format codes are not available for 32-bit versions of PHP
88+
64-bit format codes are not available for 32-bit versions of PHP

0 commit comments

Comments
 (0)