iptc.c 9.9 KB

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