Coverage Report

Created: 2022-02-19 20:31

/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
   | http://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;
182
0
  zend_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
    php_error_docref(NULL, E_WARNING, "IPTC data too large");
197
0
    RETURN_FALSE;
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
    zend_fstat(fileno(fp), &sb);
207
208
0
    spoolbuf = zend_string_safe_alloc(1, iptcdata_len + sizeof(psheader) + 1024 + 1, sb.st_size, 0);
209
0
    poi = (unsigned char*)ZSTR_VAL(spoolbuf);
210
0
    memset(poi, 0, iptcdata_len + sizeof(psheader) + sb.st_size + 1024 + 1);
211
0
  }
212
213
0
  if (php_iptc_get1(fp, spool, poi?&poi:0) != 0xFF) {
214
0
    fclose(fp);
215
0
    if (spoolbuf) {
216
0
      zend_string_efree(spoolbuf);
217
0
    }
218
0
    RETURN_FALSE;
219
0
  }
220
221
0
  if (php_iptc_get1(fp, spool, poi?&poi:0) != 0xD8) {
222
0
    fclose(fp);
223
0
    if (spoolbuf) {
224
0
      zend_string_efree(spoolbuf);
225
0
    }
226
0
    RETURN_FALSE;
227
0
  }
228
229
0
  while (!done) {
230
0
    marker = php_iptc_next_marker(fp, spool, poi?&poi:0);
231
232
0
    if (marker == M_EOI) { /* EOF */
233
0
      break;
234
0
    } else if (marker != M_APP13) {
235
0
      php_iptc_put1(fp, spool, (unsigned char)marker, poi?&poi:0);
236
0
    }
237
238
0
    switch (marker) {
239
0
      case M_APP13:
240
        /* we are going to write a new APP13 marker, so don't output the old one */
241
0
        php_iptc_skip_variable(fp, 0, 0);
242
0
        fgetc(fp); /* skip already copied 0xFF byte */
243
0
        php_iptc_read_remaining(fp, spool, poi?&poi:0);
244
0
        done = 1;
245
0
        break;
246
247
0
      case M_APP0:
248
        /* APP0 is in each and every JPEG, so when we hit APP0 we insert our new APP13! */
249
0
      case M_APP1:
250
0
        if (written) {
251
          /* don't try to write the data twice */
252
0
          break;
253
0
        }
254
0
        written = 1;
255
256
0
        php_iptc_skip_variable(fp, spool, poi?&poi:0);
257
258
0
        if (iptcdata_len & 1) {
259
0
          iptcdata_len++; /* make the length even */
260
0
        }
261
262
0
        psheader[ 2 ] = (char) ((iptcdata_len+28)>>8);
263
0
        psheader[ 3 ] = (iptcdata_len+28)&0xff;
264
265
0
        for (inx = 0; inx < 28; inx++) {
266
0
          php_iptc_put1(fp, spool, psheader[inx], poi?&poi:0);
267
0
        }
268
269
0
        php_iptc_put1(fp, spool, (unsigned char)(iptcdata_len>>8), poi?&poi:0);
270
0
        php_iptc_put1(fp, spool, (unsigned char)(iptcdata_len&0xff), poi?&poi:0);
271
272
0
        for (inx = 0; inx < iptcdata_len; inx++) {
273
0
          php_iptc_put1(fp, spool, iptcdata[inx], poi?&poi:0);
274
0
        }
275
0
        break;
276
277
0
      case M_SOS:
278
        /* we hit data, no more marker-inserting can be done! */
279
0
        php_iptc_read_remaining(fp, spool, poi?&poi:0);
280
0
        done = 1;
281
0
        break;
282
283
0
      default:
284
0
        php_iptc_skip_variable(fp, spool, poi?&poi:0);
285
0
        break;
286
0
    }
287
0
  }
288
289
0
  fclose(fp);
290
291
0
  if (spool < 2) {
292
0
    spoolbuf = zend_string_truncate(spoolbuf, poi - (unsigned char*)ZSTR_VAL(spoolbuf), 0);
293
0
    RETURN_NEW_STR(spoolbuf);
294
0
  } else {
295
0
    RETURN_TRUE;
296
0
  }
297
0
}
298
/* }}} */
299
300
/* {{{ Parse binary IPTC-data into associative array */
301
PHP_FUNCTION(iptcparse)
302
0
{
303
0
  size_t inx = 0, len;
304
0
  unsigned int tagsfound = 0;
305
0
  unsigned char *buffer, recnum, dataset;
306
0
  char *str, key[16];
307
0
  size_t str_len;
308
0
  zval values, *element;
309
310
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
311
0
    Z_PARAM_STRING(str, str_len)
312
0
  ZEND_PARSE_PARAMETERS_END();
313
314
0
  buffer = (unsigned char *)str;
315
316
0
  while (inx < str_len) { /* find 1st tag */
317
0
    if ((buffer[inx] == 0x1c) && ((buffer[inx+1] == 0x01) || (buffer[inx+1] == 0x02))){
318
0
      break;
319
0
    } else {
320
0
      inx++;
321
0
    }
322
0
  }
323
324
0
  while (inx < str_len) {
325
0
    if (buffer[ inx++ ] != 0x1c) {
326
0
      break;   /* we ran against some data which does not conform to IPTC - stop parsing! */
327
0
    }
328
329
0
    if ((inx + 4) >= str_len)
330
0
      break;
331
332
0
    dataset = buffer[ inx++ ];
333
0
    recnum = buffer[ inx++ ];
334
335
0
    if (buffer[ inx ] & (unsigned char) 0x80) { /* long tag */
336
0
      if((inx+6) >= str_len) {
337
0
        break;
338
0
      }
339
0
      len = (((zend_long) buffer[ inx + 2 ]) << 24) + (((zend_long) buffer[ inx + 3 ]) << 16) +
340
0
          (((zend_long) buffer[ inx + 4 ]) <<  8) + (((zend_long) buffer[ inx + 5 ]));
341
0
      inx += 6;
342
0
    } else { /* short tag */
343
0
      len = (((unsigned short) buffer[ inx ])<<8) | (unsigned short)buffer[ inx+1 ];
344
0
      inx += 2;
345
0
    }
346
347
0
    if ((len > str_len) || (inx + len) > str_len) {
348
0
      break;
349
0
    }
350
351
0
    snprintf(key, sizeof(key), "%d#%03d", (unsigned int) dataset, (unsigned int) recnum);
352
353
0
    if (tagsfound == 0) { /* found the 1st tag - initialize the return array */
354
0
      array_init(return_value);
355
0
    }
356
357
0
    if ((element = zend_hash_str_find(Z_ARRVAL_P(return_value), key, strlen(key))) == NULL) {
358
0
      array_init(&values);
359
360
0
      element = zend_hash_str_update(Z_ARRVAL_P(return_value), key, strlen(key), &values);
361
0
    }
362
363
0
    add_next_index_stringl(element, (char *) buffer+inx, len);
364
0
    inx += len;
365
0
    tagsfound++;
366
0
  }
367
368
0
  if (! tagsfound) {
369
0
    RETURN_FALSE;
370
0
  }
371
0
}
372
/* }}} */