Commit message: Fix ICC profile in PNG images
CVE: CVE-2019-13108
Vulnerability description: An integer overflow in Exiv2 through 0.27.1 allows an attacker to cause a denial of service (SIGSEGV) via a crafted PNG image file, because PngImage::readMetadata mishandles a zero value for iccOffset.

Project: exiv2
File: pngimage.cpp

    std::string::size_type findi(const std::string& str, const std::string& substr)
    {
        return upper(str).find(upper(substr) );
    }

Project: exiv2
File: pngimage.cpp

    static bool zlibToCompressed(const byte* bytes,long length, DataBuf& result)
    {
        uLongf compressedLen = length; // just a starting point
        int    zlibResult;

        do {
            result.alloc(compressedLen);
            zlibResult = compress((Bytef*)result.pData_,&compressedLen,bytes,length);
            if (zlibResult == Z_BUF_ERROR) {
                // the compressedArray needs to be larger
                result.free();
                compressedLen *= 2;
            } else {
                result.free();
                result.alloc(compressedLen);
                zlibResult = compress((Bytef*)result.pData_,&compressedLen,bytes,length);
            }
        } while (zlibResult == Z_BUF_ERROR);

        return zlibResult == Z_OK ;
    }

Project: exiv2
File: pngimage.cpp

    PngImage::PngImage(BasicIo::UniquePtr io, bool create)
            : Image(ImageType::png, mdExif | mdIptc | mdXmp | mdComment, std::move(io))
    {
        if (create)
        {
            if (io_->open() == 0)
            {
#ifdef DEBUG
                std::cerr << "Exiv2::PngImage:: Creating PNG image to memory\n";
#endif
                IoCloser closer(*io_);
                if (io_->write(pngBlank, sizeof(pngBlank)) != sizeof(pngBlank))
                {
#ifdef DEBUG
                    std::cerr << "Exiv2::PngImage:: Failed to create PNG image on memory\n";
#endif
                }
            }
        }
    } // PngImage::PngImage

