Coverage Report

Created: 2025-06-13 06:43

/src/php-src/ext/standard/pack.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
   +----------------------------------------------------------------------+
3
   | Copyright (c) The PHP Group                                          |
4
   +----------------------------------------------------------------------+
5
   | This source file is subject to version 3.01 of the PHP license,      |
6
   | that is bundled with this package in the file LICENSE, and is        |
7
   | available through the world-wide-web at the following url:           |
8
   | https://www.php.net/license/3_01.txt                                 |
9
   | If you did not receive a copy of the PHP license and are unable to   |
10
   | obtain it through the world-wide-web, please send a note to          |
11
   | license@php.net so we can mail you a copy immediately.               |
12
   +----------------------------------------------------------------------+
13
   | Author: Chris Schneider <cschneid@relog.ch>                          |
14
   +----------------------------------------------------------------------+
15
 */
16
17
#include "php.h"
18
19
#include <stdlib.h>
20
#include <errno.h>
21
#include <sys/types.h>
22
#include "pack.h"
23
24
#define INC_OUTPUTPOS(a,b) \
25
0
  if ((a) < 0 || ((INT_MAX - outputpos)/((int)b)) < (a)) { \
26
0
    efree(formatcodes);  \
27
0
    efree(formatargs); \
28
0
    zend_value_error("Type %c: integer overflow in format string", code); \
29
0
    RETURN_THROWS(); \
30
0
  } \
31
0
  outputpos += (a)*(b);
32
33
#ifdef WORDS_BIGENDIAN
34
#define MACHINE_LITTLE_ENDIAN 0
35
#else
36
16
#define MACHINE_LITTLE_ENDIAN 1
37
#endif
38
39
typedef ZEND_SET_ALIGNED(1, uint16_t unaligned_uint16_t);
40
typedef ZEND_SET_ALIGNED(1, uint32_t unaligned_uint32_t);
41
typedef ZEND_SET_ALIGNED(1, uint64_t unaligned_uint64_t);
42
typedef ZEND_SET_ALIGNED(1, unsigned int unaligned_uint);
43
typedef ZEND_SET_ALIGNED(1, int unaligned_int);
44
45
/* Mapping of byte from char (8bit) to long for machine endian */
46
static int byte_map[1];
47
48
/* Mappings of bytes from int (machine dependent) to int for machine endian */
49
static int int_map[sizeof(int)];
50
51
/* Mappings of bytes from shorts (16bit) for all endian environments */
52
static int machine_endian_short_map[2];
53
static int big_endian_short_map[2];
54
static int little_endian_short_map[2];
55
56
/* Mappings of bytes from longs (32bit) for all endian environments */
57
static int machine_endian_long_map[4];
58
static int big_endian_long_map[4];
59
static int little_endian_long_map[4];
60
61
#if SIZEOF_ZEND_LONG > 4
62
/* Mappings of bytes from quads (64bit) for all endian environments */
63
static int machine_endian_longlong_map[8];
64
static int big_endian_longlong_map[8];
65
static int little_endian_longlong_map[8];
66
#endif
67
68
/* {{{ php_pack */
69
static void php_pack(zval *val, size_t size, int *map, char *output)
70
0
{
71
0
  size_t i;
72
0
  char *v;
73
74
0
  convert_to_long(val);
75
0
  v = (char *) &Z_LVAL_P(val);
76
77
0
  for (i = 0; i < size; i++) {
78
0
    *output++ = v[map[i]];
79
0
  }
80
0
}
81
/* }}} */
82
83
ZEND_ATTRIBUTE_CONST static inline uint16_t php_pack_reverse_int16(uint16_t arg)
84
0
{
85
0
  return ((arg & 0xFF) << 8) | ((arg >> 8) & 0xFF);
86
0
}
87
88
/* {{{ php_pack_reverse_int32 */
89
ZEND_ATTRIBUTE_CONST static inline uint32_t php_pack_reverse_int32(uint32_t arg)
90
0
{
91
0
  uint32_t result;
92
0
  result = ((arg & 0xFF) << 24) | ((arg & 0xFF00) << 8) | ((arg >> 8) & 0xFF00) | ((arg >> 24) & 0xFF);
93
94
0
  return result;
95
0
}
96
/* }}} */
97
98
/* {{{ php_pack */
99
static inline uint64_t php_pack_reverse_int64(uint64_t arg)
100
0
{
101
0
  union Swap64 {
102
0
    uint64_t i;
103
0
    uint32_t ul[2];
104
0
  } tmp, result;
105
0
  tmp.i = arg;
106
0
  result.ul[0] = php_pack_reverse_int32(tmp.ul[1]);
107
0
  result.ul[1] = php_pack_reverse_int32(tmp.ul[0]);
108
109
0
  return result.i;
110
0
}
111
/* }}} */
112
113
/* {{{ php_pack_copy_float */
114
static void php_pack_copy_float(int is_little_endian, void * dst, float f)
115
0
{
116
0
  union Copy32 {
117
0
    float f;
118
0
    uint32_t i;
119
0
  } m;
120
0
  m.f = f;
121
122
#ifdef WORDS_BIGENDIAN
123
  if (is_little_endian) {
124
    m.i = php_pack_reverse_int32(m.i);
125
  }
126
#else /* WORDS_BIGENDIAN */
127
0
  if (!is_little_endian) {
128
0
    m.i = php_pack_reverse_int32(m.i);
129
0
  }
130
0
#endif /* WORDS_BIGENDIAN */
131
132
0
  memcpy(dst, &m.f, sizeof(float));
133
0
}
134
/* }}} */
135
136
/* {{{ php_pack_copy_double */
137
static void php_pack_copy_double(int is_little_endian, void * dst, double d)
138
0
{
139
0
  union Copy64 {
140
0
    double d;
141
0
    uint64_t i;
142
0
  } m;
143
0
  m.d = d;
144
145
#ifdef WORDS_BIGENDIAN
146
  if (is_little_endian) {
147
    m.i = php_pack_reverse_int64(m.i);
148
  }
149
#else /* WORDS_BIGENDIAN */
150
0
  if (!is_little_endian) {
151
0
    m.i = php_pack_reverse_int64(m.i);
152
0
  }
153
0
#endif /* WORDS_BIGENDIAN */
154
155
0
  memcpy(dst, &m.d, sizeof(double));
156
0
}
157
/* }}} */
158
159
/* {{{ php_pack_parse_float */
160
static float php_pack_parse_float(int is_little_endian, void * src)
161
0
{
162
0
  union Copy32 {
163
0
    float f;
164
0
    uint32_t i;
165
0
  } m;
166
0
  memcpy(&m.i, src, sizeof(float));
167
168
#ifdef WORDS_BIGENDIAN
169
  if (is_little_endian) {
170
    m.i = php_pack_reverse_int32(m.i);
171
  }
172
#else /* WORDS_BIGENDIAN */
173
0
  if (!is_little_endian) {
174
0
    m.i = php_pack_reverse_int32(m.i);
175
0
  }
176
0
#endif /* WORDS_BIGENDIAN */
177
178
0
  return m.f;
179
0
}
180
/* }}} */
181
182
/* {{{ php_pack_parse_double */
183
static double php_pack_parse_double(int is_little_endian, void * src)
184
0
{
185
0
  union Copy64 {
186
0
    double d;
187
0
    uint64_t i;
188
0
  } m;
189
0
  memcpy(&m.i, src, sizeof(double));
190
191
#ifdef WORDS_BIGENDIAN
192
  if (is_little_endian) {
193
    m.i = php_pack_reverse_int64(m.i);
194
  }
195
#else /* WORDS_BIGENDIAN */
196
0
  if (!is_little_endian) {
197
0
    m.i = php_pack_reverse_int64(m.i);
198
0
  }
199
0
#endif /* WORDS_BIGENDIAN */
200
201
0
  return m.d;
202
0
}
203
/* }}} */
204
205
/* pack() idea stolen from Perl (implemented formats behave the same as there except J and P)
206
 * 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, @.
207
 * Added g, G for little endian float and big endian float, added e, E for little endian double and big endian double.
208
 */
