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