Coverage Report

Created: 2026-06-02 06:39

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