Coverage Report

Created: 2025-06-13 06:43

/src/php-src/ext/standard/iptc.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: 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)
77
0
{
78
0
  if (spool > 0)
79
0
    PUTC(c);
80
81
0
  if (spoolbuf) *(*spoolbuf)++ = c;
82
83
0
  return c;
84
0
}
85
/* }}} */
86
87
/* {{{ php_iptc_get1 */
88
static int php_iptc_get1(FILE *fp, int spool, unsigned char **spoolbuf)
89
0
{
90
0
  int c;
91
0
  char cc;
92
93
0
  c = getc(fp);
94
95
0
  if (c == EOF) return EOF;
96
97
0
  if (spool > 0) {
98
0
    cc = c;
99
0
    PUTC(cc);
100
0
  }
101
102
0
  if (spoolbuf) *(*spoolbuf)++ = c;
103
104
0
  return c;
105
0
}
106
/* }}} */
107
108
/* {{{ php_iptc_read_remaining */
109
static int php_iptc_read_remaining(FILE *fp, int spool, unsigned char **spoolbuf)
110
0
{
111
0
  while (php_iptc_get1(fp, spool, spoolbuf) != EOF) continue;
112
113
0
  return M_EOI;
114
0
}
115
/* }}} */
116
117
/* {{{ php_iptc_skip_variable */
118
static int php_iptc_skip_variable(FILE *fp, int spool, unsigned char **spoolbuf)
119
0
{
120
0
  unsigned int  length;
121
0
  int c1, c2;
122
123
0
  if ((c1 = php_iptc_get1(fp, spool, spoolbuf)) == EOF) return M_EOI;
124
125
0
  if ((c2 = php_iptc_get1(fp, spool, spoolbuf)) == EOF) return M_EOI;
126
127
0
  length = (((unsigned char) c1) << 8) + ((unsigned char) c2);
128
129
0
  length -= 2;
130
131
0
  while (length--)
132
0
    if (php_iptc_get1(fp, spool, spoolbuf) == EOF) return M_EOI;
133
134
0
  return 0;
135
0
}
136
/* }}} */
137
138
/* {{{ php_iptc_next_marker */
139
static int php_iptc_next_marker(FILE *fp, int spool, unsigned char **spoolbuf)
140
0
{
141
0
  int c;
142
143
  /* skip unimportant stuff */
144
145
0
  c = php_iptc_get1(fp, spool, spoolbuf);
146
147
0
  if (c == EOF) return M_EOI;
148
149
0
  while (c != 0xff) {
150
0
    if ((c = php_iptc_get1(fp, spool, spoolbuf)) == EOF)
151
0
      return M_EOI; /* we hit EOF */
152
0
  }
153
154
  /* get marker byte, swallowing possible padding */
155
0
  do {
156
0
    c = php_iptc_get1(fp, 0, 0);
157
0
    if (c == EOF)
158
0
      return M_EOI;       /* we hit EOF */
159
0
    else
160
0
    if (c == 0xff)
161
0
      php_iptc_put1(fp, spool, (unsigned char)c, spoolbuf);
162
0
  } while (c == 0xff);
163
164
0
  return (unsigned int) c;
165
0
}
166
/* }}} */
167
168
static char psheader[] = "\xFF\xED\0\0Photoshop 3.0\08BIM\x04\x04\0\0\0\0";
169
170
/* {{{ Embed binary IPTC data into a JPEG image. */
171
PHP_FUNCTION(iptcembed)
172
0
{
173
0
  char *iptcdata, *jpeg_file;
174
0
  size_t iptcdata_len, jpeg_file_len;
175
0
  zend_long spool = 0;
176
0
  FILE *fp;
177
0
  unsigned int marker, done = 0;
178
0
  size_t inx;
179
0
  zend_string *spoolbuf = NULL;
180
0
  unsigned char *poi = NULL;
181
0
  zend_stat_t sb = {0};
182
0
  bool written = 0;
183
184
0
  ZEND_PARSE_PARAMETERS_START(2, 3)
185
0
    Z_PARAM_STRING(iptcdata, iptcdata_len)
186
0
    Z_PARAM_PATH(jpeg_file, jpeg_file_len)
187
0
    Z_PARAM_OPTIONAL
188
0
    Z_PARAM_LONG(spool)
189
0
  ZEND_PARSE_PARAMETERS_END();
190
191
0
  if (php_check_open_basedir(jpeg_file)) {
192
0
    RETURN_FALSE;
193
0
  }
194
195
0
  if (iptcdata_len >= SIZE_MAX - sizeof(psheader) - 1025) {
196
0
    zend_argument_value_error(1, "is too large");
197
0
    RETURN_THROWS();
198
0
  }
199
200
0
  if ((fp = VCWD_FOPEN(jpeg_file, "rb")) == 0) {
201
0
    php_error_docref(NULL, E_WARNING, "Unable to open %s", jpeg_file);
202
0
    RETURN_FALSE;
203
0
  }
204
205
0
  if (spool < 2) {
206
0
    if (zend_fstat(fileno(fp), &sb) != 0) {
207
0
      fclose(fp);
208
0
      RETURN_FALSE;
209
0
    }
210
211
0
    spoolbuf = zend_string_safe_alloc(1, iptcdata_len + sizeof(psheader) + 1024 + 1, sb.st_size, 0);
212
0
    poi = (unsigned char*)ZSTR_VAL(spoolbuf);
213
0
    memset(poi, 0, iptcdata_len + sizeof(psheader) + sb.st_size + 1024 + 1);
214
0
  }
215
216
0
  if (php_iptc_get1(fp, spool, poi?&poi:0) != 0xFF) {
217
0
    fclose(fp);
218
0
    if (spoolbuf) {
219
0
      zend_string_efree(spoolbuf);
220
0
    }
221
0
    RETURN_FALSE;
222
0
  }
223
224
0
  if (php_iptc_get1(fp, spool, poi?&poi:0) != 0xD8) {
225
0
    fclose(fp);
226
0
    if (spoolbuf) {
227
0
      zend_string_efree(spoolbuf);
228
0
    }
229
0
    RETURN_FALSE;
230
0
  }
231
232
0
  while (!done) {
233
0
    marker = php_iptc_next_marker(fp, spool, poi?&poi:0);
234
235
0
    if (marker == M_EOI) { /* EOF */
236
0
      break;
237
0
    } else if (marker != M_APP13) {
238
0
      php_iptc_put1(fp, spool, (unsigned char)marker, poi?&poi:0);
239
0
    }
240
241
0
    switch (marker) {
242
0
      case M_APP13:
243
        /* we are going to write a new APP13 marker, so don't output the old one */
244
0
        php_iptc_skip_variable(fp, 0, 0);
245
0
        fgetc(fp); /* skip already copied 0xFF byte */
246
0
        php_iptc_read_remaining(fp, spool, poi?&poi:0);
247
0
        done = 1;
248
0
        break;
249
250
0
      case M_APP0:
251
        /* APP0 is in each and every JPEG, so when we hit APP0 we insert our new APP13! */
252
0
      case M_APP1:
253
0
        if (written) {
254
          /* don't try to write the data twice */
255
0
          break;
256
0
        }
257
0
        written = 1;
258
259
0
        php_iptc_skip_variable(fp, spool, poi?&poi:0);
260
261
0
        if (iptcdata_len & 1) {
262
0
          iptcdata_len++; /* make the length even */
263
0
        }
264
265
0
        psheader[ 2 ] = (char) ((iptcdata_len+28)>>8);
266
0
        psheader[ 3 ] = (iptcdata_len+28)&0xff;
267
268
0
        for (inx = 0; inx < 28; inx++) {
269
0
          php_iptc_put1(fp, spool, psheader[inx], poi?&poi:0);
270
0
        }
271
272
0
        php_iptc_put1(fp, spool, (unsigned char)(iptcdata_len>>8), poi?&poi:0);
273
0
        php_iptc_put1(fp, spool, (unsigned char)(iptcdata_len&0xff), poi?&poi:0);
274
275
0
        for (inx = 0; inx < iptcdata_len; inx++) {
276
0
          php_iptc_put1(fp, spool, iptcdata[inx], poi?&poi:0);
277
0
        }
278
0
        break;
279
280
0
      case M_SOS:
281
        /* we hit data, no more marker-inserting can be done! */
282
0
        php_iptc_read_remaining(fp, spool, poi?&poi:0);
283
0
        done = 1;
284
0
        break;
285
286
0
      default:
287
0
        php_iptc_skip_variable(fp, spool, poi?&poi:0);
288
0
        break;
289
0
    }
290
0
  }
291
292
0
  fclose(fp);
293
294
0
  if (spool < 2) {
295
0
    spoolbuf = zend_string_truncate(spoolbuf, poi - (unsigned char*)ZSTR_VAL(spoolbuf), 0);
296
0
    RETURN_NEW_STR(spoolbuf);
297
0
  } else {
298
0
    RETURN_TRUE;
299
0
  }
300
0
}
301
/* }}} */
302
303
/* {{{ Parse binary IPTC-data into associative array */
304
PHP_FUNCTION(iptcparse)
305
0
{
306
0
  size_t inx = 0, len;
307
0
  unsigned int tagsfound = 0;
308
0
  unsigned char *buffer, recnum, dataset;
309
0
  char *str, key[16];
310
0
  size_t str_len;
311
0
  zval values, *element;
312
313
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
314
0
    Z_PARAM_STRING(str, str_len)
315
0
  ZEND_PARSE_PARAMETERS_END();
316
317
0
  buffer = (unsigned char *)str;
318
319
0
  while (inx < str_len) { /* find 1st tag */
320
0
    if ((buffer[inx] == 0x1c) && ((buffer[inx+1] == 0x01) || (buffer[inx+1] == 0x02))){
321
0
      break;
322
0
    } else {
323
0
      inx++;
324
0
    }
325
0
  }
326
327
0
  while (inx < str_len) {
328
0
    if (buffer[ inx++ ] != 0x1c) {
329
0
      break;   /* we ran against some data which does not conform to IPTC - stop parsing! */
330
0
    }
331
332
0
    if ((inx + 4) >= str_len)
333
0
      break;
334
335
0
    dataset = buffer[ inx++ ];
336
0
    recnum = buffer[ inx++ ];
337
338
0
    if (buffer[ inx ] & (unsigned char) 0x80) { /* long tag */
339
0
      if((inx+6) >= str_len) {
340
0
        break;
341
0
      }
342
0
      len = (((zend_long) buffer[ inx + 2 ]) << 24) + (((zend_long) buffer[ inx + 3 ]) << 16) +
343
0
          (((zend_long) buffer[ inx + 4 ]) <<  8) + (((zend_long) buffer[ inx + 5 ]));
344
0
      inx += 6;
345
0
    } else { /* short tag */
346
0
      len = (((unsigned short) buffer[ inx ])<<8) | (unsigned short)buffer[ inx+1 ];
347
0
      inx += 2;
348
0
    }
349
350
0
    if ((len > str_len) || (inx + len) > str_len) {
351
0
      break;
352
0
    }
353
354
0
    snprintf(key, sizeof(key), "%d#%03d", (unsigned int) dataset, (unsigned int) recnum);
355
356
0
    if (tagsfound == 0) { /* found the 1st tag - initialize the return array */
357
0
      array_init(return_value);
358
0
    }
359
360
0
    if ((element = zend_hash_str_find(Z_ARRVAL_P(return_value), key, strlen(key))) == NULL) {
361
0
      array_init(&values);
362
363
0
      element = zend_hash_str_update(Z_ARRVAL_P(return_value), key, strlen(key), &values);
364
0
    }
365
366
0
    add_next_index_stringl(element, (char *) buffer+inx, len);
367
0
    inx += len;
368
0
    tagsfound++;
369
0
  }
370
371
0
  if (! tagsfound) {
372
0
    RETURN_FALSE;
373
0
  }
374
0
}
375
/* }}} */