1/* 2 +----------------------------------------------------------------------+ 3 | PHP Version 5 | 4 +----------------------------------------------------------------------+ 5 | Copyright (c) 1997-2013 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 */ 78static 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 */ 91static 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 */ 113static 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 */ 123static 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 */ 145static 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 174static char psheader[] = "\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. */ 178PHP_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, inx; 185 unsigned char *spoolbuf = NULL, *poi = NULL; 186 struct stat sb; 187 zend_bool written = 0; 188 189 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|l", &iptcdata, &iptcdata_len, &jpeg_file, &jpeg_file_len, &spool) != SUCCESS) { 190 return; 191 } 192 193 if (strlen(jpeg_file) != jpeg_file_len) { 194 RETURN_FALSE; 195 } 196 197 if (PG(safe_mode) && (!php_checkuid(jpeg_file, NULL, CHECKUID_CHECK_FILE_AND_DIR))) { 198 RETURN_FALSE; 199 } 200 201 if (php_check_open_basedir(jpeg_file TSRMLS_CC)) { 202 RETURN_FALSE; 203 } 204 205 if ((fp = VCWD_FOPEN(jpeg_file, "rb")) == 0) { 206 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to open %s", jpeg_file); 207 RETURN_FALSE; 208 } 209 210 if (spool < 2) { 211 fstat(fileno(fp), &sb); 212 213 poi = spoolbuf = safe_emalloc(1, iptcdata_len + sizeof(psheader) + sb.st_size + 1024, 1); 214 memset(poi, 0, iptcdata_len + sizeof(psheader) + sb.st_size + 1024 + 1); 215 } 216 217 if (php_iptc_get1(fp, spool, poi?&poi:0 TSRMLS_CC) != 0xFF) { 218 fclose(fp); 219 if (spoolbuf) { 220 efree(spoolbuf); 221 } 222 RETURN_FALSE; 223 } 224 225 if (php_iptc_get1(fp, spool, poi?&poi:0 TSRMLS_CC) != 0xD8) { 226 fclose(fp); 227 if (spoolbuf) { 228 efree(spoolbuf); 229 } 230 RETURN_FALSE; 231 } 232 233 while (!done) { 234 marker = php_iptc_next_marker(fp, spool, poi?&poi:0 TSRMLS_CC); 235 236 if (marker == M_EOI) { /* EOF */ 237 break; 238 } else if (marker != M_APP13) { 239 php_iptc_put1(fp, spool, (unsigned char)marker, poi?&poi:0 TSRMLS_CC); 240 } 241 242 switch (marker) { 243 case M_APP13: 244 /* we are going to write a new APP13 marker, so don't output the old one */ 245 php_iptc_skip_variable(fp, 0, 0 TSRMLS_CC); 246 php_iptc_read_remaining(fp, spool, poi?&poi:0 TSRMLS_CC); 247 done = 1; 248 break; 249 250 case M_APP0: 251 /* APP0 is in each and every JPEG, so when we hit APP0 we insert our new APP13! */ 252 case M_APP1: 253 if (written) { 254 /* don't try to write the data twice */ 255 break; 256 } 257 written = 1; 258 259 php_iptc_skip_variable(fp, spool, poi?&poi:0 TSRMLS_CC); 260 261 if (iptcdata_len & 1) { 262 iptcdata_len++; /* make the length even */ 263 } 264 265 psheader[ 2 ] = (iptcdata_len+28)>>8; 266 psheader[ 3 ] = (iptcdata_len+28)&0xff; 267 268 for (inx = 0; inx < 28; inx++) { 269 php_iptc_put1(fp, spool, psheader[inx], poi?&poi:0 TSRMLS_CC); 270 } 271 272 php_iptc_put1(fp, spool, (unsigned char)(iptcdata_len>>8), poi?&poi:0 TSRMLS_CC); 273 php_iptc_put1(fp, spool, (unsigned char)(iptcdata_len&0xff), poi?&poi:0 TSRMLS_CC); 274 275 for (inx = 0; inx < iptcdata_len; inx++) { 276 php_iptc_put1(fp, spool, iptcdata[inx], poi?&poi:0 TSRMLS_CC); 277 } 278 break; 279 280 case M_SOS: 281 /* we hit data, no more marker-inserting can be done! */ 282 php_iptc_read_remaining(fp, spool, poi?&poi:0 TSRMLS_CC); 283 done = 1; 284 break; 285 286 default: 287 php_iptc_skip_variable(fp, spool, poi?&poi:0 TSRMLS_CC); 288 break; 289 } 290 } 291 292 fclose(fp); 293 294 if (spool < 2) { 295 RETVAL_STRINGL(spoolbuf, poi - spoolbuf, 0); 296 } else { 297 RETURN_TRUE; 298 } 299} 300/* }}} */ 301 302/* {{{ proto array iptcparse(string iptcdata) 303 Parse binary IPTC-data into associative array */ 304PHP_FUNCTION(iptcparse) 305{ 306 unsigned int inx = 0, len, tagsfound = 0; 307 unsigned char *buffer, recnum, dataset, key[ 16 ]; 308 char *str; 309 int str_len; 310 zval *values, **element; 311 312 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &str, &str_len) != SUCCESS) { 313 return; 314 } 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 len = (((long) buffer[ inx + 2 ]) << 24) + (((long) buffer[ inx + 3 ]) << 16) + 339 (((long) buffer[ inx + 4 ]) << 8) + (((long) buffer[ inx + 5 ])); 340 inx += 6; 341 } else { /* short tag */ 342 len = (((unsigned short) buffer[ inx ])<<8) | (unsigned short)buffer[ inx+1 ]; 343 inx += 2; 344 } 345 346 if ((len < 0) || (len > str_len) || (inx + len) > str_len) { 347 break; 348 } 349 350 snprintf(key, sizeof(key), "%d#%03d", (unsigned int) dataset, (unsigned int) recnum); 351 352 if (tagsfound == 0) { /* found the 1st tag - initialize the return array */ 353 array_init(return_value); 354 } 355 356 if (zend_hash_find(Z_ARRVAL_P(return_value), key, strlen(key) + 1, (void **) &element) == FAILURE) { 357 MAKE_STD_ZVAL(values); 358 array_init(values); 359 360 zend_hash_update(Z_ARRVAL_P(return_value), key, strlen(key) + 1, (void *) &values, sizeof(zval*), (void **) &element); 361 } 362 363 add_next_index_stringl(*element, buffer+inx, len, 1); 364 inx += len; 365 tagsfound++; 366 } 367 368 if (! tagsfound) { 369 RETURN_FALSE; 370 } 371} 372/* }}} */ 373 374/* 375 * Local variables: 376 * tab-width: 4 377 * c-basic-offset: 4 378 * End: 379 * vim600: sw=4 ts=4 fdm=marker 380 * vim<600: sw=4 ts=4 381 */ 382