/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 | | /* }}} */ |