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; 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 php_iptc_read_remaining(fp, spool, poi?&poi:0 TSRMLS_CC); 240 done = 1; 241 break; 242 243 case M_APP0: 244 /* APP0 is in each and every JPEG, so when we hit APP0 we insert our new APP13! */ 245 case M_APP1: 246 if (written) { 247 /* don't try to write the data twice */ 248 break; 249 } 250 written = 1; 251 252 php_iptc_skip_variable(fp, spool, poi?&poi:0 TSRMLS_CC); 253 254 if (iptcdata_len & 1) { 255 iptcdata_len++; /* make the length even */ 256 } 257 258 psheader[ 2 ] = (iptcdata_len+28)>>8; 259 psheader[ 3 ] = (iptcdata_len+28)&0xff; 260 261 for (inx = 0; inx < 28; inx++) { 262 php_iptc_put1(fp, spool, psheader[inx], poi?&poi:0 TSRMLS_CC); 263 } 264 265 php_iptc_put1(fp, spool, (unsigned char)(iptcdata_len>>8), poi?&poi:0 TSRMLS_CC); 266 php_iptc_put1(fp, spool, (unsigned char)(iptcdata_len&0xff), poi?&poi:0 TSRMLS_CC); 267 268 for (inx = 0; inx < iptcdata_len; inx++) { 269 php_iptc_put1(fp, spool, iptcdata[inx], poi?&poi:0 TSRMLS_CC); 270 } 271 break; 272 273 case M_SOS: 274 /* we hit data, no more marker-inserting can be done! */ 275 php_iptc_read_remaining(fp, spool, poi?&poi:0 TSRMLS_CC); 276 done = 1; 277 break; 278 279 default: 280 php_iptc_skip_variable(fp, spool, poi?&poi:0 TSRMLS_CC); 281 break; 282 } 283 } 284 285 fclose(fp); 286 287 if (spool < 2) { 288 RETVAL_STRINGL(spoolbuf, poi - spoolbuf, 0); 289 } else { 290 RETURN_TRUE; 291 } 292} 293/* }}} */ 294 295/* {{{ proto array iptcparse(string iptcdata) 296 Parse binary IPTC-data into associative array */ 297PHP_FUNCTION(iptcparse) 298{ 299 int inx = 0, len; 300 unsigned int tagsfound = 0; 301 unsigned char *buffer, recnum, dataset, key[ 16 ]; 302 char *str; 303 int str_len; 304 zval *values, **element; 305 306 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &str, &str_len) != SUCCESS) { 307 return; 308 } 309 310 buffer = (unsigned char *)str; 311 312 while (inx < str_len) { /* find 1st tag */ 313 if ((buffer[inx] == 0x1c) && ((buffer[inx+1] == 0x01) || (buffer[inx+1] == 0x02))){ 314 break; 315 } else { 316 inx++; 317 } 318 } 319 320 while (inx < str_len) { 321 if (buffer[ inx++ ] != 0x1c) { 322 break; /* we ran against some data which does not conform to IPTC - stop parsing! */ 323 } 324 325 if ((inx + 4) >= str_len) 326 break; 327 328 dataset = buffer[ inx++ ]; 329 recnum = buffer[ inx++ ]; 330 331 if (buffer[ inx ] & (unsigned char) 0x80) { /* long tag */ 332 len = (((long) buffer[ inx + 2 ]) << 24) + (((long) buffer[ inx + 3 ]) << 16) + 333 (((long) buffer[ inx + 4 ]) << 8) + (((long) buffer[ inx + 5 ])); 334 inx += 6; 335 } else { /* short tag */ 336 len = (((unsigned short) buffer[ inx ])<<8) | (unsigned short)buffer[ inx+1 ]; 337 inx += 2; 338 } 339 340 if ((len < 0) || (len > str_len) || (inx + len) > str_len) { 341 break; 342 } 343 344 snprintf(key, sizeof(key), "%d#%03d", (unsigned int) dataset, (unsigned int) recnum); 345 346 if (tagsfound == 0) { /* found the 1st tag - initialize the return array */ 347 array_init(return_value); 348 } 349 350 if (zend_hash_find(Z_ARRVAL_P(return_value), key, strlen(key) + 1, (void **) &element) == FAILURE) { 351 MAKE_STD_ZVAL(values); 352 array_init(values); 353 354 zend_hash_update(Z_ARRVAL_P(return_value), key, strlen(key) + 1, (void *) &values, sizeof(zval*), (void **) &element); 355 } 356 357 add_next_index_stringl(*element, buffer+inx, len, 1); 358 inx += len; 359 tagsfound++; 360 } 361 362 if (! tagsfound) { 363 RETURN_FALSE; 364 } 365} 366/* }}} */ 367 368/* 369 * Local variables: 370 * tab-width: 4 371 * c-basic-offset: 4 372 * End: 373 * vim600: sw=4 ts=4 fdm=marker 374 * vim<600: sw=4 ts=4 375 */ 376