Coverage Report

Created: 2022-10-14 11:23

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