// ***************************************************************** -*- C++ -*- /* * Copyright (C) 2004-2008 Andreas Huggel * * This program is part of the Exiv2 distribution. * * This program 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 2 * of the License, or (at your option) any later version. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. */ /* File: pngchunk.cpp Version: $Rev: 823 $ Author(s): Gilles Caulier (cgilles) History: 12-Jun-06, gc: submitted Credits: See header file */ // ***************************************************************************** #include "rcsid.hpp" EXIV2_RCSID("@(#) $Id: pngchunk.cpp 823 2006-06-23 07:35:00Z cgilles $") // ***************************************************************************** // included header files #ifdef _MSC_VER # include "exv_msvc.h" #else # include "exv_conf.h" #endif //#define DEBUG 1 #ifdef EXV_HAVE_LIBZ extern "C" { #include // To uncompress or compress text chunk } #include "pngchunk_int.hpp" #include "tiffimage.hpp" #include "exif.hpp" #include "iptc.hpp" #include "image.hpp" #include "error.hpp" // + standard includes #include #include #include #include #include /* URLs to find informations about PNG chunks : tEXt and zTXt chunks : http://www.vias.org/pngguide/chapter11_04.html iTXt chunk : http://www.vias.org/pngguide/chapter11_05.html PNG tags : http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PNG.html#TextualData */ // ***************************************************************************** // class member definitions namespace Exiv2 { namespace Internal { void PngChunk::decodeIHDRChunk(const DataBuf& data, int* outWidth, int* outHeight) { // Extract image width and height from IHDR chunk. *outWidth = getLong((const byte*)data.pData_, bigEndian); *outHeight = getLong((const byte*)data.pData_ + 4, bigEndian); } // PngChunk::decodeIHDRChunk void PngChunk::decodeTXTChunk(Image* pImage, const DataBuf& data, TxtChunkType type) { DataBuf key = keyTXTChunk(data); #ifdef DEBUG std::cout << "Exiv2::PngChunk::decodeTXTChunk: TXT chunk key: " << std::string((const char*)key.pData_) << "\n"; #endif DataBuf arr = parseTXTChunk(data, key.size_, type); #ifdef DEBUG std::cout << "Exiv2::PngChunk::decodeTXTChunk: TXT chunk data: " << std::string((const char*)arr.pData_, 32) << "\n"; #endif parseChunkContent(pImage, key.pData_, arr); } // PngChunk::decodeTXTChunk DataBuf PngChunk::keyTXTChunk(const DataBuf& data, bool stripHeader) { // From a tEXt, zTXt, or iTXt chunk, // we get the key, it's a null terminated string at the chunk start const byte *key = data.pData_ + (stripHeader ? 8 : 0); // Find null string at end of key. int keysize=0; for ( ; key[keysize] != 0 ; keysize++) { // look if keysize is valid. if (keysize >= data.size_) throw Error(14); } return DataBuf(key, keysize); } // PngChunk::keyTXTChunk DataBuf PngChunk::parseTXTChunk(const DataBuf& data, int keysize, TxtChunkType type) { DataBuf arr; if(type == zTXt_Chunk) { // Extract a deflate compressed Latin-1 text chunk // we get the compression method after the key const byte* compressionMethod = data.pData_ + keysize + 1; if ( *compressionMethod != 0x00 ) { // then it isn't zlib compressed and we are sunk #ifdef DEBUG std::cerr << "Exiv2::PngChunk::parseTXTChunk: Non-standard zTXt compression method.\n"; #endif throw Error(14); } // compressed string after the compression technique spec const byte* compressedText = data.pData_ + keysize + 2; unsigned int compressedTextSize = data.size_ - keysize - 2; zlibUncompress(compressedText, compressedTextSize, arr); } else if(type == tEXt_Chunk) { // Extract a non-compressed Latin-1 text chunk // the text comes after the key, but isn't null terminated const byte* text = data.pData_ + keysize + 1; long textsize = data.size_ - keysize - 1; arr.alloc(textsize); arr = DataBuf(text, textsize); } else if(type == iTXt_Chunk) { // Extract a deflate compressed or uncompressed UTF-8 text chunk // we get the compression flag after the key const byte* compressionFlag = data.pData_ + keysize + 1; // we get the compression method after the compression flag const byte* compressionMethod = data.pData_ + keysize + 2; // language description string after the compression technique spec std::string languageText((const char*)(data.pData_ + keysize + 3)); unsigned int languageTextSize = languageText.size(); // translated keyword string after the language description std::string translatedKeyText((const char*)(data.pData_ + keysize + 3 + languageTextSize +1)); unsigned int translatedKeyTextSize = translatedKeyText.size(); if ( compressionFlag[0] == 0x00 ) { // then it's an uncompressed iTXt chunk #ifdef DEBUG std::cout << "Exiv2::PngChunk::parseTXTChunk: We found an uncompressed iTXt field\n"; #endif // the text comes after the translated keyword, but isn't null terminated const byte* text = data.pData_ + keysize + 3 + languageTextSize + 1 + translatedKeyTextSize + 1; long textsize = data.size_ - (keysize + 3 + languageTextSize + 1 + translatedKeyTextSize + 1); arr.alloc(textsize); arr = DataBuf(text, textsize); } else if ( compressionFlag[0] == 0x01 && compressionMethod[0] == 0x00 ) { // then it's a zlib compressed iTXt chunk #ifdef DEBUG std::cout << "Exiv2::PngChunk::parseTXTChunk: We found a zlib compressed iTXt field\n"; #endif // the compressed text comes after the translated keyword, but isn't null terminated const byte* compressedText = data.pData_ + keysize + 3 + languageTextSize + 1 + translatedKeyTextSize + 1; long compressedTextSize = data.size_ - (keysize + 3 + languageTextSize + 1 + translatedKeyTextSize + 1); zlibUncompress(compressedText, compressedTextSize, arr); } else { // then it isn't zlib compressed and we are sunk #ifdef DEBUG std::cerr << "Exiv2::PngChunk::parseTXTChunk: Non-standard iTXt compression method.\n"; #endif throw Error(14); } } else { #ifdef DEBUG std::cerr << "Exiv2::PngChunk::parseTXTChunk: We found a field, not expected though\n"; #endif throw Error(14); } return arr; } // PngChunk::parsePngChunk void PngChunk::parseChunkContent(Image* pImage, const byte *key, const DataBuf arr) { // We look if an ImageMagick EXIF raw profile exist. if ( (memcmp("Raw profile type exif", key, 21) == 0 || memcmp("Raw profile type APP1", key, 21) == 0) && pImage->exifData().empty()) { DataBuf exifData = readRawProfile(arr); long length = exifData.size_; if (length > 0) { // Find the position of Exif header in bytes array. const byte exifHeader[] = { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 }; long pos = -1; for (long i=0 ; i < length-(long)sizeof(exifHeader) ; i++) { if (memcmp(exifHeader, &exifData.pData_[i], sizeof(exifHeader)) == 0) { pos = i; break; } } // If found it, store only these data at from this place. if (pos !=-1) { #ifdef DEBUG std::cout << "Exiv2::PngChunk::parseChunkContent: Exif header found at position " << pos << "\n"; #endif pos = pos + sizeof(exifHeader); ByteOrder bo = TiffParser::decode(pImage->exifData(), pImage->iptcData(), pImage->xmpData(), exifData.pData_ + pos, length - pos); pImage->setByteOrder(bo); } else { #ifndef SUPPRESS_WARNINGS std::cerr << "Exiv2::PngChunk::parseChunkContent: Failed to decode Exif metadata.\n"; #endif pImage->exifData().clear(); } } } // We look if an ImageMagick IPTC raw profile exist. if ( memcmp("Raw profile type iptc", key, 21) == 0 && pImage->iptcData().empty()) { DataBuf iptcData = readRawProfile(arr); long length = iptcData.size_; if (length > 0) IptcParser::decode(pImage->iptcData(), iptcData.pData_, length); } // We look if an ImageMagick XMP raw profile exist. if ( memcmp("Raw profile type xmp", key, 20) == 0 && pImage->xmpData().empty()) { DataBuf xmpBuf = readRawProfile(arr); long length = xmpBuf.size_; if (length > 0) { std::string& xmpPacket = pImage->xmpPacket(); xmpPacket.assign(reinterpret_cast(xmpBuf.pData_), length); std::string::size_type idx = xmpPacket.find_first_of('<'); if (idx != std::string::npos && idx > 0) { #ifndef SUPPRESS_WARNINGS std::cerr << "Exiv2::PngChunk::parseChunkContent: Removing " << idx << " characters from the beginning of the XMP packet\n"; #endif xmpPacket = xmpPacket.substr(idx); } if (XmpParser::decode(pImage->xmpData(), xmpPacket)) { #ifndef SUPPRESS_WARNINGS std::cerr << "Exiv2::PngChunk::parseChunkContent: Failed to decode XMP metadata.\n"; #endif } } } // We look if an Adobe XMP string exist. if ( memcmp("XML:com.adobe.xmp", key, 17) == 0 && pImage->xmpData().empty()) { if (arr.size_ > 0) { std::string& xmpPacket = pImage->xmpPacket(); xmpPacket.assign(reinterpret_cast(arr.pData_), arr.size_); std::string::size_type idx = xmpPacket.find_first_of('<'); if (idx != std::string::npos && idx > 0) { #ifndef SUPPRESS_WARNINGS std::cerr << "Warning: Removing " << idx << " characters " << "from the beginning of the XMP packet\n"; #endif xmpPacket = xmpPacket.substr(idx); } if (XmpParser::decode(pImage->xmpData(), xmpPacket)) { #ifndef SUPPRESS_WARNINGS std::cerr << "Warning: Failed to decode XMP metadata.\n"; #endif } } } // We look if a comments string exist. Note than we use only 'Description' keyword which // is dedicaced to store long comments. 'Comment' keyword is ignored. if ( memcmp("Description", key, 11) == 0 && pImage->comment().empty()) { pImage->comment().assign(reinterpret_cast(arr.pData_), arr.size_); } } // PngChunk::parseChunkContent DataBuf PngChunk::makeMetadataChunk(const DataBuf& metadata, MetadataType type, bool compress) { if (type == comment_Data) { DataBuf key(11); memcpy(key.pData_, "Description", 11); DataBuf rawData = makeUtf8TxtChunk(key, metadata, compress); return rawData; } else if (type == exif_Data) { DataBuf tmp(4); memcpy(tmp.pData_, "exif", 4); DataBuf rawProfile = writeRawProfile(metadata, tmp); DataBuf key(17 + tmp.size_); memcpy(key.pData_, "Raw profile type ", 17); memcpy(key.pData_ + 17, tmp.pData_, tmp.size_); DataBuf rawData = makeAsciiTxtChunk(key, rawProfile, compress); return rawData; } else if (type == iptc_Data) { DataBuf tmp(4); memcpy(tmp.pData_, "iptc", 4); DataBuf rawProfile = writeRawProfile(metadata, tmp); DataBuf key(17 + tmp.size_); memcpy(key.pData_, "Raw profile type ", 17); memcpy(key.pData_ + 17, tmp.pData_, tmp.size_); DataBuf rawData = makeAsciiTxtChunk(key, rawProfile, compress); return rawData; } else if (type == xmp_Data) { DataBuf key(17); memcpy(key.pData_, "XML:com.adobe.xmp", 17); DataBuf rawData = makeUtf8TxtChunk(key, metadata, compress); return rawData; } return DataBuf(); } // PngChunk::makeMetadataChunk void PngChunk::zlibUncompress(const byte* compressedText, unsigned int compressedTextSize, DataBuf& arr) { uLongf uncompressedLen = compressedTextSize * 2; // just a starting point int zlibResult; do { arr.alloc(uncompressedLen); zlibResult = uncompress((Bytef*)arr.pData_, &uncompressedLen, compressedText, compressedTextSize); if (zlibResult == Z_OK) { // then it is all OK arr.alloc(uncompressedLen); } else if (zlibResult == Z_BUF_ERROR) { // the uncompressedArray needs to be larger #ifdef DEBUG std::cout << "Exiv2::PngChunk::parsePngChunk: doubling size for decompression.\n"; #endif uncompressedLen *= 2; // DoS protection. can't be bigger than 64k if ( uncompressedLen > 131072 ) break; } else { // something bad happened throw Error(14); } } while (zlibResult == Z_BUF_ERROR); if (zlibResult != Z_OK) throw Error(14); } // PngChunk::zlibUncompress void PngChunk::zlibCompress(const byte* text, unsigned int textSize, DataBuf& arr) { uLongf compressedLen = textSize * 2; // just a starting point int zlibResult; do { arr.alloc(compressedLen); zlibResult = compress2((Bytef*)arr.pData_, &compressedLen, text, textSize, Z_BEST_COMPRESSION); if (zlibResult == Z_OK) { // then it is all OK arr.alloc(compressedLen); } else if (zlibResult == Z_BUF_ERROR) { // the compressedArray needs to be larger #ifdef DEBUG std::cout << "Exiv2::PngChunk::parsePngChunk: doubling size for compression.\n"; #endif compressedLen *= 2; // DoS protection. can't be bigger than 64k if ( compressedLen > 131072 ) break; } else { // something bad happened throw Error(14); } } while (zlibResult == Z_BUF_ERROR); if (zlibResult != Z_OK) throw Error(14); } // PngChunk::zlibCompress DataBuf PngChunk::makeAsciiTxtChunk(const DataBuf& key, const DataBuf& data, bool compress) { DataBuf type(4); DataBuf data4crc; DataBuf chunkData; byte chunkDataSize[4]; byte chunkCRC[4]; if (compress) { // Compressed text chunk using ZLib. // Data format : key ("zTXt") + 0x00 + compression type (0x00) + compressed data // Chunk structure: data lenght (4 bytes) + chunk type (4 bytes) + compressed data + CRC (4 bytes) memcpy(type.pData_, "zTXt", 4); DataBuf compressedData; zlibCompress(data.pData_, data.size_, compressedData); data4crc.alloc(key.size_ + 1 + 1 + compressedData.size_); memcpy(data4crc.pData_, key.pData_, key.size_); memcpy(data4crc.pData_ + key.size_, "\0\0", 2); memcpy(data4crc.pData_ + key.size_ + 2, compressedData.pData_, compressedData.size_); uLong crc = crc32(0L, Z_NULL, 0); crc = crc32(crc, data4crc.pData_, data4crc.size_); ul2Data(chunkCRC, crc, Exiv2::bigEndian); ul2Data(chunkDataSize, data4crc.size_, Exiv2::bigEndian); chunkData.alloc(4 + type.size_ + data4crc.size_ + 4); memcpy(chunkData.pData_, chunkDataSize, 4); memcpy(chunkData.pData_ + 4, type.pData_, type.size_); memcpy(chunkData.pData_ + 4 + type.size_, data4crc.pData_, data4crc.size_); memcpy(chunkData.pData_ + 4 + type.size_ + data4crc.size_, chunkCRC, 4); } else { // Not Compressed text chunk. // Data Format : key ("tEXt") + 0x00 + data // Chunk Structure: data lenght (4 bytes) + chunk type (4 bytes) + data + CRC (4 bytes) memcpy(type.pData_, "tEXt", 4); data4crc.alloc(key.size_ + 1 + data.size_); memcpy(data4crc.pData_, key.pData_, key.size_); memcpy(data4crc.pData_ + key.size_, "\0", 1); memcpy(data4crc.pData_ + key.size_ + 1, data.pData_, data.size_); uLong crc = crc32(0L, Z_NULL, 0); crc = crc32(crc, data4crc.pData_, data4crc.size_); ul2Data(chunkCRC, crc, Exiv2::bigEndian); ul2Data(chunkDataSize, data4crc.size_, Exiv2::bigEndian); chunkData.alloc(4 + type.size_ + data4crc.size_ + 4); memcpy(chunkData.pData_, chunkDataSize, 4); memcpy(chunkData.pData_ + 4, type.pData_, type.size_); memcpy(chunkData.pData_ + 4 + type.size_, data4crc.pData_, data4crc.size_); memcpy(chunkData.pData_ + 4 + type.size_ + data4crc.size_, chunkCRC, 4); } return chunkData; } // PngChunk::makeAsciiTxtChunk DataBuf PngChunk::makeUtf8TxtChunk(const DataBuf& key, const DataBuf& data, bool compress) { DataBuf type(4); DataBuf textData; // text compressed or not. DataBuf data4crc; DataBuf chunkData; byte chunkDataSize[4]; byte chunkCRC[4]; // Compressed text chunk using ZLib. // Data format : key ("iTXt") + 0x00 + compression flag (0x00: uncompressed - 0x01: compressed) + // compression method (0x00) + language id (null) + 0x00 + // translated key (null) + 0x00 + text (compressed or not) // Chunk structure: data lenght (4 bytes) + chunk type (4 bytes) + data + CRC (4 bytes) memcpy(type.pData_, "iTXt", 4); if (compress) { const unsigned char flags[] = {0x00, 0x01, 0x00, 0x00, 0x00}; zlibCompress(data.pData_, data.size_, textData); data4crc.alloc(key.size_ + 5 + textData.size_); memcpy(data4crc.pData_, key.pData_, key.size_); memcpy(data4crc.pData_ + key.size_, flags, 5); } else { const unsigned char flags[] = {0x00, 0x00, 0x00, 0x00, 0x00}; textData = DataBuf(data.pData_, data.size_); data4crc.alloc(key.size_ + 5 + textData.size_); memcpy(data4crc.pData_, key.pData_, key.size_); memcpy(data4crc.pData_ + key.size_, flags, 5); } memcpy(data4crc.pData_ + key.size_ + 5, textData.pData_, textData.size_); uLong crc = crc32(0L, Z_NULL, 0); crc = crc32(crc, data4crc.pData_, data4crc.size_); ul2Data(chunkCRC, crc, Exiv2::bigEndian); ul2Data(chunkDataSize, data4crc.size_, Exiv2::bigEndian); chunkData.alloc(4 + type.size_ + data4crc.size_ + 4); memcpy(chunkData.pData_, chunkDataSize, 4); memcpy(chunkData.pData_ + 4, type.pData_, type.size_); memcpy(chunkData.pData_ + 4 + type.size_, data4crc.pData_, data4crc.size_); memcpy(chunkData.pData_ + 4 + type.size_ + data4crc.size_, chunkCRC, 4); return chunkData; } // PngChunk::makeUtf8TxtChunk DataBuf PngChunk::readRawProfile(const DataBuf& text) { DataBuf info; register long i; register unsigned char *dp; const char *sp; unsigned int nibbles; long length; unsigned char unhex[103]={0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,1, 2,3,4,5,6,7,8,9,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,10,11,12, 13,14,15}; sp = (char*)text.pData_+1; // Look for newline while (*sp != '\n') sp++; // Look for length while (*sp == '\0' || *sp == ' ' || *sp == '\n') sp++; length = (long) atol(sp); while (*sp != ' ' && *sp != '\n') sp++; // Allocate space if (length == 0) { #ifdef DEBUG std::cerr << "Exiv2::PngChunk::readRawProfile: Unable To Copy Raw Profile: invalid profile length\n"; #endif return DataBuf(); } info.alloc(length); if (info.size_ != length) { #ifdef DEBUG std::cerr << "Exiv2::PngChunk::readRawProfile: Unable To Copy Raw Profile: cannot allocate memory\n"; #endif return DataBuf(); } // Copy profile, skipping white space and column 1 "=" signs dp = (unsigned char*)info.pData_; nibbles = length * 2; for (i = 0; i < (long) nibbles; i++) { while (*sp < '0' || (*sp > '9' && *sp < 'a') || *sp > 'f') { if (*sp == '\0') { #ifdef DEBUG std::cerr << "Exiv2::PngChunk::readRawProfile: Unable To Copy Raw Profile: ran out of data\n"; #endif return DataBuf(); } sp++; } if (i%2 == 0) *dp = (unsigned char) (16*unhex[(int) *sp++]); else (*dp++) += unhex[(int) *sp++]; } return info; } // PngChunk::readRawProfile DataBuf PngChunk::writeRawProfile(const DataBuf& profile_data, const DataBuf& profile_type) { register long i; char *sp=0; char *dp=0; char *text=0; unsigned int allocated_length, description_length, text_length; unsigned char hex[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; DataBuf formatedData; description_length = profile_type.size_; allocated_length = profile_data.size_*2 + (profile_data.size_ >> 5) + 20 + description_length; text = new char[allocated_length]; sp = (char*)profile_data.pData_; dp = text; *dp++='\n'; copyString(dp, (const char *)profile_type.pData_, allocated_length); dp += description_length; *dp++='\n'; formatString(dp, allocated_length - strlen(text), "%8lu ", profile_data.size_); dp += 8; for (i=0; i < (long)profile_data.size_; i++) { if (i%36 == 0) *dp++='\n'; *(dp++)=(char) hex[((*sp >> 4) & 0x0f)]; *(dp++)=(char) hex[((*sp++) & 0x0f)]; } *dp++='\n'; *dp='\0'; text_length = (unsigned int)(dp-text); if (text_length <= allocated_length) { formatedData.alloc(text_length); memcpy(formatedData.pData_, text, text_length); } delete [] text; return formatedData; } // PngChunk::writeRawProfile size_t PngChunk::copyString(char* destination, const char* source, const size_t length) { register char *q; register const char *p; register size_t i; if ( !destination || !source || length == 0 ) return 0; p = source; q = destination; i = length; if ((i != 0) && (--i != 0)) { do { if ((*q++=(*p++)) == '\0') break; } while (--i != 0); } if (i == 0) { if (length != 0) *q='\0'; do { } while (*p++ != '\0'); } return((size_t) (p-source-1)); } // PngChunk::copyString long PngChunk::formatString(char* string, const size_t length, const char* format, ...) { long n; va_list operands; va_start(operands,format); n = (long) formatStringList(string, length, format, operands); va_end(operands); return(n); } // PngChunk::formatString long PngChunk::formatStringList(char* string, const size_t length, const char* format, va_list operands) { int n = vsnprintf(string, length, format, operands); if (n < 0) string[length-1] = '\0'; return((long) n); } // PngChunk::formatStringList }} // namespace Internal, Exiv2 #endif // ifdef EXV_HAVE_LIBZ