/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 | | /* {{{ Embed binary IPTC data into a JPEG image. */ |
177 | | PHP_FUNCTION(iptcembed) |
178 | 0 | { |
179 | 0 | char psheader[] = "\xFF\xED\0\0Photoshop 3.0\08BIM\x04\x04\0\0\0\0"; |
180 | 0 | char *iptcdata, *jpeg_file; |
181 | 0 | size_t iptcdata_len, jpeg_file_len; |
182 | 0 | zend_long spool = 0; |
183 | 0 | FILE *fp; |
184 | 0 | unsigned int marker, done = 0; |
185 | 0 | size_t inx; |
186 | 0 | zend_string *spoolbuf = NULL; |
187 | 0 | unsigned char *poi = NULL; |
188 | 0 | unsigned char *spoolbuf_end = NULL; |
189 | 0 | zend_stat_t sb = {0}; |
190 | 0 | bool written = 0; |
191 | |
|
192 | 0 | ZEND_PARSE_PARAMETERS_START(2, 3) |
193 | 0 | Z_PARAM_STRING(iptcdata, iptcdata_len) |
194 | 0 | Z_PARAM_PATH(jpeg_file, jpeg_file_len) |
195 | 0 | Z_PARAM_OPTIONAL |
196 | 0 | Z_PARAM_LONG(spool) |
197 | 0 | ZEND_PARSE_PARAMETERS_END(); |
198 | | |
199 | 0 | if (php_check_open_basedir(jpeg_file)) { |
200 | 0 | RETURN_FALSE; |
201 | 0 | } |
202 | | |
203 | 0 | if (iptcdata_len >= SIZE_MAX - sizeof(psheader) - 1025) { |
204 | 0 | zend_argument_value_error(1, "is too large"); |
205 | 0 | RETURN_THROWS(); |
206 | 0 | } |
207 | | |
208 | 0 | if ((fp = VCWD_FOPEN(jpeg_file, "rb")) == 0) { |
209 | 0 | php_error_docref(NULL, E_WARNING, "Unable to open %s", jpeg_file); |
210 | 0 | RETURN_FALSE; |
211 | 0 | } |
212 | | |
213 | 0 | if (spool < 2) { |
214 | 0 | if (zend_fstat(fileno(fp), &sb) != 0) { |
215 | 0 | fclose(fp); |
216 | 0 | RETURN_FALSE; |
217 | 0 | } |
218 | | |
219 | 0 | spoolbuf = zend_string_safe_alloc(1, iptcdata_len + sizeof(psheader) + 1024 + 1, sb.st_size, 0); |
220 | 0 | poi = (unsigned char*)ZSTR_VAL(spoolbuf); |
221 | 0 | spoolbuf_end = poi + ZSTR_LEN(spoolbuf); |
222 | 0 | memset(poi, 0, iptcdata_len + sizeof(psheader) + sb.st_size + 1024 + 1); |
223 | 0 | } |
224 | | |
225 | 0 | if (php_iptc_get1(fp, spool, poi?&poi:0, spoolbuf_end) != 0xFF) { |
226 | 0 | fclose(fp); |
227 | 0 | if (spoolbuf) { |
228 | 0 | zend_string_efree(spoolbuf); |
229 | 0 | } |
230 | 0 | RETURN_FALSE; |
231 | 0 | } |
232 | | |
233 | 0 | if (php_iptc_get1(fp, spool, poi?&poi:0, spoolbuf_end) != 0xD8) { |
234 | 0 | err: |
235 | 0 | fclose(fp); |
236 | 0 | if (spoolbuf) { |
237 | 0 | zend_string_efree(spoolbuf); |
238 | 0 | } |
239 | 0 | RETURN_FALSE; |
240 | 0 | } |
241 | | |
242 | 0 | while (!done) { |
243 | 0 | marker = php_iptc_next_marker(fp, spool, poi?&poi:0, spoolbuf_end); |
244 | |
|
245 | 0 | if (marker == M_EOI) { /* EOF */ |
246 | 0 | break; |
247 | 0 | } else if (marker != M_APP13) { |
248 | 0 | if (php_iptc_put1(fp, spool, (unsigned char)marker, poi?&poi:0, spoolbuf_end) < 0) { |
249 | 0 | goto err; |
250 | 0 | } |
251 | 0 | } |
252 | | |
253 | 0 | switch (marker) { |
254 | 0 | case M_APP13: |
255 | | /* we are going to write a new APP13 marker, so don't output the old one */ |
256 | 0 | php_iptc_skip_variable(fp, 0, 0, spoolbuf_end); |
257 | 0 | fgetc(fp); /* skip already copied 0xFF byte */ |
258 | 0 | php_iptc_read_remaining(fp, spool, poi?&poi:0, spoolbuf_end); |
259 | 0 | done = 1; |
260 | 0 | break; |
261 | | |
262 | 0 | case M_APP0: |
263 | | /* APP0 is in each and every JPEG, so when we hit APP0 we insert our new APP13! */ |
264 | 0 | case M_APP1: |
265 | 0 | if (written) { |
266 | | /* don't try to write the data twice */ |
267 | 0 | break; |
268 | 0 | } |
269 | 0 | written = 1; |
270 | |
|
271 | 0 | php_iptc_skip_variable(fp, spool, poi?&poi:0, spoolbuf_end); |
272 | |
|
273 | 0 | if (iptcdata_len & 1) { |
274 | 0 | iptcdata_len++; /* make the length even */ |
275 | 0 | } |
276 | |
|
277 | 0 | psheader[ 2 ] = (char) ((iptcdata_len+28)>>8); |
278 | 0 | psheader[ 3 ] = (iptcdata_len+28)&0xff; |
279 | |
|
280 | 0 | for (inx = 0; inx < 28; inx++) { |
281 | 0 | if (php_iptc_put1(fp, spool, psheader[inx], poi?&poi:0, spoolbuf_end) < 0) { |
282 | 0 | goto err; |
283 | 0 | } |
284 | 0 | } |
285 | | |
286 | 0 | if (php_iptc_put1(fp, spool, (unsigned char)(iptcdata_len>>8), poi?&poi:0, spoolbuf_end) < 0) { |
287 | 0 | goto err; |
288 | 0 | } |
289 | 0 | if (php_iptc_put1(fp, spool, (unsigned char)(iptcdata_len&0xff), poi?&poi:0, spoolbuf_end) < 0) { |
290 | 0 | goto err; |
291 | 0 | } |
292 | | |
293 | 0 | for (inx = 0; inx < iptcdata_len; inx++) { |
294 | 0 | if (php_iptc_put1(fp, spool, iptcdata[inx], poi?&poi:0, spoolbuf_end) < 0) { |
295 | 0 | goto err; |
296 | 0 | } |
297 | 0 | } |
298 | 0 | break; |
299 | | |
300 | 0 | case M_SOS: |
301 | | /* we hit data, no more marker-inserting can be done! */ |
302 | 0 | php_iptc_read_remaining(fp, spool, poi?&poi:0, spoolbuf_end); |
303 | 0 | done = 1; |
304 | 0 | break; |
305 | | |
306 | 0 | default: |
307 | 0 | php_iptc_skip_variable(fp, spool, poi?&poi:0, spoolbuf_end); |
308 | 0 | break; |
309 | 0 | } |
310 | 0 | } |
311 | | |
312 | 0 | fclose(fp); |
313 | |
|
314 | 0 | if (spool < 2) { |
315 | 0 | *poi = '\0'; |
316 | 0 | spoolbuf = zend_string_truncate(spoolbuf, poi - (unsigned char*)ZSTR_VAL(spoolbuf), 0); |
317 | 0 | RETURN_NEW_STR(spoolbuf); |
318 | 0 | } else { |
319 | 0 | RETURN_TRUE; |
320 | 0 | } |
321 | 0 | } |
322 | | /* }}} */ |
323 | | |
324 | | /* {{{ Parse binary IPTC-data into associative array */ |
325 | | PHP_FUNCTION(iptcparse) |
326 | 0 | { |
327 | 0 | size_t inx = 0, len; |
328 | 0 | unsigned int tagsfound = 0; |
329 | 0 | unsigned char *buffer, recnum, dataset; |
330 | 0 | char *str, key[16]; |
331 | 0 | size_t str_len; |
332 | |
|
333 | 0 | ZEND_PARSE_PARAMETERS_START(1, 1) |
334 | 0 | Z_PARAM_STRING(str, str_len) |
335 | 0 | ZEND_PARSE_PARAMETERS_END(); |
336 | | |
337 | 0 | buffer = (unsigned char *)str; |
338 | |
|
339 | 0 | while (inx < str_len) { /* find 1st tag */ |
340 | 0 | if ((buffer[inx] == 0x1c) && ((buffer[inx+1] == 0x01) || (buffer[inx+1] == 0x02))){ |
341 | 0 | break; |
342 | 0 | } else { |
343 | 0 | inx++; |
344 | 0 | } |
345 | 0 | } |
346 | |
|
347 | 0 | while (inx < str_len) { |
348 | 0 | if (buffer[ inx++ ] != 0x1c) { |
349 | 0 | break; /* we ran against some data which does not conform to IPTC - stop parsing! */ |
350 | 0 | } |
351 | | |
352 | 0 | if ((inx + 4) >= str_len) |
353 | 0 | break; |
354 | | |
355 | 0 | dataset = buffer[ inx++ ]; |
356 | 0 | recnum = buffer[ inx++ ]; |
357 | |
|
358 | 0 | if (buffer[ inx ] & (unsigned char) 0x80) { /* long tag */ |
359 | 0 | if((inx+6) >= str_len) { |
360 | 0 | break; |
361 | 0 | } |
362 | 0 | len = (((zend_long) buffer[ inx + 2 ]) << 24) + (((zend_long) buffer[ inx + 3 ]) << 16) + |
363 | 0 | (((zend_long) buffer[ inx + 4 ]) << 8) + (((zend_long) buffer[ inx + 5 ])); |
364 | 0 | inx += 6; |
365 | 0 | } else { /* short tag */ |
366 | 0 | len = (((unsigned short) buffer[ inx ])<<8) | (unsigned short)buffer[ inx+1 ]; |
367 | 0 | inx += 2; |
368 | 0 | } |
369 | | |
370 | 0 | if ((len > str_len) || (inx + len) > str_len) { |
371 | 0 | break; |
372 | 0 | } |
373 | | |
374 | 0 | snprintf(key, sizeof(key), "%d#%03d", (unsigned int) dataset, (unsigned int) recnum); |
375 | |
|
376 | 0 | if (tagsfound == 0) { /* found the 1st tag - initialize the return array */ |
377 | 0 | array_init(return_value); |
378 | 0 | } |
379 | |
|
380 | 0 | zval *element = zend_hash_str_lookup(Z_ARRVAL_P(return_value), key, strlen(key)); |
381 | 0 | if (Z_ISNULL_P(element)) { |
382 | 0 | array_init(element); |
383 | 0 | } |
384 | |
|
385 | 0 | add_next_index_stringl(element, (char *) buffer+inx, len); |
386 | 0 | inx += len; |
387 | 0 | tagsfound++; |
388 | 0 | } |
389 | |
|
390 | 0 | if (! tagsfound) { |
391 | 0 | RETURN_FALSE; |
392 | 0 | } |
393 | 0 | } |
394 | | /* }}} */ |