Project: exiv2
File: pngimage.cpp

    void PngImage::doWriteMetadata(BasicIo& outIo)
    {
        if (!io_->isopen()) throw Error(kerInputDataReadFailed);
        if (!outIo.isopen()) throw Error(kerImageWriteFailed);

#ifdef DEBUG
        std::cout << "Exiv2::PngImage::doWriteMetadata: Writing PNG file " << io_->path() << "\n";
        std::cout << "Exiv2::PngImage::doWriteMetadata: tmp file created " << outIo.path() << "\n";
#endif

        // Ensure that this is the correct image type
        if (!isPngType(*io_, true))
        {
            if (io_->error() || io_->eof()) throw Error(kerInputDataReadFailed);
            throw Error(kerNoImageInInputData);
        }

        // Write PNG Signature.
        if (outIo.write(pngSignature, 8) != 8) throw Error(kerImageWriteFailed);

        DataBuf cheaderBuf(8);       // Chunk header : 4 bytes (data size) + 4 bytes (chunk type).

        while(!io_->eof())
        {
            // Read chunk header.

            std::memset(cheaderBuf.pData_, 0x00, cheaderBuf.size_);
            long bufRead = io_->read(cheaderBuf.pData_, cheaderBuf.size_);
            if (io_->error()) throw Error(kerFailedToReadImageData);
            if (bufRead != cheaderBuf.size_) throw Error(kerInputDataReadFailed);

            // Decode chunk data length.

            uint32_t dataOffset = getULong(cheaderBuf.pData_, bigEndian);
            if (dataOffset > 0x7FFFFFFF) throw Exiv2::Error(kerFailedToReadImageData);

            // Read whole chunk : Chunk header + Chunk data (not fixed size - can be null) + CRC (4 bytes).

            DataBuf chunkBuf(8 + dataOffset + 4);                     // Chunk header (8 bytes) + Chunk data + CRC (4 bytes).
            memcpy(chunkBuf.pData_, cheaderBuf.pData_, 8);            // Copy header.
            bufRead = io_->read(chunkBuf.pData_ + 8, dataOffset + 4); // Extract chunk data + CRC
            if (io_->error()) throw Error(kerFailedToReadImageData);
            if (bufRead != (long)(dataOffset + 4)) throw Error(kerInputDataReadFailed);

            char szChunk[5];
            memcpy(szChunk,cheaderBuf.pData_ + 4,4);
            szChunk[4]  = 0;

            if (!memcmp(cheaderBuf.pData_ + 4, "IEND", 4))
            {
                // Last chunk found: we write it and done.
#ifdef DEBUG
                std::cout << "Exiv2::PngImage::doWriteMetadata: Write IEND chunk (length: " << dataOffset << ")\n";
#endif
                if (outIo.write(chunkBuf.pData_, chunkBuf.size_) != chunkBuf.size_) throw Error(kerImageWriteFailed);
                return;
            }
            else if (!memcmp(cheaderBuf.pData_ + 4, "IHDR", 4))
            {
#ifdef DEBUG
                std::cout << "Exiv2::PngImage::doWriteMetadata: Write IHDR chunk (length: " << dataOffset << ")\n";
#endif
                if (outIo.write(chunkBuf.pData_, chunkBuf.size_) != chunkBuf.size_) throw Error(kerImageWriteFailed);

                // Write all updated metadata here, just after IHDR.
                if (!comment_.empty())
                {
                    // Update Comment data to a new PNG chunk
                    std::string chunk = PngChunk::makeMetadataChunk(comment_, mdComment);
                    if (outIo.write((const byte*)chunk.data(), static_cast<long>(chunk.size())) != (long)chunk.size())
                    {
                        throw Error(kerImageWriteFailed);
                    }
                }

                if (exifData_.count() > 0)
                {
                    // Update Exif data to a new PNG chunk
                    Blob blob;
                    ExifParser::encode(blob, littleEndian, exifData_);
                    if (blob.size() > 0)
                    {
                        static const char exifHeader[] = { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 };
                        std::string rawExif =   std::string(exifHeader, 6)
                                              + std::string((const char*)&blob[0], blob.size());
                        std::string chunk = PngChunk::makeMetadataChunk(rawExif, mdExif);
                        if (outIo.write((const byte*)chunk.data(), static_cast<long>(chunk.size())) != (long)chunk.size())
                        {
                            throw Error(kerImageWriteFailed);
                        }
                    }
                }

                if (iptcData_.count() > 0)
                {
                    // Update IPTC data to a new PNG chunk
                    DataBuf newPsData = Photoshop::setIptcIrb(0, 0, iptcData_);
                    if (newPsData.size_ > 0)
                    {
                        std::string rawIptc((const char*)newPsData.pData_, newPsData.size_);
                        std::string chunk = PngChunk::makeMetadataChunk(rawIptc, mdIptc);
                        if (outIo.write((const byte*)chunk.data(), static_cast<long>(chunk.size())) != (long)chunk.size())
                        {
                            throw Error(kerImageWriteFailed);
                        }
                    }
                }

                if ( iccProfileDefined() ) {
                    DataBuf compressed;
                    if ( zlibToCompressed(iccProfile_.pData_,iccProfile_.size_,compressed) ) {

                        const byte* header   = (const byte*) "ICC PROFILE\0\0" ; // \0 = default compression
                        const byte*  type    = (const byte*) "iCCP";
                        uint32_t headerLen   = 13 ;
                        uint32_t typeLen     = 4;
                        uint32_t chunkLength = headerLen + compressed.size_ ;
                        byte     length[4];
                        ul2Data (length,chunkLength,bigEndian);

                        // calculate CRC
                        uLong   tmp = crc32(0L, Z_NULL, 0);
                        tmp         = crc32(tmp, (const Bytef*)type             ,typeLen);
                        tmp         = crc32(tmp, (const Bytef*)header           ,headerLen);
                        tmp         = crc32(tmp, (const Bytef*)compressed.pData_,compressed.size_);
                        byte    crc[4];
                        ul2Data(crc, tmp, bigEndian);

                        if( outIo.write(length,4)         != 4
                        ||  outIo.write(type  ,typeLen)   != (long) typeLen
                        ||  outIo.write(header,headerLen) != (long) headerLen
                        ||  outIo.write (compressed.pData_,compressed.size_) != compressed.size_
                        ||  outIo.write(crc,4)            != 4
                        ){
                            throw Error(kerImageWriteFailed);
                        }
#ifdef DEBUG
                        std::cout << "Exiv2::PngImage::doWriteMetadata: build iCCP"
                        << " chunk (length: " << compressed.size_ + headerLen << ")" << std::endl;
#endif
                    }
                }

                if (writeXmpFromPacket() == false) {
                    if (XmpParser::encode(xmpPacket_, xmpData_) > 1) {
#ifndef SUPPRESS_WARNINGS
                        EXV_ERROR << "Failed to encode XMP metadata.\n";
#endif
                    }
                }
                if (xmpPacket_.size() > 0) {
                    // Update XMP data to a new PNG chunk
                    std::string chunk = PngChunk::makeMetadataChunk(xmpPacket_, mdXmp);
                    if (outIo.write((const byte*)chunk.data(), static_cast<long>(chunk.size())) != (long)chunk.size()) {
                        throw Error(kerImageWriteFailed);
                    }
                }
            }
            else if (!memcmp(cheaderBuf.pData_ + 4, "tEXt", 4) ||
                     !memcmp(cheaderBuf.pData_ + 4, "zTXt", 4) ||
                     !memcmp(cheaderBuf.pData_ + 4, "iTXt", 4) ||
                     !memcmp(cheaderBuf.pData_ + 4, "iCCP", 4))
            {
                DataBuf key = PngChunk::keyTXTChunk(chunkBuf, true);
                if (compare("Raw profile type exif", key, 21) ||
                    compare("Raw profile type APP1", key, 21) ||
                    compare("Raw profile type iptc", key, 21) ||
                    compare("Raw profile type xmp",  key, 20) ||
                    compare("XML:com.adobe.xmp",     key, 17) ||
                    compare("icc",                   key,  3) || // see test/data/imagemagick.png
                    compare("ICC",                   key,  3) ||
                    compare("Description",           key, 11))
                {
#ifdef DEBUG
                    std::cout << "Exiv2::PngImage::doWriteMetadata: strip " << szChunk
                              << " chunk (length: " << dataOffset << ")" << std::endl;
#endif
                }
                else
                {
#ifdef DEBUG
                    std::cout << "Exiv2::PngImage::doWriteMetadata: write " << szChunk
                              << " chunk (length: " << dataOffset << ")" << std::endl;
#endif
                    if (outIo.write(chunkBuf.pData_, chunkBuf.size_) != chunkBuf.size_) throw Error(kerImageWriteFailed);
                }
            }
            else
            {
                // Write all others chunk as well.
#ifdef DEBUG
                std::cout << "Exiv2::PngImage::doWriteMetadata:  copy " << szChunk
                          << " chunk (length: " << dataOffset << ")" << std::endl;
#endif
                if (outIo.write(chunkBuf.pData_, chunkBuf.size_) != chunkBuf.size_) throw Error(kerImageWriteFailed);

            }
        }

    } // PngImage::doWriteMetadata

