Coverage Report

Created: 2025-12-31 07:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/ext/standard/iptc.c
Line
Count
Source
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: Thies C. Arntzen <thies@thieso.net>                          |
14
   +----------------------------------------------------------------------+
15
 */
16
17
/*
18
 * Functions to parse & compse IPTC data.
19
 * PhotoShop >= 3.0 can read and write textual data to JPEG files.
20
 * ... more to come .....
21
 *
22
 * i know, parts of this is now duplicated in image.c
23
 * but in this case i think it's okay!
24
 */
25
26
/*
27
 * TODO:
28
 *  - add IPTC translation table
29
 */
30
31
#include "php.h"
32
#include "ext/standard/head.h"
33
34
#include <sys/stat.h>
35
36
#include <stdint.h>
37
#ifndef PHP_WIN32
38
# include <inttypes.h>
39
#endif
40
41
/* some defines for the different JPEG block types */
42
#define M_SOF0  0xC0            /* Start Of Frame N */
43
#define M_SOF1  0xC1            /* N indicates which compression process */
44
#define M_SOF2  0xC2            /* Only SOF0-SOF2 are now in common use */
45
#define M_SOF3  0xC3
46
#define M_SOF5  0xC5            /* NB: codes C4 and CC are NOT SOF markers */
47
#define M_SOF6  0xC6
48
#define M_SOF7  0xC7
49
#define M_SOF9  0xC9
50
#define M_SOF10 0xCA
51
#define M_SOF11 0xCB
52
#define M_SOF13 0xCD
53
#define M_SOF14 0xCE
54
#define M_SOF15 0xCF
55
#define M_SOI   0xD8
56
0
#define M_EOI   0xD9            /* End Of Image (end of datastream) */
57
0
#define M_SOS   0xDA            /* Start Of Scan (begins compressed data) */
58
0
#define M_APP0  0xe0
59
0
#define M_APP1  0xe1
60
#define M_APP2  0xe2
61
#define M_APP3  0xe3
62
#define M_APP4  0xe4
63
#define M_APP5  0xe5
64
#define M_APP6  0xe6
65
#define M_APP7  0xe7
66
#define M_APP8  0xe8
67
#define M_APP9  0xe9
68
#define M_APP10 0xea
69
#define M_APP11 0xeb
70
#define M_APP12 0xec
71
0
#define M_APP13 0xed
72
#define M_APP14 0xee
73
#define M_APP15 0xef
74
75
/* {{{ php_iptc_put1 */
76
static int php_iptc_put1(FILE *fp, int spool, unsigned char c, unsigned char **spoolbuf, const unsigned char *spoolbuf_end)
77
0
{
78
0
  if (spool > 0)
79
0
    PUTC(c);
80
81
0
  if (spoolbuf) {
82
0
    if (UNEXPECTED(*spoolbuf >= spoolbuf_end)) {
83
0
      return EOF;
84
0
    }
85
0
    *(*spoolbuf)++ = c;
86
0
  }
87
88
0
  return c;
89
0
}
90
/* }}} */
91
92
/* {{{ php_iptc_get1 */
93
static int php_iptc_get1(FILE *fp, int spool, unsigned char **spoolbuf, const unsigned char *spoolbuf_end)
94
0
{
95
0
  int c;
96
0
  char cc;
97
98
0
  c = getc(fp);
99
100
0
  if (c == EOF) return EOF;
101
102
0
  if (spool > 0) {
103
0
    cc = c;
104
0
    PUTC(cc);
105
0
  }
106
107
0
  if (spoolbuf) {
108
0
    if (UNEXPECTED(*spoolbuf >= spoolbuf_end)) {
109
0
      return EOF;
110
0
    }
111
0
    *(*spoolbuf)++ = c;
112
0
  }
113
114
0
  return c;
115
0
}
116
/* }}} */
117
118
/* {{{ php_iptc_read_remaining */
119
static int php_iptc_read_remaining(FILE *fp, int spool, unsigned char **spoolbuf, const unsigned char *spoolbuf_end)
120
0
{
121
0
  while (php_iptc_get1(fp, spool, spoolbuf, spoolbuf_end) != EOF) continue;
122
123
0
  return M_EOI;
124
0
}
125
/* }}} */
126
127
/* {{{ php_iptc_skip_variable */
128
static int php_iptc_skip_variable(FILE *fp, int spool, unsigned char **spoolbuf, const unsigned char *spoolbuf_end)
129
0
{
130
0
  unsigned int  length;
131
0
  int c1, c2;
132
133
0
  if ((c1 = php_iptc_get1(fp, spool, spoolbuf, spoolbuf_end)) == EOF) return M_EOI;
134
135
0
  if ((c2 = php_iptc_get1(fp, spool, spoolbuf, spoolbuf_end)) == EOF) return M_EOI;
136
137
0
  length = (((unsigned char) c1) << 8) + ((unsigned char) c2);
138
139
0
  length -= 2;
140
141
0
  while (length--)
142
0
    if (php_iptc_get1(fp, spool, spoolbuf, spoolbuf_end) == EOF) return M_EOI;
143
144
0
  return 0;
145
0
}
146
/* }}} */
147
148
/* {{{ php_iptc_next_marker */
149
static int php_iptc_next_marker(FILE *fp, int spool, unsigned char **spoolbuf, const unsigned char *spoolbuf_end)
150
0
{
151
0
  int c;
152
153
  /* skip unimportant stuff */
154
155
0
  c = php_iptc_get1(fp, spool, spoolbuf, spoolbuf_end);
156
157
0
  if (c == EOF) return M_EOI;
158
159
0
  while (c != 0xff) {
160
0
    if ((c = php_iptc_get1(fp, spool, spoolbuf, spoolbuf_end)) == EOF)
161
0
      return M_EOI; /* we hit EOF */
162
0
  }
163
164
  /* get marker byte, swallowing possible padding */
165
0
  do {
166
0
    c = php_iptc_get1(fp, 0, 0, NULL);
167
0
    if (c == EOF)
168
0
      return M_EOI;       /* we hit EOF */
169
0
    else
170
0
    if (c == 0xff)
171
0
      php_iptc_put1(fp, spool, (unsigned char)c, spoolbuf, spoolbuf_end);
172
0
  } while (c == 0xff);
173
174
0
  return (unsigned int) c;
175
0
}
176
/* }}} */
177
178
static char psheader[] = "\xFF\xED\0\0Photoshop 3.0\08BIM\x04\x04\0\0\0\0";
179
180
/* {{{ Embed binary IPTC data into a JPEG image. */
181
PHP_FUNCTION(iptcembed)
182
0
{
183
0
  char *iptcdata, *jpeg_file;
184
0
  size_t iptcdata_len, jpeg_file_len;
185
0
  zend_long spool = 0;
186
0
  FILE *fp;
187
0
  unsigned int marker, done = 0;
188
0
  size_t inx;
189
0
  zend_string *spoolbuf = NULL;
190
0
  unsigned char *poi = NULL;
191
0
  unsigned char *spoolbuf_end = NULL;
192
0
  zend_stat_t sb = {0};
193
0
  bool written = 0;
194
195
0
  ZEND_PARSE_PARAMETERS_START(2, 3)
196
0
    Z_PARAM_STRING(iptcdata, iptcdata_len)
197
0
    Z_PARAM_PATH(jpeg_file, jpeg_file_len)
198
0
    Z_PARAM_OPTIONAL
199
0
    Z_PARAM_LONG(spool)
200
0
  ZEND_PARSE_PARAMETERS_END();
201
202
0
  if (php_check_open_basedir(jpeg_file)) {
203
0
    RETURN_FALSE;
204
0
  }
205
206
0
  if (iptcdata_len >= SIZE_MAX - sizeof(psheader) - 1025) {
207
0
    zend_argument_value_error(1, "is too large");
208
0
    RETURN_THROWS();
209
0
  }
210
211
0
  if ((fp = VCWD_FOPEN(jpeg_file, "rb")) == 0) {
212
0
    php_error_docref(NULL, E_WARNING, "Unable to open %s", jpeg_file);
213
0
    RETURN_FALSE;
214
0
  }
215
216
0
  if (spool < 2) {
217
0
    if (zend_fstat(fileno(fp), &sb) != 0) {
218
0
      fclose(fp);
219
0
      RETURN_FALSE;
220
0
    }
221
222
0
    spoolbuf = zend_string_safe_alloc(1, iptcdata_len + sizeof(psheader) + 1024 + 1, sb.st_size, 0);
223
0
    poi = (unsigned char*)ZSTR_VAL(spoolbuf);
224
0
    spoolbuf_end = poi + ZSTR_LEN(spoolbuf);
225
0
    memset(poi, 0, iptcdata_len + sizeof(psheader) + sb.st_size + 1024 + 1);
226
0
  }
227
228
0
  if (php_iptc_get1(fp, spool, poi?&poi:0, spoolbuf_end) != 0xFF) {
229
0
    fclose(fp);
230
0
    if (spoolbuf) {
231
0
      zend_string_efree(spoolbuf);
232
0
    }
233
0
    RETURN_FALSE;
234
0
  }
235
236
0
  if (php_iptc_get1(fp, spool, poi?&poi:0, spoolbuf_end) != 0xD8) {
237
0
err:
238
0
    fclose(fp);
239
0
    if (spoolbuf) {
240
0
      zend_string_efree(spoolbuf);
241
0
    }
242
0
    RETURN_FALSE;
243
0
  }
244
245
0
  while (!done) {
246
0
    marker = php_iptc_next_marker(fp, spool, poi?&poi:0, spoolbuf_end);
247
248
0
    if (marker == M_EOI) { /* EOF */
249
0
      break;
250
0
    } else if (marker != M_APP13) {
251
0
      if (php_iptc_put1(fp, spool, (unsigned char)marker, poi?&poi:0, spoolbuf_end) < 0) {
252
0
        goto err;
253
0
      }
254
0
    }
255
256
0
    switch (marker) {
257
0
      case M_APP13:
258
        /* we are going to write a new APP13 marker, so don't output the old one */
259
0
        php_iptc_skip_variable(fp, 0, 0, spoolbuf_end);
260
0
        fgetc(fp); /* skip already copied 0xFF byte */
261
0
        php_iptc_read_remaining(fp, spool, poi?&poi:0, spoolbuf_end);
262
0
        done = 1;
263
0
        break;
264
265
0
      case M_APP0:
266
        /* APP0 is in each and every JPEG, so when we hit APP0 we insert our new APP13! */
267
0
      case M_APP1:
268
0
        if (written) {
269
          /* don't try to write the data twice */
270
0
          break;
271
0
        }
272
0
        written = 1;
273
274
0
        php_iptc_skip_variable(fp, spool, poi?&poi:0, spoolbuf_end);
275
276
0
        if (iptcdata_len & 1) {
277
0
          iptcdata_len++; /* make the length even */
278
0
        }
279
280
0
        psheader[ 2 ] = (char) ((iptcdata_len+28)>>8);
281
0
        psheader[ 3 ] = (iptcdata_len+28)&0xff;
282
283
0
        for (inx = 0; inx < 28; inx++) {
284
0
          if (php_iptc_put1(fp, spool, psheader[inx], poi?&poi:0, spoolbuf_end) < 0) {
285
0
            goto err;
286
0
          }
287
0
        }
288
289
0
        if (php_iptc_put1(fp, spool, (unsigned char)(iptcdata_len>>8), poi?&poi:0, spoolbuf_end) < 0) {
290
0
          goto err;
291
0
        }
292
0
        if (php_iptc_put1(fp, spool, (unsigned char)(iptcdata_len&0xff), poi?&poi:0, spoolbuf_end) < 0) {
293
0
          goto err;
294
0
        }
295
296
0
        for (inx = 0; inx < iptcdata_len; inx++) {
297
0
          if (php_iptc_put1(fp, spool, iptcdata[inx], poi?&poi:0, spoolbuf_end) < 0) {
298
0
            goto err;
299
0
          }
300
0
        }
301
0
        break;
302
303
0
      case M_SOS:
304
        /* we hit data, no more marker-inserting can be done! */
305
0
        php_iptc_read_remaining(fp, spool, poi?&poi:0, spoolbuf_end);
306
0
        done = 1;
307
0
        break;
308
309
0
      default:
310
0
        php_iptc_skip_variable(fp, spool, poi?&poi:0, spoolbuf_end);
311
0
        break;
312
0
    }
313
0
  }
314
315
0
  fclose(fp);
316
317
0
  if (spool < 2) {
318
0
    *poi = '\0';
319
0
    spoolbuf = zend_string_truncate(spoolbuf, poi - (unsigned char*)ZSTR_VAL(spoolbuf), 0);
320
0
    RETURN_NEW_STR(spoolbuf);
321
0
  } else {
322
0
    RETURN_TRUE;
323
0
  }
324
0
}
325
/* }}} */
326
327
/* {{{ Parse binary IPTC-data into associative array */
328
PHP_FUNCTION(iptcparse)
329
0
{
330
0
  size_t inx = 0, len;
331
0
  unsigned int tagsfound = 0;
332
0
  unsigned char *buffer, recnum, dataset;
333
0
  char *str, key[16];
334
0
  size_t str_len;
335
336
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
337
0
    Z_PARAM_STRING(str, str_len)
338
0
  ZEND_PARSE_PARAMETERS_END();
339
340
0
  buffer = (unsigned char *)str;
341
342
0
  while (inx < str_len) { /* find 1st tag */
343
0
    if ((buffer[inx] == 0x1c) && ((buffer[inx+1] == 0x01) || (buffer[inx+1] == 0x02))){
344
0
      break;
345
0
    } else {
346
0
      inx++;
347
0
    }
348
0
  }
349
350
0
  while (inx < str_len) {
351
0
    if (buffer[ inx++ ] != 0x1c) {
352
0
      break;   /* we ran against some data which does not conform to IPTC - stop parsing! */
353
0
    }
354
355
0
    if ((inx + 4) >= str_len)
356
0
      break;
357
358
0
    dataset = buffer[ inx++ ];
359
0
    recnum = buffer[ inx++ ];
360
361
0
    if (buffer[ inx ] & (unsigned char) 0x80) { /* long tag */
362
0
      if((inx+6) >= str_len) {
363
0
        break;
364
0
      }
365
0
      len = (((zend_long) buffer[ inx + 2 ]) << 24) + (((zend_long) buffer[ inx + 3 ]) << 16) +
366
0
          (((zend_long) buffer[ inx + 4 ]) <<  8) + (((zend_long) buffer[ inx + 5 ]));
367
0
      inx += 6;
368
0
    } else { /* short tag */
369
0
      len = (((unsigned short) buffer[ inx ])<<8) | (unsigned short)buffer[ inx+1 ];
370
0
      inx += 2;
371
0
    }
372
373
0
    if ((len > str_len) || (inx + len) > str_len) {
374
0
      break;
375
0
    }
376
377
0
    snprintf(key, sizeof(key), "%d#%03d", (unsigned int) dataset, (unsigned int) recnum);
378
379
0
    if (tagsfound == 0) { /* found the 1st tag - initialize the return array */
380
0
      array_init(return_value);
381
0
    }
382
383
0
    zval *element = zend_hash_str_lookup(Z_ARRVAL_P(return_value), key, strlen(key));
384
0
    if (Z_ISNULL_P(element)) {
385
0
      array_init(element);
386
0
    }
387
388
0
    add_next_index_stringl(element, (char *) buffer+inx, len);
389
0
    inx += len;
390
0
    tagsfound++;
391
0
  }
392
393
0
  if (! tagsfound) {
394
0
    RETURN_FALSE;
395
0
  }
396
0
}
397
/* }}} */