209
/* {{{ Takes one or more arguments and packs them into a binary string according to the format argument */
210
PHP_FUNCTION(pack)
211
0
{
212
0
  zval *argv = NULL;
213
0
  int num_args = 0;
214
0
  size_t i;
215
0
  int currentarg;
216
0
  char *format;
217
0
  size_t formatlen;
218
0
  char *formatcodes;
219
0
  int *formatargs;
220
0
  size_t formatcount = 0;
221
0
  int outputpos = 0, outputsize = 0;
222
0
  zend_string *output;
223
224
0
  ZEND_PARSE_PARAMETERS_START(1, -1)
225
0
    Z_PARAM_STRING(format, formatlen)
226
0
    Z_PARAM_VARIADIC('*', argv, num_args)
227
0
  ZEND_PARSE_PARAMETERS_END();
228
229
  /* We have a maximum of <formatlen> format codes to deal with */
230
0
  formatcodes = safe_emalloc(formatlen, sizeof(*formatcodes), 0);
231
0
  formatargs = safe_emalloc(formatlen, sizeof(*formatargs), 0);
232
0
  currentarg = 0;
233
234
  /* Preprocess format into formatcodes and formatargs */
235
0
  for (i = 0; i < formatlen; formatcount++) {
236
0
    char code = format[i++];
237
0
    int arg = 1;
238
239
    /* Handle format arguments if any */
240
0
    if (i < formatlen) {
241
0
      char c = format[i];
242
243
0
      if (c == '*') {
244
0
        arg = -1;
245
0
        i++;
246
0
      }
247
0
      else if (c >= '0' && c <= '9') {
248
0
        arg = atoi(&format[i]);
249
250
0
        while (format[i] >= '0' && format[i] <= '9' && i < formatlen) {
251
0
          i++;
252
0
        }
253
0
      }
254
0
    }
255
256
    /* Handle special arg '*' for all codes and check argv overflows */
257
0
    switch (code) {
258
      /* Never uses any args */
259
0
      case 'x':
260
0
      case 'X':
261
0
      case '@':
262
0
        if (arg < 0) {
263
0
          php_error_docref(NULL, E_WARNING, "Type %c: '*' ignored", code);
264
0
          arg = 1;
265
0
        }
266
0
        break;
267
268
      /* Always uses one arg */
269
0
      case 'a':
270
0
      case 'A':
271
0
      case 'Z':
272
0
      case 'h':
273
0
      case 'H':
274
0
        if (currentarg >= num_args) {
275
0
          efree(formatcodes);
276
0
          efree(formatargs);
277
0
          zend_value_error("Type %c: not enough arguments", code);
278
0
          RETURN_THROWS();
279
0
        }
280
281
0
        if (arg < 0) {
282
0
          if (!try_convert_to_string(&argv[currentarg])) {
283
0
            efree(formatcodes);
284
0
            efree(formatargs);
285
0
            RETURN_THROWS();
286
0
          }
287
288
0
          arg = Z_STRLEN(argv[currentarg]);
289
0
          if (code == 'Z') {
290
            /* add one because Z is always NUL-terminated:
291
             * pack("Z*", "aa") === "aa\0"
292
             * pack("Z2", "aa") === "a\0" */
293
0
            arg++;
294
0
          }
295
0
        }
296
297
0
        currentarg++;
298
0
        break;
299
300
      /* Use as many args as specified */
301
0
      case 'q':
302
0
      case 'Q':
303
0
      case 'J':
304
0
      case 'P':
305
#if SIZEOF_ZEND_LONG < 8
306
          efree(formatcodes);
307
          efree(formatargs);
308
          zend_value_error("64-bit format codes are not available for 32-bit versions of PHP");
309
          RETURN_THROWS();
310
#endif
311
0
      case 'c':
312
0
      case 'C':
313
0
      case 's':
314
0
      case 'S':
315
0
      case 'i':
316
0
      case 'I':
317
0
      case 'l':
318
0
      case 'L':
319
0
      case 'n':
320
0
      case 'N':
321
0
      case 'v':
322
0
      case 'V':
323
0
      case 'f': /* float */
324
0
      case 'g': /* little endian float */
325
0
      case 'G': /* big endian float */
326
0
      case 'd': /* double */
327
0
      case 'e': /* little endian double */
328
0
      case 'E': /* big endian double */
329
0
        if (arg < 0) {
330
0
          arg = num_args - currentarg;
331
0
        }
332
0
        if (currentarg > INT_MAX - arg) {
333
0
          goto too_few_args;
334
0
        }
335
0
        currentarg += arg;
336
337
0
        if (currentarg > num_args) {
338
0
too_few_args:
339
0
          efree(formatcodes);
340
0
          efree(formatargs);
341
0
          zend_value_error("Type %c: too few arguments", code);
342
0
          RETURN_THROWS();
343
0
        }
344
0
        break;
345
346
0
      default:
347
0
        efree(formatcodes);
348
0
        efree(formatargs);
349
0
        zend_value_error("Type %c: unknown format code", code);
350
0
        RETURN_THROWS();
351
0
    }
352
353
0
    formatcodes[formatcount] = code;
354
0
    formatargs[formatcount] = arg;
355
0
  }
356
357
0
  if (currentarg < num_args) {
358
0
    php_error_docref(NULL, E_WARNING, "%d arguments unused", (num_args - currentarg));
359
0
  }
360
361
  /* Calculate output length and upper bound while processing*/
362
0
  for (i = 0; i < formatcount; i++) {
363
0
    char code = formatcodes[i];
364
0
    int arg = formatargs[i];
365
366
0
    switch (code) {
367
0
      case 'h':
368
0
      case 'H':
369
0
        INC_OUTPUTPOS((arg + (arg % 2)) / 2,1) /* 4 bit per arg */
370
0
        break;
371
372
0
      case 'a':
373
0
      case 'A':
374
0
      case 'Z':
375
0
      case 'c':
376
0
      case 'C':
377
0
      case 'x':
378
0
        INC_OUTPUTPOS(arg,1)   /* 8 bit per arg */
379
0
        break;
380
381
0
      case 's':
382
0
      case 'S':
383
0
      case 'n':
384
0
      case 'v':
385
0
        INC_OUTPUTPOS(arg,2)   /* 16 bit per arg */
386
0
        break;
387
388
0
      case 'i':
389
0
      case 'I':
390
0
        INC_OUTPUTPOS(arg,sizeof(int))
391
0
        break;
392
393
0
      case 'l':
394
0
      case 'L':
395
0
      case 'N':
396
0
      case 'V':
397
0
        INC_OUTPUTPOS(arg,4)   /* 32 bit per arg */
398
0
        break;
399
400
0
#if SIZEOF_ZEND_LONG > 4
401
0
      case 'q':
402
0
      case 'Q':
403
0
      case 'J':
404
0
      case 'P':
405
0
        INC_OUTPUTPOS(arg,8)   /* 32 bit per arg */
406
0
        break;
407
0
#endif
408
409
0
      case 'f': /* float */
410
0
      case 'g': /* little endian float */
411
0
      case 'G': /* big endian float */
412
0
        INC_OUTPUTPOS(arg,sizeof(float))
413
0
        break;
414
415
0
      case 'd': /* double */
416
0
      case 'e': /* little endian double */
417
0
      case 'E': /* big endian double */
418
0
        INC_OUTPUTPOS(arg,sizeof(double))
419
0
        break;
420
421
0
      case 'X':
422
0
        outputpos -= arg;
423
424
0
        if (outputpos < 0) {
425
0
          php_error_docref(NULL, E_WARNING, "Type %c: outside of string", code);
426
0
          outputpos = 0;
427
0
        }
428
0
        break;
429
430
0
      case '@':
431
0
        outputpos = arg;
432
0
        break;
433
0
    }
434
435
0
    if (outputsize < outputpos) {
436
0
      outputsize = outputpos;
437
0
    }
438
0
  }
439
440
0
  output = zend_string_alloc(outputsize, 0);
441
0
  outputpos = 0;
442
0
  currentarg = 0;
443
444
  /* Do actual packing */
445
0
  for (i = 0; i < formatcount; i++) {
446
0
    char code = formatcodes[i];
447
0
    int arg = formatargs[i];
448
449
0
    switch (code) {
450
0
      case 'a':
451
0
      case 'A':
452
0
      case 'Z': {
453
0
        size_t arg_cp = (code != 'Z') ? arg : MAX(0, arg - 1);
454
0
        zend_string *tmp_str;
455
0
        zend_string *str = zval_get_tmp_string(&argv[currentarg++], &tmp_str);
456
457
0
        memset(&ZSTR_VAL(output)[outputpos], (code == 'a' || code == 'Z') ? '\0' : ' ', arg);
458
0
        memcpy(&ZSTR_VAL(output)[outputpos], ZSTR_VAL(str),
459
0
             (ZSTR_LEN(str) < arg_cp) ? ZSTR_LEN(str) : arg_cp);
460
461
0
        outputpos += arg;
462
0
        zend_tmp_string_release(tmp_str);
463
0
        break;
464
0
      }
465
466
0
      case 'h':
467
0
      case 'H': {
468
0
        int nibbleshift = (code == 'h') ? 0 : 4;
469
0
        int first = 1;
470
0
        zend_string *tmp_str;
471
0
        zend_string *str = zval_get_tmp_string(&argv[currentarg++], &tmp_str);
472
0
        char *v = ZSTR_VAL(str);
473
474
0
        outputpos--;
475
0
        if ((size_t)arg > ZSTR_LEN(str)) {
476
0
          php_error_docref(NULL, E_WARNING, "Type %c: not enough characters in string", code);
477
0
          arg = ZSTR_LEN(str);
478
0
        }
479
480
0
        while (arg-- > 0) {
481
0
          char n = *v++;
482
483
0
          if (n >= '0' && n <= '9') {
484
0
            n -= '0';
485
0
          } else if (n >= 'A' && n <= 'F') {
486
0
            n -= ('A' - 10);
487
0
          } else if (n >= 'a' && n <= 'f') {
488
0
            n -= ('a' - 10);
489
0
          } else {
490
0
            php_error_docref(NULL, E_WARNING, "Type %c: illegal hex digit %c", code, n);
491
0
            n = 0;
492
0
          }
493
494
0
          if (first--) {
495
0
            ZSTR_VAL(output)[++outputpos] = 0;
496
0
          } else {
497
0
            first = 1;
498
0
          }
499
500
0
          ZSTR_VAL(output)[outputpos] |= (n << nibbleshift);
501
0
          nibbleshift = (nibbleshift + 4) & 7;
502
0
        }
503
504
0
        outputpos++;
505
0
        zend_tmp_string_release(tmp_str);
506
0
        break;
507
0
      }
508
509
0
      case 'c':
510
0
      case 'C':
511
0
        while (arg-- > 0) {
512
0
          php_pack(&argv[currentarg++], 1, byte_map, &ZSTR_VAL(output)[outputpos]);
513
0
          outputpos++;
514
0
        }
515
0
        break;
516
517
0
      case 's':
518
0
      case 'S':
519
0
      case 'n':
520
0
      case 'v': {
521
0
        int *map = machine_endian_short_map;
522
523
0
        if (code == 'n') {
524
0
          map = big_endian_short_map;
525
0
        } else if (code == 'v') {
526
0
          map = little_endian_short_map;
527
0
        }
528
529
0
        while (arg-- > 0) {
530
0
          php_pack(&argv[currentarg++], 2, map, &ZSTR_VAL(output)[outputpos]);
531
0
          outputpos += 2;
532
0
        }
533
0
        break;
534
0
      }
535
536
0
      case 'i':
537
0
      case 'I':
538
0
        while (arg-- > 0) {
539
0
          php_pack(&argv[currentarg++], sizeof(int), int_map, &ZSTR_VAL(output)[outputpos]);
540
0
          outputpos += sizeof(int);
541
0
        }
542
0
        break;
543
544
0
      case 'l':
545
0
      case 'L':
546
0
      case 'N':
547
0
      case 'V': {
548
0
        int *map = machine_endian_long_map;
549
550
0
        if (code == 'N') {
551
0
          map = big_endian_long_map;
552
0
        } else if (code == 'V') {
553
0
          map = little_endian_long_map;
554
0
        }
555
556
0
        while (arg-- > 0) {
557
0
          php_pack(&argv[currentarg++], 4, map, &ZSTR_VAL(output)[outputpos]);
558
0
          outputpos += 4;
559
0
        }
560
0
        break;
561
0
      }
562
563
0
#if SIZEOF_ZEND_LONG > 4
564
0
      case 'q':
565
0
      case 'Q':
566
0
      case 'J':
567
0
      case 'P': {
568
0
        int *map = machine_endian_longlong_map;
569
570
0
        if (code == 'J') {
571
0
          map = big_endian_longlong_map;
572
0
        } else if (code == 'P') {
573
0
          map = little_endian_longlong_map;
574
0
        }
575
576
0
        while (arg-- > 0) {
577
0
          php_pack(&argv[currentarg++], 8, map, &ZSTR_VAL(output)[outputpos]);
578
0
          outputpos += 8;
579
0
        }
580
0
        break;
581
0
      }
582
0
#endif
583
584
0
      case 'f': {
585
0
        while (arg-- > 0) {
586
0
          float v = (float) zval_get_double(&argv[currentarg++]);
587
0
          memcpy(&ZSTR_VAL(output)[outputpos], &v, sizeof(v));
588
0
          outputpos += sizeof(v);
589
0
        }
590
0
        break;
591
0
      }
592
593
0
      case 'g': {
594
        /* pack little endian float */
595
0
        while (arg-- > 0) {
596
0
          float v = (float) zval_get_double(&argv[currentarg++]);
597
0
          php_pack_copy_float(1, &ZSTR_VAL(output)[outputpos], v);
598
0
          outputpos += sizeof(v);
599
0
        }
600
601
0
        break;
602
0
      }
603
0
      case 'G': {
604
        /* pack big endian float */
605
0
        while (arg-- > 0) {
606
0
          float v = (float) zval_get_double(&argv[currentarg++]);
607
0
          php_pack_copy_float(0, &ZSTR_VAL(output)[outputpos], v);
608
0
          outputpos += sizeof(v);
609
0
        }
610
0
        break;
611
0
      }
612
613
0
      case 'd': {
614
0
        while (arg-- > 0) {
615
0
          double v = zval_get_double(&argv[currentarg++]);
616
0
          memcpy(&ZSTR_VAL(output)[outputpos], &v, sizeof(v));
617
0
          outputpos += sizeof(v);
618
0
        }
619
0
        break;
620
0
      }
621
622
0
      case 'e': {
623
        /* pack little endian double */
624
0
        while (arg-- > 0) {
625
0
          double v = zval_get_double(&argv[currentarg++]);
626
0
          php_pack_copy_double(1, &ZSTR_VAL(output)[outputpos], v);
627
0
          outputpos += sizeof(v);
628
0
        }
629
0
        break;
630
0
      }
631
632
0
      case 'E': {
633
        /* pack big endian double */
634
0
        while (arg-- > 0) {
635
0
          double v = zval_get_double(&argv[currentarg++]);
636
0
          php_pack_copy_double(0, &ZSTR_VAL(output)[outputpos], v);
637
0
          outputpos += sizeof(v);
638
0
        }
639
0
        break;
640
0
      }
641
642
0
      case 'x':
643
0
        memset(&ZSTR_VAL(output)[outputpos], '\0', arg);
644
0
        outputpos += arg;
645
0
        break;
646
647
0
      case 'X':
648
0
        outputpos -= arg;
649
650
0
        if (outputpos < 0) {
651
0
          outputpos = 0;
652
0
        }
653
0
        break;
654
655
0
      case '@':
656
0
        if (arg > outputpos) {
657
0
          memset(&ZSTR_VAL(output)[outputpos], '\0', arg - outputpos);
658
0
        }
659
0
        outputpos = arg;
660
0
        break;
661
0
    }
662
0
  }
663
664
0
  efree(formatcodes);
665
0
  efree(formatargs);
666
0
  ZSTR_VAL(output)[outputpos] = '\0';
667
0
  ZSTR_LEN(output) = outputpos;
668
0
  RETURN_NEW_STR(output);
669
0
}
670
/* }}} */
671
672
/* unpack() is based on Perl's unpack(), but is modified a bit from there.
673
 * Rather than depending on error-prone ordered lists or syntactically
674
 * unpleasant pass-by-reference, we return an object with named parameters
675
 * (like *_fetch_object()). Syntax is "f[repeat]name/...", where "f" is the
676
 * formatter char (like pack()), "[repeat]" is the optional repeater argument,
677
 * and "name" is the name of the variable to use.
678
 * Example: "c2chars/nints" will return an object with fields
679
 * chars1, chars2, and ints.
680
 * Numeric pack types will return numbers, a and A will return strings,
681
 * f and d will return doubles.
682
 * 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, @.
683
 * Added g, G for little endian float and big endian float, added e, E for little endian double and big endian double.
684
 */