Project: exiv2
File: pngimage.cpp

    void PngImage::writeMetadata()
    {
        if (io_->open() != 0)
        {
            throw Error(kerDataSourceOpenFailed, io_->path(), strError());
        }
        IoCloser closer(*io_);
        BasicIo::UniquePtr tempIo(new MemIo);
        assert (tempIo.get() != 0);

        doWriteMetadata(*tempIo); // may throw
        io_->close();
        io_->transfer(*tempIo); // may throw

    } // PngImage::writeMetadata

Project: exiv2
File: pngimage.cpp

    void PngImage::printStructure(std::ostream& out, PrintStructureOption option, int depth)
    {
        if (io_->open() != 0) {
            throw Error(kerDataSourceOpenFailed, io_->path(), strError());
        }
        // Ensure that this is the correct image type
        if (!isPngType(*io_, true)) {
            if (io_->error() || io_->eof()) throw Error(kerFailedToReadImageData);
            throw Error(kerNotAnImage, "PNG");
        }

        char    chType[5];
        chType[0]=0;
        chType[4]=0;

        if ( option == kpsBasic || option == kpsXMP || option == kpsIccProfile || option == kpsRecursive ) {

            const std::string xmpKey  = "XML:com.adobe.xmp";
            const std::string exifKey = "Raw profile type exif";
            const std::string app1Key = "Raw profile type APP1";
            const std::string iptcKey = "Raw profile type iptc";
            const std::string iccKey  = "icc";
            const std::string softKey = "Software";
            const std::string commKey = "Comment";
            const std::string descKey = "Description";

            bool bPrint = option == kpsBasic || option == kpsRecursive ;
            if ( bPrint ) {
                out << "STRUCTURE OF PNG FILE: " << io_->path() << std::endl;
                out << " address | chunk |  length | data                           | checksum" << std::endl;
            }

            const long imgSize = (long) io_->size();
            DataBuf    cheaderBuf(8);

            while( !io_->eof() && ::strcmp(chType,"IEND") ) {
                size_t address = io_->tell();

                std::memset(cheaderBuf.pData_, 0x0, cheaderBuf.size_);
                long bufRead = io_->read(cheaderBuf.pData_, cheaderBuf.size_);
                if (io_->error()) throw Error(kerFailedToReadImageData);
                if (bufRead != cheaderBuf.size_) throw Error(kerInputDataReadFailed);

                // Decode chunk data length.
                uint32_t dataOffset = Exiv2::getULong(cheaderBuf.pData_, Exiv2::bigEndian);
                for (int i = 4; i < 8; i++) {
                    chType[i-4]=cheaderBuf.pData_[i];
                }

                // test that we haven't hit EOF, or wanting to read excessive data
                long restore = io_->tell();
                if(  restore == -1
                ||  dataOffset > uint32_t(0x7FFFFFFF)
                ||  static_cast<long>(dataOffset) > imgSize - restore
                ){
                    throw Exiv2::Error(kerFailedToReadImageData);
                }

                DataBuf   buff(dataOffset);
                io_->read(buff.pData_,dataOffset);
                io_->seek(restore, BasicIo::beg);

                // format output
                const int    iMax = 30 ;
                const uint32_t blen = dataOffset > iMax ? iMax : dataOffset ;
                std::string dataString = "";
                // if blen == 0 => slice construction fails
                if (blen > 0) {
                    std::stringstream ss;
                    ss << Internal::binaryToString(makeSlice(buff, 0, blen));
                    dataString = ss.str();
                }
                while (      dataString.size() < iMax ) dataString += ' ';
                dataString = dataString.substr(0,iMax);

                if ( bPrint ) {
                    io_->seek(dataOffset, BasicIo::cur);// jump to checksum
                    byte checksum[4];
                    io_->read(checksum,4);
                    io_->seek(restore, BasicIo::beg)   ;// restore file pointer

                    out << Internal::stringFormat("%8d | %-5s |%8d | "
                                                  ,(uint32_t)address, chType,dataOffset)
                        << dataString
                        << Internal::stringFormat(" | 0x%02x%02x%02x%02x"
                                                   ,checksum[0],checksum[1],checksum[2],checksum[3])
                        << std::endl;
                }

                // chunk type
                bool tEXt  = std::strcmp(chType,"tEXt")== 0;
                bool zTXt  = std::strcmp(chType,"zTXt")== 0;
                bool iCCP  = std::strcmp(chType,"iCCP")== 0;
                bool iTXt  = std::strcmp(chType,"iTXt")== 0;

                // for XMP, ICC etc: read and format data
                bool bXMP  = option == kpsXMP        && findi(dataString,xmpKey)==0;
                bool bICC  = option == kpsIccProfile && findi(dataString,iccKey)==0;
                bool bExif = option == kpsRecursive  &&(findi(dataString,exifKey)==0 || findi(dataString,app1Key)==0);
                bool bIptc = option == kpsRecursive  && findi(dataString,iptcKey)==0;
                bool bSoft = option == kpsRecursive  && findi(dataString,softKey)==0;
                bool bComm = option == kpsRecursive  && findi(dataString,commKey)==0;
                bool bDesc = option == kpsRecursive  && findi(dataString,descKey)==0;
                bool bDump = bXMP || bICC || bExif || bIptc || bSoft || bComm || bDesc ;

                if( bDump ) {
                    DataBuf   dataBuf;
                    byte*     data   = new byte[dataOffset+1];
                    data[dataOffset] = 0;
                    io_->read(data,dataOffset);
                    io_->seek(restore, BasicIo::beg);
                    uint32_t  name_l = (uint32_t) std::strlen((const char*)data)+1; // leading string length
                    uint32_t  start  = name_l;

                    // decode the chunk
                    bool bGood = false;
                    if ( tEXt ) {
                        bGood = tEXtToDataBuf(data+name_l,dataOffset-name_l,dataBuf);
                    }
                    if ( zTXt || iCCP ) {
                        bGood = zlibToDataBuf(data+name_l+1,dataOffset-name_l-1,dataBuf); // +1 = 'compressed' flag
                    }
                    if ( iTXt ) {
                        bGood = (start+3) < dataOffset ;    // good if not a nul chunk
                    }

                    // format is content dependent
                    if ( bGood ) {
                        bool bLF = false;
                        if ( bXMP ) {
                            while ( !data[start] && start < dataOffset) start++; // skip leading nul bytes
                            out <<  data+start;             // output the xmp
                        }

                        if ( bExif || bIptc ) {
                            DataBuf parsedBuf = PngChunk::readRawProfile(dataBuf,tEXt);
#if DEBUG
                            std::cerr << Exiv2::Internal::binaryToString(parsedBuf.pData_, parsedBuf.size_>50?50:parsedBuf.size_,0) << std::endl;
#endif
                            if ( parsedBuf.size_ ) {
                                if ( bExif ) {
                                    // create memio object with the data, then print the structure
                                    BasicIo::UniquePtr p = BasicIo::UniquePtr(new MemIo(parsedBuf.pData_+6,parsedBuf.size_-6));
                                    printTiffStructure(*p,out,option,depth);
                                }
                                if ( bIptc ) {
                                    IptcData::printStructure(out, makeSlice(parsedBuf.pData_, 0, parsedBuf.size_), depth);
                                }
                            }
                        }

                        if ( bSoft && dataBuf.size_ > 0) {
                            DataBuf     s(dataBuf.size_+1);               // allocate buffer with an extra byte
                            memcpy(s.pData_,dataBuf.pData_,dataBuf.size_);// copy in the dataBuf
                            s.pData_[dataBuf.size_] = 0 ;                 // nul terminate it
                            const char* str = (const char*) s.pData_;     // give it name
                            out << Internal::indent(depth) << (const char*) buff.pData_ << ": " << str ;
                            bLF=true;
                        }

                        if ( bICC || bComm ) {
                            out.write((const char*) dataBuf.pData_,dataBuf.size_);
                            bLF = bComm ;
                        }

                        if ( bDesc && iTXt ) {
                            DataBuf decoded = PngChunk::decodeTXTChunk(buff,PngChunk::iTXt_Chunk );
                            out.write((const char*)decoded.pData_,decoded.size_);
                            bLF = true;
                        }

                        if ( bLF ) out << std::endl;
                    }
                    delete[] data;
                }
                io_->seek(dataOffset+4, BasicIo::cur);// jump past checksum
                if (io_->error()) throw Error(kerFailedToReadImageData);
            }
        }
    }

