1 | /* |
2 | +----------------------------------------------------------------------+ |
3 | | PHP Version 5 | |
4 | +----------------------------------------------------------------------+ |
5 | | Copyright (c) 1997-2015 The PHP Group | |
6 | +----------------------------------------------------------------------+ |
7 | | This source file is subject to version 3.01 of the PHP license, | |
8 | | that is bundled with this package in the file LICENSE, and is | |
9 | | available through the world-wide-web at the following url: | |
10 | | http://www.php.net/license/3_01.txt | |
11 | | If you did not receive a copy of the PHP license and are unable to | |
12 | | obtain it through the world-wide-web, please send a note to | |
13 | | license@php.net so we can mail you a copy immediately. | |
14 | +----------------------------------------------------------------------+ |
15 | | Author: Thies C. Arntzen <thies@thieso.net> | |
16 | +----------------------------------------------------------------------+ |
17 | */ |
18 | |
19 | /* $Id$ */ |
20 | |
21 | /* |
22 | * Functions to parse & compse IPTC data. |
23 | * PhotoShop >= 3.0 can read and write textual data to JPEG files. |
24 | * ... more to come ..... |
25 | * |
26 | * i know, parts of this is now duplicated in image.c |
27 | * but in this case i think it's okay! |
28 | */ |
29 | |
30 | /* |
31 | * TODO: |
32 | * - add IPTC translation table |
33 | */ |
34 | |
35 | #include "php.h" |
36 | #include "php_iptc.h" |
37 | #include "ext/standard/head.h" |
38 | |
39 | #include <sys/stat.h> |
40 | |
41 | |
42 | /* some defines for the different JPEG block types */ |
43 | #define M_SOF0 0xC0 /* Start Of Frame N */ |
44 | #define M_SOF1 0xC1 /* N indicates which compression process */ |
45 | #define M_SOF2 0xC2 /* Only SOF0-SOF2 are now in common use */ |
46 | #define M_SOF3 0xC3 |
47 | #define M_SOF5 0xC5 /* NB: codes C4 and CC are NOT SOF markers */ |
48 | #define M_SOF6 0xC6 |
49 | #define M_SOF7 0xC7 |
50 | #define M_SOF9 0xC9 |
51 | #define M_SOF10 0xCA |
52 | #define M_SOF11 0xCB |
53 | #define M_SOF13 0xCD |
54 | #define M_SOF14 0xCE |
55 | #define M_SOF15 0xCF |
56 | #define M_SOI 0xD8 |
57 | #define M_EOI 0xD9 /* End Of Image (end of datastream) */ |
58 | #define M_SOS 0xDA /* Start Of Scan (begins compressed data) */ |
59 | #define M_APP0 0xe0 |
60 | #define M_APP1 0xe1 |
61 | #define M_APP2 0xe2 |
62 | #define M_APP3 0xe3 |
63 | #define M_APP4 0xe4 |
64 | #define M_APP5 0xe5 |
65 | #define M_APP6 0xe6 |
66 | #define M_APP7 0xe7 |
67 | #define M_APP8 0xe8 |
68 | #define M_APP9 0xe9 |
69 | #define M_APP10 0xea |
70 | #define M_APP11 0xeb |
71 | #define M_APP12 0xec |
72 | #define M_APP13 0xed |
73 | #define M_APP14 0xee |
74 | #define M_APP15 0xef |
75 | |
76 | /* {{{ php_iptc_put1 |
77 | */ |
78 | static int php_iptc_put1(FILE *fp, int spool, unsigned char c, unsigned char **spoolbuf TSRMLS_DC) |
79 | { |
80 | if (spool > 0) |
81 | PUTC(c); |
82 | |
83 | if (spoolbuf) *(*spoolbuf)++ = c; |
84 | |
85 | return c; |
86 | } |
87 | /* }}} */ |
88 | |
89 | /* {{{ php_iptc_get1 |
90 | */ |
91 | static int php_iptc_get1(FILE *fp, int spool, unsigned char **spoolbuf TSRMLS_DC) |
92 | { |
93 | int c; |
94 | char cc; |
95 | |
96 | c = getc(fp); |
97 | |
98 | if (c == EOF) return EOF; |
99 | |
100 | if (spool > 0) { |
101 | cc = c; |
102 | PUTC(cc); |
103 | } |
104 | |
105 | if (spoolbuf) *(*spoolbuf)++ = c; |
106 | |
107 | return c; |
108 | } |
109 | /* }}} */ |
110 | |
111 | /* {{{ php_iptc_read_remaining |
112 | */ |
113 | static int php_iptc_read_remaining(FILE *fp, int spool, unsigned char **spoolbuf TSRMLS_DC) |
114 | { |
115 | while (php_iptc_get1(fp, spool, spoolbuf TSRMLS_CC) != EOF) continue; |
116 | |
117 | return M_EOI; |
118 | } |
119 | /* }}} */ |
120 | |
121 | /* {{{ php_iptc_skip_variable |
122 | */ |
123 | static int php_iptc_skip_variable(FILE *fp, int spool, unsigned char **spoolbuf TSRMLS_DC) |
124 | { |
125 | unsigned int length; |
126 | int c1, c2; |
127 | |
128 | if ((c1 = php_iptc_get1(fp, spool, spoolbuf TSRMLS_CC)) == EOF) return M_EOI; |
129 | |
130 | if ((c2 = php_iptc_get1(fp, spool, spoolbuf TSRMLS_CC)) == EOF) return M_EOI; |
131 | |
132 | length = (((unsigned char) c1) << 8) + ((unsigned char) c2); |
133 | |
134 | length -= 2; |
135 | |
136 | while (length--) |
137 | if (php_iptc_get1(fp, spool, spoolbuf TSRMLS_CC) == EOF) return M_EOI; |
138 | |
139 | return 0; |
140 | } |
141 | /* }}} */ |
142 | |
143 | /* {{{ php_iptc_next_marker |
144 | */ |
145 | static int php_iptc_next_marker(FILE *fp, int spool, unsigned char **spoolbuf TSRMLS_DC) |
146 | { |
147 | int c; |
148 | |
149 | /* skip unimportant stuff */ |
150 | |
151 | c = php_iptc_get1(fp, spool, spoolbuf TSRMLS_CC); |
152 | |
153 | if (c == EOF) return M_EOI; |
154 | |
155 | while (c != 0xff) { |
156 | if ((c = php_iptc_get1(fp, spool, spoolbuf TSRMLS_CC)) == EOF) |
157 | return M_EOI; /* we hit EOF */ |
158 | } |
159 | |
160 | /* get marker byte, swallowing possible padding */ |
161 | do { |
162 | c = php_iptc_get1(fp, 0, 0 TSRMLS_CC); |
163 | if (c == EOF) |
164 | return M_EOI; /* we hit EOF */ |
165 | else |
166 | if (c == 0xff) |
167 | php_iptc_put1(fp, spool, (unsigned char)c, spoolbuf TSRMLS_CC); |
168 | } while (c == 0xff); |
169 | |
170 | return (unsigned int) c; |
171 | } |
172 | /* }}} */ |
173 | |
174 | static char [] = "\xFF\xED\0\0Photoshop 3.0\08BIM\x04\x04\0\0\0\0" ; |
175 | |
176 | /* {{{ proto array iptcembed(string iptcdata, string jpeg_file_name [, int spool]) |
177 | Embed binary IPTC data into a JPEG image. */ |
178 | PHP_FUNCTION(iptcembed) |
179 | { |
180 | char *iptcdata, *jpeg_file; |
181 | int iptcdata_len, jpeg_file_len; |
182 | long spool = 0; |
183 | FILE *fp; |
184 | unsigned int marker, done = 0; |
185 | int inx; |
186 | unsigned char *spoolbuf = NULL, *poi = NULL; |
187 | struct stat sb; |
188 | zend_bool written = 0; |
189 | |
190 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sp|l" , &iptcdata, &iptcdata_len, &jpeg_file, &jpeg_file_len, &spool) != SUCCESS) { |
191 | return; |
192 | } |
193 | |
194 | if (php_check_open_basedir(jpeg_file TSRMLS_CC)) { |
195 | RETURN_FALSE; |
196 | } |
197 | |
198 | if ((fp = VCWD_FOPEN(jpeg_file, "rb" )) == 0) { |
199 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to open %s" , jpeg_file); |
200 | RETURN_FALSE; |
201 | } |
202 | |
203 | if (spool < 2) { |
204 | fstat(fileno(fp), &sb); |
205 | |
206 | poi = spoolbuf = safe_emalloc(1, iptcdata_len + sizeof(psheader) + sb.st_size + 1024, 1); |
207 | memset(poi, 0, iptcdata_len + sizeof(psheader) + sb.st_size + 1024 + 1); |
208 | } |
209 | |
210 | if (php_iptc_get1(fp, spool, poi?&poi:0 TSRMLS_CC) != 0xFF) { |
211 | fclose(fp); |
212 | if (spoolbuf) { |
213 | efree(spoolbuf); |
214 | } |
215 | RETURN_FALSE; |
216 | } |
217 | |
218 | if (php_iptc_get1(fp, spool, poi?&poi:0 TSRMLS_CC) != 0xD8) { |
219 | fclose(fp); |
220 | if (spoolbuf) { |
221 | efree(spoolbuf); |
222 | } |
223 | RETURN_FALSE; |
224 | } |
225 | |
226 | while (!done) { |
227 | marker = php_iptc_next_marker(fp, spool, poi?&poi:0 TSRMLS_CC); |
228 | |
229 | if (marker == M_EOI) { /* EOF */ |
230 | break; |
231 | } else if (marker != M_APP13) { |
232 | php_iptc_put1(fp, spool, (unsigned char)marker, poi?&poi:0 TSRMLS_CC); |
233 | } |
234 | |
235 | switch (marker) { |
236 | case M_APP13: |
237 | /* we are going to write a new APP13 marker, so don't output the old one */ |
238 | php_iptc_skip_variable(fp, 0, 0 TSRMLS_CC); |
239 | fgetc(fp); /* skip already copied 0xFF byte */ |
240 | php_iptc_read_remaining(fp, spool, poi?&poi:0 TSRMLS_CC); |
241 | done = 1; |
242 | break; |
243 | |
244 | case M_APP0: |
245 | /* APP0 is in each and every JPEG, so when we hit APP0 we insert our new APP13! */ |
246 | case M_APP1: |
247 | if (written) { |
248 | /* don't try to write the data twice */ |
249 | break; |
250 | } |
251 | written = 1; |
252 | |
253 | php_iptc_skip_variable(fp, spool, poi?&poi:0 TSRMLS_CC); |
254 | |
255 | if (iptcdata_len & 1) { |
256 | iptcdata_len++; /* make the length even */ |
257 | } |
258 | |
259 | psheader[ 2 ] = (iptcdata_len+28)>>8; |
260 | psheader[ 3 ] = (iptcdata_len+28)&0xff; |
261 | |
262 | for (inx = 0; inx < 28; inx++) { |
263 | php_iptc_put1(fp, spool, psheader[inx], poi?&poi:0 TSRMLS_CC); |
264 | } |
265 | |
266 | php_iptc_put1(fp, spool, (unsigned char)(iptcdata_len>>8), poi?&poi:0 TSRMLS_CC); |
267 | php_iptc_put1(fp, spool, (unsigned char)(iptcdata_len&0xff), poi?&poi:0 TSRMLS_CC); |
268 | |
269 | for (inx = 0; inx < iptcdata_len; inx++) { |
270 | php_iptc_put1(fp, spool, iptcdata[inx], poi?&poi:0 TSRMLS_CC); |
271 | } |
272 | break; |
273 | |
274 | case M_SOS: |
275 | /* we hit data, no more marker-inserting can be done! */ |
276 | php_iptc_read_remaining(fp, spool, poi?&poi:0 TSRMLS_CC); |
277 | done = 1; |
278 | break; |
279 | |
280 | default: |
281 | php_iptc_skip_variable(fp, spool, poi?&poi:0 TSRMLS_CC); |
282 | break; |
283 | } |
284 | } |
285 | |
286 | fclose(fp); |
287 | |
288 | if (spool < 2) { |
289 | RETVAL_STRINGL(spoolbuf, poi - spoolbuf, 0); |
290 | } else { |
291 | RETURN_TRUE; |
292 | } |
293 | } |
294 | /* }}} */ |
295 | |
296 | /* {{{ proto array iptcparse(string iptcdata) |
297 | Parse binary IPTC-data into associative array */ |
298 | PHP_FUNCTION(iptcparse) |
299 | { |
300 | int inx = 0, len; |
301 | unsigned int tagsfound = 0; |
302 | unsigned char *buffer, recnum, dataset, key[ 16 ]; |
303 | char *str; |
304 | int str_len; |
305 | zval *values, **element; |
306 | |
307 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s" , &str, &str_len) != SUCCESS) { |
308 | return; |
309 | } |
310 | |
311 | buffer = (unsigned char *)str; |
312 | |
313 | while (inx < str_len) { /* find 1st tag */ |
314 | if ((buffer[inx] == 0x1c) && ((buffer[inx+1] == 0x01) || (buffer[inx+1] == 0x02))){ |
315 | break; |
316 | } else { |
317 | inx++; |
318 | } |
319 | } |
320 | |
321 | while (inx < str_len) { |
322 | if (buffer[ inx++ ] != 0x1c) { |
323 | break; /* we ran against some data which does not conform to IPTC - stop parsing! */ |
324 | } |
325 | |
326 | if ((inx + 4) >= str_len) |
327 | break; |
328 | |
329 | dataset = buffer[ inx++ ]; |
330 | recnum = buffer[ inx++ ]; |
331 | |
332 | if (buffer[ inx ] & (unsigned char) 0x80) { /* long tag */ |
333 | if((inx+6) >= str_len) { |
334 | break; |
335 | } |
336 | len = (((long) buffer[ inx + 2 ]) << 24) + (((long) buffer[ inx + 3 ]) << 16) + |
337 | (((long) buffer[ inx + 4 ]) << 8) + (((long) buffer[ inx + 5 ])); |
338 | inx += 6; |
339 | } else { /* short tag */ |
340 | len = (((unsigned short) buffer[ inx ])<<8) | (unsigned short)buffer[ inx+1 ]; |
341 | inx += 2; |
342 | } |
343 | |
344 | if ((len < 0) || (len > str_len) || (inx + len) > str_len) { |
345 | break; |
346 | } |
347 | |
348 | snprintf(key, sizeof(key), "%d#%03d" , (unsigned int) dataset, (unsigned int) recnum); |
349 | |
350 | if (tagsfound == 0) { /* found the 1st tag - initialize the return array */ |
351 | array_init(return_value); |
352 | } |
353 | |
354 | if (zend_hash_find(Z_ARRVAL_P(return_value), key, strlen(key) + 1, (void **) &element) == FAILURE) { |
355 | MAKE_STD_ZVAL(values); |
356 | array_init(values); |
357 | |
358 | zend_hash_update(Z_ARRVAL_P(return_value), key, strlen(key) + 1, (void *) &values, sizeof(zval*), (void **) &element); |
359 | } |
360 | |
361 | add_next_index_stringl(*element, buffer+inx, len, 1); |
362 | inx += len; |
363 | tagsfound++; |
364 | } |
365 | |
366 | if (! tagsfound) { |
367 | RETURN_FALSE; |
368 | } |
369 | } |
370 | /* }}} */ |
371 | |
372 | /* |
373 | * Local variables: |
374 | * tab-width: 4 |
375 | * c-basic-offset: 4 |
376 | * End: |
377 | * vim600: sw=4 ts=4 fdm=marker |
378 | * vim<600: sw=4 ts=4 |
379 | */ |
380 | |