685
/* {{{ Unpack binary string into named array elements according to format argument */
686
PHP_FUNCTION(unpack)
687
0
{
688
0
  char *format, *input;
689
0
  zend_string *formatarg, *inputarg;
690
0
  zend_long formatlen, inputpos, inputlen;
691
0
  int i;
692
0
  zend_long offset = 0;
693
694
0
  ZEND_PARSE_PARAMETERS_START(2, 3)
695
0
    Z_PARAM_STR(formatarg)
696
0
    Z_PARAM_STR(inputarg)
697
0
    Z_PARAM_OPTIONAL
698
0
    Z_PARAM_LONG(offset)
699
0
  ZEND_PARSE_PARAMETERS_END();
700
701
0
  format = ZSTR_VAL(formatarg);
702
0
  formatlen = ZSTR_LEN(formatarg);
703
0
  input = ZSTR_VAL(inputarg);
704
0
  inputlen = ZSTR_LEN(inputarg);
705
0
  inputpos = 0;
706
707
708
0
  if (offset < 0 || offset > inputlen) {
709
0
    zend_argument_value_error(3, "must be contained in argument #2 ($data)");
710
0
    RETURN_THROWS();
711
0
  }
712
713
0
  input += offset;
714
0
  inputlen -= offset;
715
716
0
  array_init(return_value);
717
718
0
  while (formatlen-- > 0) {
719
0
    char type = *(format++);
720
0
    int repetitions = 1, argb;
721
0
    char *name;
722
0
    int namelen;
723
0
    int size = 0;
724
725
    /* Handle format arguments if any */
726
0
    if (formatlen > 0) {
727
0
      char c = *format;
728
729
0
      if (c >= '0' && c <= '9') {
730
0
        errno = 0;
731
0
        long tmp = strtol(format, NULL, 10);
732
        /* There is not strtoi. We have to check the range ourselves.
733
         * With 32-bit long the INT_{MIN,MAX} are useless because long == int, but with 64-bit they do limit us to 32-bit. */
734
0
        if (errno || tmp < INT_MIN || tmp > INT_MAX) {
735
0
          php_error_docref(NULL, E_WARNING, "Type %c: integer overflow", type);
736
0
          zend_array_destroy(Z_ARR_P(return_value));
737
0
          RETURN_FALSE;
738
0
        }
739
0
        repetitions = tmp;
740
741
0
        while (formatlen > 0 && *format >= '0' && *format <= '9') {
742
0
          format++;
743
0
          formatlen--;
744
0
        }
745
0
      } else if (c == '*') {
746
0
        repetitions = -1;
747
0
        format++;
748
0
        formatlen--;
749
0
      }
750
0
    }
751
752
    /* Get of new value in array */
753
0
    name = format;
754
0
    argb = repetitions;
755
756
0
    while (formatlen > 0 && *format != '/') {
757
0
      formatlen--;
758
0
      format++;
759
0
    }
760
761
0
    namelen = format - name;
762
763
0
    if (namelen > 200)
764
0
      namelen = 200;
765
766
0
    switch (type) {
767
      /* Never use any input */
768
0
      case 'X':
769
0
        size = -1;
770
0
        if (repetitions < 0) {
771
0
          php_error_docref(NULL, E_WARNING, "Type %c: '*' ignored", type);
772
0
          repetitions = 1;
773
0
        }
774
0
        break;
775
776
0
      case '@':
777
0
        size = 0;
778
0
        break;
779
780
0
      case 'a':
781
0
      case 'A':
782
0
      case 'Z':
783
0
        size = repetitions;
784
0
        repetitions = 1;
785
0
        break;
786
787
0
      case 'h':
788
0
      case 'H':
789
0
        size = (repetitions > 0) ? ((unsigned int) repetitions + 1) / 2 : repetitions;
790
0
        repetitions = 1;
791
0
        break;
792
793
      /* Use 1 byte of input */
794
0
      case 'c':
795
0
      case 'C':
796
0
      case 'x':
797
0
        size = 1;
798
0
        break;
799
800
      /* Use 2 bytes of input */
801
0
      case 's':
802
0
      case 'S':
803
0
      case 'n':
804
0
      case 'v':
805
0
        size = 2;
806
0
        break;
807
808
      /* Use sizeof(int) bytes of input */
809
0
      case 'i':
810
0
      case 'I':
811
0
        size = sizeof(int);
812
0
        break;
813
814
      /* Use 4 bytes of input */
815
0
      case 'l':
816
0
      case 'L':
817
0
      case 'N':
818
0
      case 'V':
819
0
        size = 4;
820
0
        break;
821
822
      /* Use 8 bytes of input */
823
0
      case 'q':
824
0
      case 'Q':
825
0
      case 'J':
826
0
      case 'P':
827
0
#if SIZEOF_ZEND_LONG > 4
828
0
        size = 8;
829
0
        break;
830
#else
831
        zend_value_error("64-bit format codes are not available for 32-bit versions of PHP");
832
        RETURN_THROWS();
833
#endif
834
835
      /* Use sizeof(float) bytes of input */
836
0
      case 'f':
837
0
      case 'g':
838
0
      case 'G':
839
0
        size = sizeof(float);
840
0
        break;
841
842
      /* Use sizeof(double) bytes of input */
843
0
      case 'd':
844
0
      case 'e':
845
0
      case 'E':
846
0
        size = sizeof(double);
847
0
        break;
848
849
0
      default:
850
0
        zend_value_error("Invalid format type %c", type);
851
0
        RETURN_THROWS();
852
0
    }
853
854
855
    /* Do actual unpacking */
856
0
    for (i = 0; i != repetitions; i++ ) {
857
858
0
      if (size != 0 && size != -1 && INT_MAX - size + 1 < inputpos) {
859
0
        php_error_docref(NULL, E_WARNING, "Type %c: integer overflow", type);
860
0
        zend_array_destroy(Z_ARR_P(return_value));
861
0
        RETURN_FALSE;
862
0
      }
863
864
0
      if ((inputpos + size) <= inputlen) {
865
866
0
        zend_string* real_name;
867
0
        zend_long long_key = 0;
868
0
        zval val;
869
870
0
        if (namelen == 0) {
871
0
          real_name = NULL;
872
0
          long_key = i + 1;
873
0
        } else if (repetitions == 1) {
874
          /* Use a part of the formatarg argument directly as the name. */
875
0
          real_name = zend_string_init_fast(name, namelen);
876
0
        } else {
877
          /* Need to add the 1-based element number to the name */
878
0
          char buf[MAX_LENGTH_OF_LONG + 1];
879
0
          char *res = zend_print_ulong_to_buf(buf + sizeof(buf) - 1, i+1);
880
0
          size_t digits = buf + sizeof(buf) - 1 - res;
881
0
          real_name = zend_string_concat2(name, namelen, res, digits);
882
0
        }
883
884
0
        switch (type) {
885
0
          case 'a': {
886
            /* a will not strip any trailing whitespace or null padding */
887
0
            zend_long len = inputlen - inputpos;  /* Remaining string */
888
889
            /* If size was given take minimum of len and size */
890
0
            if ((size >= 0) && (len > size)) {
891
0
              len = size;
892
0
            }
893
894
0
            size = len;
895
896
0
            ZVAL_STRINGL(&val, &input[inputpos], len);
897
0
            break;
898
0
          }
899
0
          case 'A': {
900
            /* A will strip any trailing whitespace */
901
0
            zend_long len = inputlen - inputpos;  /* Remaining string */
902
903
            /* If size was given take minimum of len and size */
904
0
            if ((size >= 0) && (len > size)) {
905
0
              len = size;
906
0
            }
907
908
0
            size = len;
909
910
            /* Remove trailing white space and nulls chars from unpacked data */
911
0
            while (--len >= 0) {
912
0
              if (input[inputpos + len] != '\0'
913
0
                && input[inputpos + len] != ' '
914
0
                && input[inputpos + len] != '\t'
915
0
                && input[inputpos + len] != '\r'
916
0
                && input[inputpos + len] != '\n'
917
0
              )
918
0
                break;
919
0
            }
920
921
0
            ZVAL_STRINGL(&val, &input[inputpos], len + 1);
922
0
            break;
923
0
          }
924
          /* New option added for Z to remain in-line with the Perl implementation */
925
0
          case 'Z': {
926
            /* Z will strip everything after the first null character */
927
0
            zend_long s,
928
0
               len = inputlen - inputpos; /* Remaining string */
929
930
            /* If size was given take minimum of len and size */
931
0
            if ((size >= 0) && (len > size)) {
932
0
              len = size;
933
0
            }
934
935
0
            size = len;
936
937
            /* Remove everything after the first null */
938
0
            for (s=0 ; s < len ; s++) {
939
0
              if (input[inputpos + s] == '\0')
940
0
                break;
941
0
            }
942
0
            len = s;
943
944
0
            ZVAL_STRINGL(&val, &input[inputpos], len);
945
0
            break;
946
0
          }
947
948
949
0
          case 'h':
950
0
          case 'H': {
951
0
            zend_long len = (inputlen - inputpos) * 2;  /* Remaining */
952
0
            int nibbleshift = (type == 'h') ? 0 : 4;
953
0
            int first = 1;
954
0
            zend_string *buf;
955
0
            zend_long ipos, opos;
956
957
958
0
            if (size > INT_MAX / 2) {
959
0
              if (real_name) {
960
0
                zend_string_release_ex(real_name, false);
961
0
              }
962
0
              zend_argument_value_error(1, "repeater must be less than or equal to %d", INT_MAX / 2);
963
0
              RETURN_THROWS();
964
0
            }
965
966
            /* If size was given take minimum of len and size */
967
0
            if (size >= 0 && len > (size * 2)) {
968
0
              len = size * 2;
969
0
            }
970
971
0
            if (len > 0 && argb > 0) {
972
0
              len -= argb % 2;
973
0
            }
974
975
0
            buf = zend_string_alloc(len, 0);
976
977
0
            for (ipos = opos = 0; opos < len; opos++) {
978
0
              char cc = (input[inputpos + ipos] >> nibbleshift) & 0xf;
979
980
0
              if (cc < 10) {
981
0
                cc += '0';
982
0
              } else {
983
0
                cc += 'a' - 10;
984
0
              }
985
986
0
              ZSTR_VAL(buf)[opos] = cc;
987
0
              nibbleshift = (nibbleshift + 4) & 7;
988
989
0
              if (first-- == 0) {
990
0
                ipos++;
991
0
                first = 1;
992
0
              }
993
0
            }
994
995
0
            ZSTR_VAL(buf)[len] = '\0';
996
997
0
            ZVAL_STR(&val, buf);
998
0
            break;
999
0
          }
1000
1001
0
          case 'c':   /* signed */
1002
0
          case 'C': { /* unsigned */
1003
0
            uint8_t x = input[inputpos];
1004
0
            zend_long v = (type == 'c') ? (int8_t) x : x;
1005
1006
0
            ZVAL_LONG(&val, v);
1007
0
            break;
1008
0
          }
1009
1010
0
          case 's':   /* signed machine endian   */
1011
0
          case 'S':   /* unsigned machine endian */
1012
0
          case 'n':   /* unsigned big endian     */
1013
0
          case 'v': { /* unsigned little endian  */
1014
0
            zend_long v = 0;
1015
0
            uint16_t x = *((unaligned_uint16_t*) &input[inputpos]);
1016
1017
0
            if (type == 's') {
1018
0
              v = (int16_t) x;
1019
0
            } else if ((type == 'n' && MACHINE_LITTLE_ENDIAN) || (type == 'v' && !MACHINE_LITTLE_ENDIAN)) {
1020
0
              v = php_pack_reverse_int16(x);
1021
0
            } else {
1022
0
              v = x;
1023
0
            }
1024
1025
0
            ZVAL_LONG(&val, v);
1026
0
            break;
1027
0
          }
1028
1029
0
          case 'i':   /* signed integer, machine size, machine endian */
1030
0
          case 'I': { /* unsigned integer, machine size, machine endian */
1031
0
            zend_long v;
1032
0
            if (type == 'i') {
1033
0
              int x = *((unaligned_int*) &input[inputpos]);
1034
0
              v = x;
1035
0
            } else {
1036
0
              unsigned int x = *((unaligned_uint*) &input[inputpos]);
1037
0
              v = x;
1038
0
            }
1039
1040
0
            ZVAL_LONG(&val, v);
1041
0
            break;
1042
0
          }
1043
1044
0
          case 'l':   /* signed machine endian   */
1045
0
          case 'L':   /* unsigned machine endian */
1046
0
          case 'N':   /* unsigned big endian     */
1047
0
          case 'V': { /* unsigned little endian  */
1048
0
            zend_long v = 0;
1049
0
            uint32_t x = *((unaligned_uint32_t*) &input[inputpos]);
1050
1051
0
            if (type == 'l') {
1052
0
              v = (int32_t) x;
1053
0
            } else if ((type == 'N' && MACHINE_LITTLE_ENDIAN) || (type == 'V' && !MACHINE_LITTLE_ENDIAN)) {
1054
0
              v = php_pack_reverse_int32(x);
1055
0
            } else {
1056
0
              v = x;
1057
0
            }
1058
1059
0
            ZVAL_LONG(&val, v);
1060
0
            break;
1061
0
          }
1062
1063
0
#if SIZEOF_ZEND_LONG > 4
1064
0
          case 'q':   /* signed machine endian   */
1065
0
          case 'Q':   /* unsigned machine endian */
1066
0
          case 'J':   /* unsigned big endian     */
1067
0
          case 'P': { /* unsigned little endian  */
1068
0
            zend_long v = 0;
1069
0
            uint64_t x = *((unaligned_uint64_t*) &input[inputpos]);
1070
1071
0
            if (type == 'q') {
1072
0
              v = (int64_t) x;
1073
0
            } else if ((type == 'J' && MACHINE_LITTLE_ENDIAN) || (type == 'P' && !MACHINE_LITTLE_ENDIAN)) {
1074
0
              v = php_pack_reverse_int64(x);
1075
0
            } else {
1076
0
              v = x;
1077
0
            }
1078
1079
0
            ZVAL_LONG(&val, v);
1080
0
            break;
1081
0
          }
1082
0
#endif
1083
1084
0
          case 'f': /* float */
1085
0
          case 'g': /* little endian float*/
1086
0
          case 'G': /* big endian float*/
1087
0
          {
1088
0
            float v;
1089
1090
0
            if (type == 'g') {
1091
0
              v = php_pack_parse_float(1, &input[inputpos]);
1092
0
            } else if (type == 'G') {
1093
0
              v = php_pack_parse_float(0, &input[inputpos]);
1094
0
            } else {
1095
0
              memcpy(&v, &input[inputpos], sizeof(float));
1096
0
            }
1097
1098
0
            ZVAL_DOUBLE(&val, v);
1099
0
            break;
1100
0
          }
1101
1102
1103
0
          case 'd': /* double */
1104
0
          case 'e': /* little endian float */
1105
0
          case 'E': /* big endian float */
1106
0
          {
1107
0
            double v;
1108
0
            if (type == 'e') {
1109
0
              v = php_pack_parse_double(1, &input[inputpos]);
1110
0
            } else if (type == 'E') {
1111
0
              v = php_pack_parse_double(0, &input[inputpos]);
1112
0
            } else {
1113
0
              memcpy(&v, &input[inputpos], sizeof(double));
1114
0
            }
1115
1116
0
            ZVAL_DOUBLE(&val, v);
1117
0
            break;
1118
0
          }
1119
1120
0
          case 'x':
1121
            /* Do nothing with input, just skip it */
1122
0
            goto no_output;
1123
1124
0
          case 'X':
1125
0
            if (inputpos < size) {
1126
0
              inputpos = -size;
1127
0
              i = repetitions - 1;    /* Break out of for loop */
1128
1129
0
              if (repetitions >= 0) {
1130
0
                php_error_docref(NULL, E_WARNING, "Type %c: outside of string", type);
1131
0
              }
1132
0
            }
1133
0
            goto no_output;
1134
1135
0
          case '@':
1136
0
            if (repetitions <= inputlen) {
1137
0
              inputpos = repetitions;
1138
0
            } else {
1139
0
              php_error_docref(NULL, E_WARNING, "Type %c: outside of string", type);
1140
0
            }
1141
1142
0
            i = repetitions - 1;  /* Done, break out of for loop */
1143
0
            goto no_output;
1144
0
        }
1145
1146
0
        if (real_name) {
1147
0
          zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
1148
0
        } else {
1149
0
          zend_hash_index_update(Z_ARRVAL_P(return_value), long_key, &val);
1150
0
        }
1151
1152
0
no_output:
1153
0
        if (real_name) {
1154
0
          zend_string_release_ex(real_name, false);
1155
0
        }
1156
1157
0
        inputpos += size;
1158
0
        if (inputpos < 0) {
1159
0
          if (size != -1) { /* only print warning if not working with * */
1160
0
            php_error_docref(NULL, E_WARNING, "Type %c: outside of string", type);
1161
0
          }
1162
0
          inputpos = 0;
1163
0
        }
1164
0
      } else if (repetitions < 0) {
1165
        /* Reached end of input for '*' repeater */
1166
0
        break;
1167
0
      } else {
1168
0
        php_error_docref(NULL, E_WARNING, "Type %c: not enough input values, need %d values but only " ZEND_LONG_FMT " %s provided", type, size, inputlen - inputpos, inputlen - inputpos == 1 ? "was" : "were");
1169
0
        zend_array_destroy(Z_ARR_P(return_value));
1170
0
        RETURN_FALSE;
1171
0
      }
1172
0
    }
1173
1174
0
    if (formatlen > 0) {
1175
0
      formatlen--;  /* Skip '/' separator, does no harm if inputlen == 0 */
1176
0
      format++;
1177
0
    }
1178
0
  }
1179
0
}
1180
/* }}} */
1181
1182
/* {{{ PHP_MINIT_FUNCTION */
1183
PHP_MINIT_FUNCTION(pack)
1184
16
{
1185
16
  int i;
1186
1187
16
  if (MACHINE_LITTLE_ENDIAN) {
1188
    /* Where to get lo to hi bytes from */
1189
16
    byte_map[0] = 0;
1190
1191
80
    for (i = 0; i < (int)sizeof(int); i++) {
1192
64
      int_map[i] = i;
1193
64
    }
1194
1195
16
    machine_endian_short_map[0] = 0;
1196
16
    machine_endian_short_map[1] = 1;
1197
16
    big_endian_short_map[0] = 1;
1198
16
    big_endian_short_map[1] = 0;
1199
16
    little_endian_short_map[0] = 0;
1200
16
    little_endian_short_map[1] = 1;
1201
1202
16
    machine_endian_long_map[0] = 0;
1203
16
    machine_endian_long_map[1] = 1;
1204
16
    machine_endian_long_map[2] = 2;
1205
16
    machine_endian_long_map[3] = 3;
1206
16
    big_endian_long_map[0] = 3;
1207
16
    big_endian_long_map[1] = 2;
1208
16
    big_endian_long_map[2] = 1;
1209
16
    big_endian_long_map[3] = 0;
1210
16
    little_endian_long_map[0] = 0;
1211
16
    little_endian_long_map[1] = 1;
1212
16
    little_endian_long_map[2] = 2;
1213
16
    little_endian_long_map[3] = 3;
1214
1215
16
#if SIZEOF_ZEND_LONG > 4
1216
16
    machine_endian_longlong_map[0] = 0;
1217
16
    machine_endian_longlong_map[1] = 1;
1218
16
    machine_endian_longlong_map[2] = 2;
1219
16
    machine_endian_longlong_map[3] = 3;
1220
16
    machine_endian_longlong_map[4] = 4;
1221
16
    machine_endian_longlong_map[5] = 5;
1222
16
    machine_endian_longlong_map[6] = 6;
1223
16
    machine_endian_longlong_map[7] = 7;
1224
16
    big_endian_longlong_map[0] = 7;
1225
16
    big_endian_longlong_map[1] = 6;
1226
16
    big_endian_longlong_map[2] = 5;
1227
16
    big_endian_longlong_map[3] = 4;
1228
16
    big_endian_longlong_map[4] = 3;
1229
16
    big_endian_longlong_map[5] = 2;
1230
16
    big_endian_longlong_map[6] = 1;
1231
16
    big_endian_longlong_map[7] = 0;
1232
16
    little_endian_longlong_map[0] = 0;
1233
16
    little_endian_longlong_map[1] = 1;
1234
16
    little_endian_longlong_map[2] = 2;
1235
16
    little_endian_longlong_map[3] = 3;
1236
16
    little_endian_longlong_map[4] = 4;
1237
16
    little_endian_longlong_map[5] = 5;
1238
16
    little_endian_longlong_map[6] = 6;
1239
16
    little_endian_longlong_map[7] = 7;
1240
16
#endif
1241
16
  }
1242
0
  else {
1243
0
    zval val;
1244
0
    int size = sizeof(Z_LVAL(val));
1245
0
    Z_LVAL(val)=0; /*silence a warning*/
1246
1247
    /* Where to get hi to lo bytes from */
1248
0
    byte_map[0] = size - 1;
1249
1250
0
    for (i = 0; i < (int)sizeof(int); i++) {
1251
0
      int_map[i] = size - (sizeof(int) - i);
1252
0
    }
1253
1254
0
    machine_endian_short_map[0] = size - 2;
1255
0
    machine_endian_short_map[1] = size - 1;
1256
0
    big_endian_short_map[0] = size - 2;
1257
0
    big_endian_short_map[1] = size - 1;
1258
0
    little_endian_short_map[0] = size - 1;
1259
0
    little_endian_short_map[1] = size - 2;
1260
1261
0
    machine_endian_long_map[0] = size - 4;
1262
0
    machine_endian_long_map[1] = size - 3;
1263
0
    machine_endian_long_map[2] = size - 2;
1264
0
    machine_endian_long_map[3] = size - 1;
1265
0
    big_endian_long_map[0] = size - 4;
1266
0
    big_endian_long_map[1] = size - 3;
1267
0
    big_endian_long_map[2] = size - 2;
1268
0
    big_endian_long_map[3] = size - 1;
1269
0
    little_endian_long_map[0] = size - 1;
1270
0
    little_endian_long_map[1] = size - 2;
1271
0
    little_endian_long_map[2] = size - 3;
1272
0
    little_endian_long_map[3] = size - 4;
1273
1274
0
#if SIZEOF_ZEND_LONG > 4
1275
0
    machine_endian_longlong_map[0] = size - 8;
1276
0
    machine_endian_longlong_map[1] = size - 7;
1277
0
    machine_endian_longlong_map[2] = size - 6;
1278
0
    machine_endian_longlong_map[3] = size - 5;
1279
0
    machine_endian_longlong_map[4] = size - 4;
1280
0
    machine_endian_longlong_map[5] = size - 3;
1281
0
    machine_endian_longlong_map[6] = size - 2;
1282
0
    machine_endian_longlong_map[7] = size - 1;
1283
0
    big_endian_longlong_map[0] = size - 8;
1284
0
    big_endian_longlong_map[1] = size - 7;
1285
0
    big_endian_longlong_map[2] = size - 6;
1286
0
    big_endian_longlong_map[3] = size - 5;
1287
0
    big_endian_longlong_map[4] = size - 4;
1288
0
    big_endian_longlong_map[5] = size - 3;
1289
0
    big_endian_longlong_map[6] = size - 2;
1290
0
    big_endian_longlong_map[7] = size - 1;
1291
0
    little_endian_longlong_map[0] = size - 1;
1292
0
    little_endian_longlong_map[1] = size - 2;
1293
0
    little_endian_longlong_map[2] = size - 3;
1294
0
    little_endian_longlong_map[3] = size - 4;
1295
0
    little_endian_longlong_map[4] = size - 5;
1296
0
    little_endian_longlong_map[5] = size - 6;
1297
0
    little_endian_longlong_map[6] = size - 7;
1298
0
    little_endian_longlong_map[7] = size - 8;
1299
0
#endif
1300
0
  }
1301
1302
16
  return SUCCESS;
1303
16
}
1304
/* }}} */