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