/* This file is part of libextractor. Copyright (C) 2002, 2003, 2004, 2005, 2009, 2012 Vidyut Samanta and Christian Grothoff libextractor is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. libextractor is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with libextractor; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * @file plugins/png_extractor.c * @brief plugin to support PNG files * @author Christian Grothoff */ #include "platform.h" #include #include "extractor.h" #include "convert.h" /** * Header that every PNG file must start with. */ #define PNG_HEADER "\211PNG\r\n\032\n" /** * Function to create 0-terminated string from the * first n characters of the given input. * * @param str input string * @param n length of the input * @return n-bytes from str followed by 0-termination, NULL on error */ static char * stndup (const char *str, size_t n) { char *tmp; if (n + 1 < n) return NULL; if (NULL == (tmp = malloc (n + 1))) return NULL; tmp[n] = '\0'; memcpy (tmp, str, n); return tmp; } /** * strnlen is GNU specific, let's redo it here to be * POSIX compliant. * * @param str input string * @param maxlen maximum length of str * @return first position of 0-terminator in str, or maxlen */ static size_t stnlen (const char *str, size_t maxlen) { size_t ret; ret = 0; while ( (ret < maxlen) && ('\0' != str[ret]) ) ret++; return ret; } /** * Interpret the 4 bytes in 'buf' as a big-endian * encoded 32-bit integer, convert and return. * * @param pos (unaligned) pointer to 4 byte integer * @return converted integer in host byte order */ static uint32_t get_int_at (const void *pos) { uint32_t i; memcpy (&i, pos, sizeof (i)); return htonl (i); } /** * Map from PNG meta data descriptor strings * to LE types. */ static struct { /** * PNG name. */ const char *name; /** * Corresponding LE type. */ enum EXTRACTOR_MetaType type; } tagmap[] = { { "Author", EXTRACTOR_METATYPE_AUTHOR_NAME }, { "Description", EXTRACTOR_METATYPE_DESCRIPTION }, { "Comment", EXTRACTOR_METATYPE_COMMENT }, { "Copyright", EXTRACTOR_METATYPE_COPYRIGHT }, { "Source", EXTRACTOR_METATYPE_SOURCE_DEVICE }, { "Creation Time", EXTRACTOR_METATYPE_CREATION_DATE }, { "Title", EXTRACTOR_METATYPE_TITLE }, { "Software", EXTRACTOR_METATYPE_PRODUCED_BY_SOFTWARE }, { "Disclaimer", EXTRACTOR_METATYPE_DISCLAIMER }, { "Warning", EXTRACTOR_METATYPE_WARNING }, { "Signature", EXTRACTOR_METATYPE_UNKNOWN }, { NULL, EXTRACTOR_METATYPE_RESERVED } }; /** * Give the given metadata to LE. Set "ret" to 1 and * goto 'FINISH' if LE says we are done. * * @param t type of the metadata * @param s utf8 string with the metadata */ #define ADD(t,s) do { if (0 != (ret = ec->proc (ec->cls, "png", t, EXTRACTOR_METAFORMAT_UTF8, "text/plain", s, strlen (s) + 1))) goto FINISH; } while (0) /** * Give the given metadata to LE and free the memory. Set "ret" to 1 and * goto 'FINISH' if LE says we are done. * * @param t type of the metadata * @param s utf8 string with the metadata, to be freed afterwards */ #define ADDF(t,s) do { if ( (NULL != s) && (0 != (ret = ec->proc (ec->cls, "png", t, EXTRACTOR_METAFORMAT_UTF8, "text/plain", s, strlen (s) + 1))) ) { free (s); goto FINISH; } if (NULL != s) free (s); } while (0) /** * Process EXt tag. * * @param ec extraction context * @param length length of the tag * @return 0 to continue extracting, 1 if we are done */ static int processtEXt (struct EXTRACTOR_ExtractContext *ec, uint32_t length) { void *ptr; unsigned char *data; char *keyword; size_t off; unsigned int i; int ret; if (length != ec->read (ec->cls, &ptr, length)) return 1; data = ptr; off = stnlen ((char*) data, length) + 1; if (off >= length) return 0; /* failed to find '\0' */ if (NULL == (keyword = EXTRACTOR_common_convert_to_utf8 ((char*) &data[off], length - off, "ISO-8859-1"))) return 0; ret = 0; for (i = 0; NULL != tagmap[i].name; i++) if (0 == strcmp (tagmap[i].name, (char*) data)) { ADDF (tagmap[i].type, keyword); return 0; } ADDF (EXTRACTOR_METATYPE_KEYWORDS, keyword); FINISH: return ret; } /** * Process iTXt tag. * * @param ec extraction context * @param length length of the tag * @return 0 to continue extracting, 1 if we are done */ static int processiTXt (struct EXTRACTOR_ExtractContext *ec, uint32_t length) { void *ptr; unsigned char *data; size_t pos; char *keyword; const char *language; const char *translated; unsigned int i; int compressed; char *buf; char *lan; uLongf bufLen; int ret; int zret; if (length != ec->read (ec->cls, &ptr, length)) return 1; data = ptr; pos = stnlen ((char *) data, length) + 1; if (pos >= length) return 0; compressed = data[pos++]; if (compressed && (0 != data[pos++])) return 0; /* bad compression method */ if (pos > length) return 0; language = (char *) &data[pos]; ret = 0; if ( (stnlen (language, length - pos) > 0) && (NULL != (lan = stndup (language, length - pos))) ) ADDF (EXTRACTOR_METATYPE_LANGUAGE, lan); pos += stnlen (language, length - pos) + 1; if (pos + 1 >= length) return 0; translated = (char*) &data[pos]; /* already in utf-8! */ if ( (stnlen (translated, length - pos) > 0) && (NULL != (lan = stndup (translated, length - pos))) ) ADDF (EXTRACTOR_METATYPE_KEYWORDS, lan); pos += stnlen (translated, length - pos) + 1; if (pos >= length) return 0; if (compressed) { bufLen = 1024 + 2 * (length - pos); while (1) { if (bufLen * 2 < bufLen) return 0; bufLen *= 2; if (bufLen > 50 * (length - pos)) { /* printf("zlib problem"); */ return 0; } if (NULL == (buf = malloc (bufLen))) { /* printf("out of memory"); */ return 0; /* out of memory */ } if (Z_OK == (zret = uncompress ((Bytef *) buf, &bufLen, (const Bytef *) &data[pos], length - pos))) { /* printf("zlib ok"); */ break; } free (buf); if (Z_BUF_ERROR != zret) return 0; /* unknown error, abort */ } keyword = stndup (buf, bufLen); free (buf); } else { keyword = stndup ((char *) &data[pos], length - pos); } if (NULL == keyword) return ret; for (i = 0; NULL != tagmap[i].name; i++) if (0 == strcmp (tagmap[i].name, (char*) data)) { ADDF (tagmap[i].type, keyword /* already in utf8 */); return 0; } ADDF (EXTRACTOR_METATYPE_COMMENT, keyword); FINISH: return ret; } /** * Process IHDR tag. * * @param ec extraction context * @param length length of the tag * @return 0 to continue extracting, 1 if we are done */ static int processIHDR (struct EXTRACTOR_ExtractContext *ec, uint32_t length) { void *ptr; unsigned char *data; char tmp[128]; int ret; if (length < 12) return 0; if (length != ec->read (ec->cls, &ptr, length)) return 1; data = ptr; ret = 0; snprintf (tmp, sizeof (tmp), "%ux%u", get_int_at (data), get_int_at (&data[4])); ADD (EXTRACTOR_METATYPE_IMAGE_DIMENSIONS, tmp); FINISH: return ret; } /** * Process zTXt tag. * * @param ec extraction context * @param length length of the tag * @return 0 to continue extracting, 1 if we are done */ static int processzTXt (struct EXTRACTOR_ExtractContext *ec, uint32_t length) { void *ptr; unsigned char *data; char *keyword; size_t off; unsigned int i; char *buf; uLongf bufLen; int zret; int ret; if (length != ec->read (ec->cls, &ptr, length)) return 1; data = ptr; off = stnlen ((char *) data, length) + 1; if (off >= length) return 0; /* failed to find '\0' */ if (0 != data[off]) return 0; /* compression method must be 0 */ off++; ret = 0; bufLen = 1024 + 2 * (length - off); while (1) { if (bufLen * 2 < bufLen) return 0; bufLen *= 2; if (bufLen > 50 * (length - off)) { /* printf("zlib problem"); */ return 0; } if (NULL == (buf = malloc (bufLen))) { /* printf("out of memory"); */ return 0; /* out of memory */ } if (Z_OK == (zret = uncompress ((Bytef *) buf, &bufLen, (const Bytef *) &data[off], length - off))) { /* printf("zlib ok"); */ break; } free (buf); if (Z_BUF_ERROR != zret) return 0; /* unknown error, abort */ } keyword = EXTRACTOR_common_convert_to_utf8 (buf, bufLen, "ISO-8859-1"); free (buf); for (i = 0; NULL != tagmap[i].name; i++) if (0 == strcmp (tagmap[i].name, (char*) data)) { ADDF (tagmap[i].type, keyword); return 0; } ADDF (EXTRACTOR_METATYPE_COMMENT, keyword); FINISH: return ret; } /** * Process IME tag. * * @param ec extraction context * @param length length of the tag * @return 0 to continue extracting, 1 if we are done */ static int processtIME (struct EXTRACTOR_ExtractContext *ec, uint32_t length) { void *ptr; unsigned char *data; unsigned short y; unsigned int year; unsigned int mo; unsigned int day; unsigned int h; unsigned int m; unsigned int s; char val[256]; int ret; if (length != 7) return 0; if (length != ec->read (ec->cls, &ptr, length)) return 1; data = ptr; ret = 0; memcpy (&y, data, sizeof (uint16_t)); year = ntohs (y); mo = (unsigned char) data[6]; day = (unsigned char) data[7]; h = (unsigned char) data[8]; m = (unsigned char) data[9]; s = (unsigned char) data[10]; snprintf (val, sizeof (val), "%04u-%02u-%02u %02d:%02d:%02d", year, mo, day, h, m, s); ADD (EXTRACTOR_METATYPE_MODIFICATION_DATE, val); FINISH: return ret; } /** * Main entry method for the 'image/png' extraction plugin. * * @param ec extraction context provided to the plugin */ void EXTRACTOR_png_extract_method (struct EXTRACTOR_ExtractContext *ec) { void *data; uint32_t length; int64_t pos; int ret; ssize_t len; len = strlen (PNG_HEADER); if (len != ec->read (ec->cls, &data, len)) return; if (0 != strncmp ((const char*) data, PNG_HEADER, len)) return; ADD (EXTRACTOR_METATYPE_MIMETYPE, "image/png"); ret = 0; while (0 == ret) { if (sizeof (uint32_t) + 4 != ec->read (ec->cls, &data, sizeof (uint32_t) + 4)) break; length = get_int_at (data); if (0 > (pos = ec->seek (ec->cls, 0, SEEK_CUR))) break; pos += length + 4; /* Chunk type, data, crc */ if (0 == strncmp ((char*) data + sizeof (uint32_t), "IHDR", 4)) ret = processIHDR (ec, length); if (0 == strncmp ((char*) data + sizeof (uint32_t), "iTXt", 4)) ret = processiTXt (ec, length); if (0 == strncmp ((char*) data + sizeof (uint32_t), "tEXt", 4)) ret = processtEXt (ec, length); if (0 == strncmp ((char*) data + sizeof (uint32_t), "zTXt", 4)) ret = processzTXt (ec, length); if (0 == strncmp ((char*) data + sizeof (uint32_t), "tIME", 4)) ret = processtIME (ec, length); if (ret != 0) break; if (pos != ec->seek (ec->cls, pos, SEEK_SET)) break; } FINISH: return; } /* end of png_extractor.c */