Project: exiv2
File: pngimage.cpp

    Image::UniquePtr newPngInstance(BasicIo::UniquePtr io, bool create)
    {
        Image::UniquePtr image(new PngImage(std::move(io), create));
        if (!image->good())
        {
            image.reset();
        }
        return image;
    }

Project: exiv2
File: pngimage.cpp

    static bool zlibToDataBuf(const byte* bytes,long length, DataBuf& result)
    {
        uLongf uncompressedLen = length * 2; // just a starting point
        int    zlibResult;

        do {
            result.alloc(uncompressedLen);
            zlibResult = uncompress((Bytef*)result.pData_,&uncompressedLen,bytes,length);
            // if result buffer is large than necessary, redo to fit perfectly.
            if (zlibResult == Z_OK && (long) uncompressedLen < result.size_ ) {
                result.free();

                result.alloc(uncompressedLen);
                zlibResult = uncompress((Bytef*)result.pData_,&uncompressedLen,bytes,length);
            }
            if (zlibResult == Z_BUF_ERROR) {
                // the uncompressed buffer needs to be larger
                result.free();

                // Sanity - never bigger than 16mb
                if  (uncompressedLen > 16*1024*1024) zlibResult = Z_DATA_ERROR;
                else uncompressedLen *= 2;
            }
        } while (zlibResult == Z_BUF_ERROR);

        return zlibResult == Z_OK ;
    }

