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