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