Project: exiv2
File: pngimage.cpp

    std::string upper(const std::string& str)
    {
        std::string result;
        transform(str.begin(), str.end(), std::back_inserter(result), toupper);
        return result;
    }

Project: exiv2
File: pngimage.cpp

    bool isPngType(BasicIo& iIo, bool advance)
    {
        const int32_t len = 8;
        byte buf[len];
        iIo.read(buf, len);
        if (iIo.error() || iIo.eof())
        {
            return false;
        }
        int rc = memcmp(buf, pngSignature, 8);
        if (!advance || rc != 0)
        {
            iIo.seek(-len, BasicIo::cur);
        }

        return rc == 0;
    }

Project: exiv2
File: pngimage.cpp

    void PngImage::readMetadata()
    {
#ifdef DEBUG
        std::cerr << "Exiv2::PngImage::readMetadata: Reading PNG file " << io_->path() << std::endl;
#endif
        if (io_->open() != 0)
        {
            throw Error(kerDataSourceOpenFailed, io_->path(), strError());
        }
        IoCloser closer(*io_);
        // Ensure that this is the correct image type
        if (!isPngType(*io_, true))
        {
            if (io_->error() || io_->eof()) throw Error(kerFailedToReadImageData);
            throw Error(kerNotAnImage, "PNG");
        }
        clearMetadata();

        const long imgSize = (long) io_->size();
        DataBuf cheaderBuf(8);       // Chunk header size : 4 bytes (data size) + 4 bytes (chunk type).

        while(!io_->eof())
        {
            // Read chunk header.

#ifdef DEBUG
            std::cout << "Exiv2::PngImage::readMetadata: Position: " << io_->tell() << std::endl;
#endif
            std::memset(cheaderBuf.pData_, 0x0, cheaderBuf.size_);
            long bufRead = io_->read(cheaderBuf.pData_, cheaderBuf.size_);
            if (io_->error()) {
                throw Error(kerFailedToReadImageData);
            }
            if (bufRead != cheaderBuf.size_) {
                throw Error(kerInputDataReadFailed);
            }

            // Decode chunk data length.
            uint32_t dataOffset = Exiv2::getULong(cheaderBuf.pData_, Exiv2::bigEndian);
            long pos = io_->tell();
            if (pos == -1 ||
                dataOffset > uint32_t(0x7FFFFFFF) ||
                static_cast<long>(dataOffset) > imgSize - pos) {
                throw Exiv2::Error(kerFailedToReadImageData);
            }

#ifdef DEBUG
            std::string chunkName(reinterpret_cast<char *>(cheaderBuf.pData_) + 4, 4);
            std::cout << "Exiv2::PngImage::readMetadata: chunk name: " << chunkName << std::endl;
#endif

            // Perform a chunk triage for item that we need.
            if (!memcmp(cheaderBuf.pData_ + 4, "IEND", 4) ||
                !memcmp(cheaderBuf.pData_ + 4, "IHDR", 4) ||
                !memcmp(cheaderBuf.pData_ + 4, "tEXt", 4) ||
                !memcmp(cheaderBuf.pData_ + 4, "zTXt", 4) ||
                !memcmp(cheaderBuf.pData_ + 4, "iTXt", 4) ||
                !memcmp(cheaderBuf.pData_ + 4, "iCCP", 4))
            {
                // Extract chunk data.

                DataBuf cdataBuf(dataOffset);
                bufRead = io_->read(cdataBuf.pData_, dataOffset);
                if (io_->error()) {
                    throw Error(kerFailedToReadImageData);
                }
                if (bufRead != (long)dataOffset) {
                    throw Error(kerInputDataReadFailed);
                }

                if (!memcmp(cheaderBuf.pData_ + 4, "IEND", 4))
                {
                    // Last chunk found: we stop parsing.
#ifdef DEBUG
                    std::cout << "Exiv2::PngImage::readMetadata: Found IEND chunk with length: " << dataOffset << std::endl;
#endif
                    return;
                }
                else if (!memcmp(cheaderBuf.pData_ + 4, "IHDR", 4))
                {
#ifdef DEBUG
                    std::cout << "Exiv2::PngImage::readMetadata: Found IHDR chunk with length: " << dataOffset << std::endl;
#endif
                    if (cdataBuf.size_ >= 8) {
                        PngChunk::decodeIHDRChunk(cdataBuf, &pixelWidth_, &pixelHeight_);
                    }
                }
                else if (!memcmp(cheaderBuf.pData_ + 4, "tEXt", 4))
                {
#ifdef DEBUG
                    std::cout << "Exiv2::PngImage::readMetadata: Found tEXt chunk with length: " << dataOffset << std::endl;
#endif
                    PngChunk::decodeTXTChunk(this, cdataBuf, PngChunk::tEXt_Chunk);
                }
                else if (!memcmp(cheaderBuf.pData_ + 4, "zTXt", 4))
                {
#ifdef DEBUG
                    std::cout << "Exiv2::PngImage::readMetadata: Found zTXt chunk with length: " << dataOffset << std::endl;
#endif
                    PngChunk::decodeTXTChunk(this, cdataBuf, PngChunk::zTXt_Chunk);
                }
                else if (!memcmp(cheaderBuf.pData_ + 4, "iTXt", 4))
                {
#ifdef DEBUG
                    std::cout << "Exiv2::PngImage::readMetadata: Found iTXt chunk with length: " << dataOffset << std::endl;
#endif
                    PngChunk::decodeTXTChunk(this, cdataBuf, PngChunk::iTXt_Chunk);
                }
                else if (!memcmp(cheaderBuf.pData_ + 4, "iCCP", 4))
                {
                    // The ICC profile name can vary from 1-79 characters.
                    uint32_t iccOffset = 0;
                    while (iccOffset < 80 && iccOffset < dataOffset) {

                        const byte* profileName = cdataBuf.pData_ + iccOffset;
                        ++iccOffset;

                        if (*profileName == 0x00)
                            break;
                    }

                    ++iccOffset; // +1 = 'compressed' flag

                    zlibToDataBuf(cdataBuf.pData_ +iccOffset,dataOffset-iccOffset,iccProfile_);
#ifdef DEBUG
                    std::cout << "Exiv2::PngImage::readMetadata: Found iCCP chunk length: " << dataOffset  << std::endl;
                    std::cout << "Exiv2::PngImage::readMetadata: iccProfile.size_ : " << iccProfile_.size_ << std::endl;
#endif
                }

                // Set dataOffset to null like chunk data have been extracted previously.
                dataOffset = 0;
            }

            // Move to the next chunk: chunk data size + 4 CRC bytes.
#ifdef DEBUG
            std::cout << "Exiv2::PngImage::readMetadata: Seek to offset: " << dataOffset + 4 << std::endl;
#endif
            io_->seek(dataOffset + 4 , BasicIo::cur);
            if (io_->error() || io_->eof()) {
                throw Error(kerFailedToReadImageData);
            }
        }

    } // PngImage::readMetadata

