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