iptc.c 9.1 KB

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