Project: exiv2
File: pngimage.cpp

    std::string PngImage::mimeType() const
    {
        return "image/png";
    }

Project: exiv2
File: pngimage.cpp

    static bool tEXtToDataBuf(const byte* bytes,long length,DataBuf& result)
    {
        static const char* hexdigits = "0123456789ABCDEF";
        static int         value   [256] ;
        static bool        bFirst = true ;
        if ( bFirst ) {
            for ( int i = 0 ; i < 256 ; i++ )
                value[i] = 0;
            for ( int i = 0 ; i < 16 ; i++ ) {
                value[tolower(hexdigits[i])]=i+1;
                value[toupper(hexdigits[i])]=i+1;
            }
            bFirst = false;
        }

        // calculate length and allocate result;
        // count: number of \n in the header
        long        count=0;
        // p points to the current position in the array bytes
        const byte* p = bytes ;

        // header is '\nsomething\n number\n hex'
        // => increment p until it points to the byte after the last \n
        //    p must stay within bounds of the bytes array!
        while ((count < 3) && (p - bytes < length)) {
            // length is later used for range checks of p => decrement it for each increment of p
            --length;
            if ( *p++ == '\n' ) {
                count++;
            }
        }
        for ( long i = 0 ; i < length ; i++ )
            if ( value[p[i]] )
                ++count;
        result.alloc((count+1)/2) ;

        // hex to binary
        count   = 0 ;
        byte* r = result.pData_;
        int   n = 0 ; // nibble
        for ( long i = 0 ; i < length ; i++ ) {
            if ( value[p[i]] ) {
                int v = value[p[i]]-1 ;
                if ( ++count % 2 ) n = v*16 ; // leading digit
                else *r++ =        n + v    ; // trailing
            }
        }
        return true;
    }