Coverage Report

Created: 2026-03-12 07:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/exiv2/src/pngimage.cpp
Line
Count
Source
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
3
// included header files
4
#include "config.h"
5
6
#ifdef EXV_HAVE_LIBZ
7
#include <zlib.h>  // To uncompress IccProfiles
8
9
#include "basicio.hpp"
10
#include "enforce.hpp"
11
#include "error.hpp"
12
#include "futils.hpp"
13
#include "image.hpp"
14
#include "image_int.hpp"
15
#include "photoshop.hpp"
16
#include "pngchunk_int.hpp"
17
#include "pngimage.hpp"
18
#include "tiffimage.hpp"
19
#include "types.hpp"
20
#include "utils.hpp"
21
22
#include <array>
23
#include <cstring>
24
#include <iostream>
25
26
namespace {
27
// Signature from front of PNG file
28
constexpr std::array<unsigned char, 8> pngSignature{
29
    0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
30
};
31
32
constexpr unsigned char pngBlank[] = {
33
    0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00,
34
    0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53, 0xde, 0x00, 0x00, 0x00,
35
    0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73,
36
    0x00, 0x00, 0x0b, 0x13, 0x00, 0x00, 0x0b, 0x13, 0x01, 0x00, 0x9a, 0x9c, 0x18, 0x00, 0x00, 0x00, 0x0c, 0x49,
37
    0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, 0xf8, 0xff, 0xff, 0x3f, 0x00, 0x05, 0xfe, 0x02, 0xfe, 0xdc, 0xcc, 0x59,
38
    0xe7, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
39
};
40
41
const auto nullComp = reinterpret_cast<const Exiv2::byte*>("\0\0");
42
const auto typeExif = reinterpret_cast<const Exiv2::byte*>("eXIf");
43
const auto typeICCP = reinterpret_cast<const Exiv2::byte*>("iCCP");
44
0
bool compare(std::string_view str, const Exiv2::DataBuf& buf) {
45
0
  const auto minlen = std::min<size_t>(str.size(), buf.size());
46
0
  return buf.cmpBytes(0, str.data(), minlen) == 0;
47
0
}
48
}  // namespace
49
50
// *****************************************************************************
51
// class member definitions
52
namespace Exiv2 {
53
using namespace Internal;
54
55
PngImage::PngImage(BasicIo::UniquePtr io, bool create) :
56
1.47k
    Image(ImageType::png, mdExif | mdIptc | mdXmp | mdComment, std::move(io)) {
57
1.47k
  if (create && io_->open() == 0) {
58
#ifdef EXIV2_DEBUG_MESSAGES
59
    std::cerr << "Exiv2::PngImage:: Creating PNG image to memory\n";
60
#endif
61
0
    IoCloser closer(*io_);
62
0
    if (io_->write(pngBlank, sizeof(pngBlank)) != sizeof(pngBlank)) {
63
#ifdef EXIV2_DEBUG_MESSAGES
64
      std::cerr << "Exiv2::PngImage:: Failed to create PNG image on memory\n";
65
#endif
66
0
    }
67
0
  }
68
1.47k
}
69
70
43
std::string PngImage::mimeType() const {
71
43
  return "image/png";
72
43
}
73
74
1.88k
static bool zlibToDataBuf(const byte* bytes, uLongf length, DataBuf& result) {
75
1.88k
  uLongf uncompressedLen = length;  // just a starting point
76
1.88k
  int zlibResult = Z_BUF_ERROR;
77
78
5.63k
  while (zlibResult == Z_BUF_ERROR) {
79
3.74k
    result.alloc(uncompressedLen);
80
3.74k
    zlibResult = uncompress(result.data(), &uncompressedLen, bytes, length);
81
    // if result buffer is large than necessary, redo to fit perfectly.
82
3.74k
    if (zlibResult == Z_OK && uncompressedLen < result.size()) {
83
579
      result.reset();
84
85
579
      result.alloc(uncompressedLen);
86
579
      zlibResult = uncompress(result.data(), &uncompressedLen, bytes, length);
87
579
    }
88
3.74k
    if (zlibResult == Z_BUF_ERROR) {
89
      // the uncompressed buffer needs to be larger
90
1.86k
      result.reset();
91
92
      // Sanity - never bigger than 16mb
93
1.86k
      if (uncompressedLen > 16 * 1024 * 1024)
94
0
        zlibResult = Z_DATA_ERROR;
95
1.86k
      else
96
1.86k
        uncompressedLen *= 2;
97
1.86k
    }
98
3.74k
  }
99
100
1.88k
  return zlibResult == Z_OK;
101
1.88k
}
102
103
0
static bool zlibToCompressed(const byte* bytes, uLongf length, DataBuf& result) {
104
0
  uLongf compressedLen = length;  // just a starting point
105
0
  int zlibResult = Z_BUF_ERROR;
106
107
0
  while (zlibResult == Z_BUF_ERROR) {
108
0
    result.alloc(compressedLen);
109
0
    zlibResult = compress(result.data(), &compressedLen, bytes, length);
110
0
    if (zlibResult == Z_BUF_ERROR) {
111
      // the compressedArray needs to be larger
112
0
      result.reset();
113
0
      compressedLen *= 2;
114
0
    } else {
115
0
      result.reset();
116
0
      result.alloc(compressedLen);
117
0
      zlibResult = compress(result.data(), &compressedLen, bytes, length);
118
0
    }
119
0
  }
120
121
0
  return zlibResult == Z_OK;
122
0
}
123
124
0
static bool tEXtToDataBuf(const byte* bytes, size_t length, DataBuf& result) {
125
0
  static std::array<int, 256> value;
126
0
  static bool bFirst = true;
127
0
  if (bFirst) {
128
0
    value.fill(0);
129
0
    for (int i = 0; i < 10; i++) {
130
0
      value['0' + i] = i + 1;
131
0
    }
132
0
    for (int i = 0; i < 6; i++) {
133
0
      value['a' + i] = i + 10 + 1;
134
0
      value['A' + i] = i + 10 + 1;
135
0
    }
136
0
    bFirst = false;
137
0
  }
138
139
  // calculate length and allocate result;
140
  // count: number of \n in the header
141
0
  size_t count = 0;
142
  // p points to the current position in the array bytes
143
0
  const byte* p = bytes;
144
145
  // header is '\nsomething\n number\n hex'
146
  // => increment p until it points to the byte after the last \n
147
  //    p must stay within bounds of the bytes array!
148
0
  while (count < 3 && 0 < length) {
149
    // length is later used for range checks of p => decrement it for each increment of p
150
0
    --length;
151
0
    if (*p++ == '\n') {
152
0
      count++;
153
0
    }
154
0
  }
155
0
  for (size_t i = 0; i < length; i++)
156
0
    if (value[p[i]])
157
0
      ++count;
158
0
  result.alloc((count + 1) / 2);
159
160
  // hex to binary
161
0
  count = 0;
162
0
  byte* r = result.data();
163
0
  int n = 0;  // nibble
164
0
  for (size_t i = 0; i < length; i++) {
165
0
    if (value[p[i]]) {
166
0
      int v = value[p[i]] - 1;
167
0
      if (++count % 2)
168
0
        n = v * 16;  // leading digit
169
0
      else
170
0
        *r++ = n + v;  // trailing
171
0
    }
172
0
  }
173
0
  return true;
174
0
}
175
176
0
static std::string::size_type findi(const std::string& str, const std::string& substr) {
177
0
  return str.find(substr);
178
0
}
179
180
0
void PngImage::printStructure(std::ostream& out, PrintStructureOption option, size_t depth) {
181
0
  if (io_->open() != 0) {
182
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
183
0
  }
184
0
  if (!isPngType(*io_, true)) {
185
0
    throw Error(ErrorCode::kerNotAnImage, "PNG");
186
0
  }
187
188
0
  std::string chType(4, 0);
189
190
0
  if (option == kpsBasic || option == kpsXMP || option == kpsIccProfile || option == kpsRecursive) {
191
0
    const auto xmpKey = upper("XML:com.adobe.xmp");
192
0
    const auto exifKey = upper("Raw profile type exif");
193
0
    const auto app1Key = upper("Raw profile type APP1");
194
0
    const auto iptcKey = upper("Raw profile type iptc");
195
0
    const auto softKey = upper("Software");
196
0
    const auto commKey = upper("Comment");
197
0
    const auto descKey = upper("Description");
198
199
0
    bool bPrint = option == kpsBasic || option == kpsRecursive;
200
0
    if (bPrint) {
201
0
      out << "STRUCTURE OF PNG FILE: " << io_->path() << '\n';
202
0
      out << " address | chunk |  length | data                           | checksum" << '\n';
203
0
    }
204
205
0
    const size_t imgSize = io_->size();
206
0
    DataBuf cheaderBuf(8);
207
208
0
    while (!io_->eof() && chType != "IEND") {
209
0
      const size_t address = io_->tell();
210
211
0
      size_t bufRead = io_->read(cheaderBuf.data(), cheaderBuf.size());
212
0
      if (io_->error())
213
0
        throw Error(ErrorCode::kerFailedToReadImageData);
214
0
      if (bufRead != cheaderBuf.size())
215
0
        throw Error(ErrorCode::kerInputDataReadFailed);
216
217
      // Decode chunk data length.
218
0
      const uint32_t dataOffset = cheaderBuf.read_uint32(0, Exiv2::bigEndian);
219
0
      for (int i = 4; i < 8; i++) {
220
0
        chType[i - 4] = cheaderBuf.read_uint8(i);
221
0
      }
222
223
      // test that we haven't hit EOF, or wanting to read excessive data
224
0
      const size_t restore = io_->tell();
225
0
      if (dataOffset > imgSize - restore) {
226
0
        throw Exiv2::Error(ErrorCode::kerFailedToReadImageData);
227
0
      }
228
229
0
      DataBuf buff(dataOffset);
230
0
      if (dataOffset > 0) {
231
0
        bufRead = io_->read(buff.data(), dataOffset);
232
0
        enforce(bufRead == dataOffset, ErrorCode::kerFailedToReadImageData);
233
0
      }
234
0
      io_->seek(restore, BasicIo::beg);
235
236
      // format output
237
0
      const int iMax = 30;
238
0
      const auto blen = std::min<uint32_t>(iMax, dataOffset);
239
0
      std::string dataString;
240
      // if blen == 0 => slice construction fails
241
0
      if (blen > 0) {
242
0
        std::stringstream ss;
243
0
        ss << Internal::binaryToString(makeSlice(buff, 0, blen));
244
0
        dataString = ss.str();
245
0
      }
246
0
      while (dataString.size() < iMax)
247
0
        dataString += ' ';
248
0
      dataString.resize(iMax);
249
250
0
      if (bPrint) {
251
0
        io_->seek(dataOffset, BasicIo::cur);  // jump to checksum
252
0
        byte checksum[4];
253
0
        bufRead = io_->read(checksum, 4);
254
0
        enforce(bufRead == 4, ErrorCode::kerFailedToReadImageData);
255
0
        io_->seek(restore, BasicIo::beg);  // restore file pointer
256
257
0
        out << stringFormat("{:8} | {:<5} |{:8} | {}", address, chType, dataOffset, dataString)
258
0
            << stringFormat(" | 0x{:02x}{:02x}{:02x}{:02x}\n", checksum[0], checksum[1], checksum[2], checksum[3]);
259
0
      }
260
261
      // chunk type
262
0
      bool tEXt = chType == "tEXt";
263
0
      bool zTXt = chType == "zTXt";
264
0
      bool iCCP = chType == "iCCP";
265
0
      bool iTXt = chType == "iTXt";
266
0
      bool eXIf = chType == "eXIf";
267
268
      // for XMP, ICC etc: read and format data
269
0
      const auto dataStringU = upper(dataString);
270
0
      bool bXMP = option == kpsXMP && findi(dataStringU, xmpKey) == 0;
271
0
      bool bExif = option == kpsRecursive && (findi(dataStringU, exifKey) == 0 || findi(dataStringU, app1Key) == 0);
272
0
      bool bIptc = option == kpsRecursive && findi(dataStringU, iptcKey) == 0;
273
0
      bool bSoft = option == kpsRecursive && findi(dataStringU, softKey) == 0;
274
0
      bool bComm = option == kpsRecursive && findi(dataStringU, commKey) == 0;
275
0
      bool bDesc = option == kpsRecursive && findi(dataStringU, descKey) == 0;
276
0
      bool bDump = bXMP || bExif || bIptc || bSoft || bComm || bDesc || iCCP || eXIf;
277
278
0
      if (bDump) {
279
0
        DataBuf dataBuf;
280
0
        enforce(dataOffset < std::numeric_limits<uint32_t>::max(), ErrorCode::kerFailedToReadImageData);
281
0
        DataBuf data(dataOffset + 1ul);
282
0
        data.write_uint8(dataOffset, 0);
283
0
        bufRead = io_->read(data.data(), dataOffset);
284
0
        enforce(bufRead == dataOffset, ErrorCode::kerFailedToReadImageData);
285
0
        io_->seek(restore, BasicIo::beg);
286
0
        size_t name_l = std::strlen(data.c_str()) + 1;  // leading string length
287
0
        enforce(name_l < dataOffset, ErrorCode::kerCorruptedMetadata);
288
289
0
        auto start = static_cast<uint32_t>(name_l);
290
0
        bool bLF = false;
291
292
        // decode the chunk
293
0
        bool bGood = false;
294
0
        if (tEXt) {
295
0
          bGood = tEXtToDataBuf(data.c_data(name_l), dataOffset - name_l, dataBuf);
296
0
        }
297
0
        if (zTXt || iCCP) {
298
0
          enforce(dataOffset - name_l - 1 <= std::numeric_limits<uLongf>::max(), ErrorCode::kerCorruptedMetadata);
299
0
          bGood = zlibToDataBuf(data.c_data(name_l + 1), static_cast<uLongf>(dataOffset - name_l - 1),
300
0
                                dataBuf);  // +1 = 'compressed' flag
301
0
        }
302
0
        if (iTXt) {
303
0
          bGood = (3 <= dataOffset) && (start < dataOffset - 3);  // good if not a nul chunk
304
0
        }
305
0
        if (eXIf) {
306
0
          bGood = true;  // eXIf requires no pre-processing
307
0
        }
308
309
        // format is content dependent
310
0
        if (bGood) {
311
0
          if (bXMP) {
312
0
            while (start < dataOffset && !data.read_uint8(start))
313
0
              start++;                  // skip leading nul bytes
314
0
            out << data.c_data(start);  // output the xmp
315
0
          }
316
317
0
          if (bExif || bIptc) {
318
0
            DataBuf parsedBuf = PngChunk::readRawProfile(dataBuf, tEXt);
319
#ifdef EXIV2_DEBUG_MESSAGES
320
            std::cerr << Exiv2::Internal::binaryToString(
321
                             makeSlice(parsedBuf.c_data(), std::min<size_t>(50, parsedBuf.size()), 0))
322
                      << '\n';
323
#endif
324
0
            if (!parsedBuf.empty()) {
325
0
              if (bExif) {
326
                // check for expected "Exif\0\0" APP1 identifier, punt otherwise
327
0
                size_t offset = 0;
328
0
                std::array<byte, 6> exifId{0x45, 0x78, 0x69, 0x66, 0x00, 0x00};  // "Exif\0\0"
329
0
                if (0 == parsedBuf.cmpBytes(0, exifId.data(), exifId.size())) {
330
0
                  offset = 6;
331
0
                }
332
                // create memio object with the data, then print the structure
333
0
                MemIo p(parsedBuf.c_data(offset), parsedBuf.size() - offset);
334
0
                printTiffStructure(p, out, option, depth + 1);
335
0
              }
336
0
              if (bIptc) {
337
0
                IptcData::printStructure(out, makeSlice(parsedBuf, 0, parsedBuf.size()), depth);
338
0
              }
339
0
            }
340
0
          }
341
342
0
          if (bSoft && !dataBuf.empty()) {
343
0
            DataBuf s(dataBuf.size() + 1);                         // allocate buffer with an extra byte
344
0
            std::copy(dataBuf.begin(), dataBuf.end(), s.begin());  // copy in the dataBuf
345
0
            s.write_uint8(dataBuf.size(), 0);                      // nul terminate it
346
0
            const auto str = s.c_str();                            // give it name
347
0
            out << Internal::indent(depth) << buff.c_str() << ": " << str;
348
0
            bLF = true;
349
0
          }
350
351
0
          if ((iCCP && option == kpsIccProfile) || bComm) {
352
0
            out.write(dataBuf.c_str(), dataBuf.size());
353
0
            bLF = bComm;
354
0
          }
355
356
0
          if (bDesc && iTXt) {
357
0
            DataBuf decoded = PngChunk::decodeTXTChunk(buff, PngChunk::iTXt_Chunk);
358
0
            out.write(decoded.c_str(), decoded.size());
359
0
            bLF = true;
360
0
          }
361
362
0
          if (eXIf && option == kpsRecursive) {
363
            // create memio object with the data, then print the structure
364
0
            MemIo p(data.c_data(), dataOffset);
365
0
            printTiffStructure(p, out, option, depth + 1);
366
0
          }
367
368
0
          if (bLF)
369
0
            out << '\n';
370
0
        }
371
0
      }
372
0
      io_->seek(dataOffset + 4, BasicIo::cur);  // jump past checksum
373
0
      if (io_->error())
374
0
        throw Error(ErrorCode::kerFailedToReadImageData);
375
0
    }
376
0
  }
377
0
}
378
379
96.9k
static void readChunk(DataBuf& buffer, BasicIo& io) {
380
#ifdef EXIV2_DEBUG_MESSAGES
381
  std::cout << "Exiv2::PngImage::readMetadata: Position: " << io.tell() << '\n';
382
#endif
383
96.9k
  const size_t bufRead = io.read(buffer.data(), buffer.size());
384
96.9k
  if (io.error()) {
385
0
    throw Error(ErrorCode::kerFailedToReadImageData);
386
0
  }
387
96.9k
  if (bufRead != buffer.size()) {
388
229
    throw Error(ErrorCode::kerInputDataReadFailed);
389
229
  }
390
96.9k
}
391
392
1.47k
void PngImage::readMetadata() {
393
#ifdef EXIV2_DEBUG_MESSAGES
394
  std::cerr << "Exiv2::PngImage::readMetadata: Reading PNG file " << io_->path() << '\n';
395
#endif
396
1.47k
  if (io_->open() != 0) {
397
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
398
0
  }
399
1.47k
  IoCloser closer(*io_);
400
1.47k
  if (!isPngType(*io_, true)) {
401
0
    throw Error(ErrorCode::kerNotAnImage, "PNG");
402
0
  }
403
1.47k
  clearMetadata();
404
405
1.47k
  const size_t imgSize = io_->size();
406
1.47k
  DataBuf cheaderBuf(8);  // Chunk header: 4 bytes (data size) + 4 bytes (chunk type).
407
408
93.8k
  while (!io_->eof()) {
409
93.0k
    readChunk(cheaderBuf, *io_);  // Read chunk header.
410
411
    // Decode chunk data length.
412
93.0k
    uint32_t chunkLength = cheaderBuf.read_uint32(0, Exiv2::bigEndian);
413
93.0k
    if (chunkLength > imgSize - io_->tell()) {
414
644
      throw Exiv2::Error(ErrorCode::kerFailedToReadImageData);
415
644
    }
416
417
92.3k
    std::string chunkType(cheaderBuf.c_str(4), 4);
418
#ifdef EXIV2_DEBUG_MESSAGES
419
    std::cout << "Exiv2::PngImage::readMetadata: chunk type: " << chunkType << " length: " << chunkLength << '\n';
420
#endif
421
422
    /// \todo analyse remaining chunks of the standard
423
    // Perform a chunk triage for item that we need.
424
92.3k
    if (chunkType == "IEND" || chunkType == "IHDR" || chunkType == "tEXt" || chunkType == "zTXt" ||
425
90.9k
        chunkType == "eXIf" || chunkType == "iTXt" || chunkType == "iCCP") {
426
4.01k
      DataBuf chunkData(chunkLength);
427
4.01k
      if (chunkLength > 0) {
428
3.94k
        readChunk(chunkData, *io_);  // Extract chunk data.
429
3.94k
      }
430
431
4.01k
      if (chunkType == "IEND") {
432
43
        return;  // Last chunk found: we stop parsing.
433
43
      }
434
3.96k
      if (chunkType == "IHDR" && chunkData.size() >= 8) {
435
261
        PngChunk::decodeIHDRChunk(chunkData, &pixelWidth_, &pixelHeight_);
436
3.70k
      } else if (chunkType == "tEXt") {
437
327
        PngChunk::decodeTXTChunk(this, chunkData, PngChunk::tEXt_Chunk);
438
3.37k
      } else if (chunkType == "zTXt") {
439
579
        PngChunk::decodeTXTChunk(this, chunkData, PngChunk::zTXt_Chunk);
440
2.80k
      } else if (chunkType == "iTXt") {
441
876
        PngChunk::decodeTXTChunk(this, chunkData, PngChunk::iTXt_Chunk);
442
1.92k
      } else if (chunkType == "eXIf") {
443
12
        ByteOrder bo = TiffParser::decode(exifData(), iptcData(), xmpData(), chunkData.c_data(), chunkData.size());
444
12
        setByteOrder(bo);
445
1.91k
      } else if (chunkType == "iCCP") {
446
        // The ICC profile name can vary from 1-79 characters.
447
1.90k
        uint32_t iccOffset = 0;
448
5.78k
        do {
449
5.78k
          enforce(iccOffset < 80 && iccOffset < chunkLength, Exiv2::ErrorCode::kerCorruptedMetadata);
450
5.78k
        } while (chunkData.read_uint8(iccOffset++) != 0x00);
451
452
1.90k
        profileName_ = std::string(chunkData.c_str(), iccOffset - 1);
453
1.90k
        ++iccOffset;  // +1 = 'compressed' flag
454
1.90k
        enforce(iccOffset <= chunkLength, Exiv2::ErrorCode::kerCorruptedMetadata);
455
456
1.90k
        zlibToDataBuf(chunkData.c_data(iccOffset), static_cast<uLongf>(chunkLength - iccOffset), iccProfile_);
457
#ifdef EXIV2_DEBUG_MESSAGES
458
        std::cout << "Exiv2::PngImage::readMetadata: profile name: " << profileName_ << '\n';
459
        std::cout << "Exiv2::PngImage::readMetadata: iccProfile.size_ (uncompressed) : " << iccProfile_.size() << '\n';
460
#endif
461
1.90k
      }
462
463
      // Set chunkLength to 0 in case we have read a supported chunk type. Otherwise, we need to seek the
464
      // file to the next chunk position.
465
3.96k
      chunkLength = 0;
466
3.96k
    }
467
468
    // Move to the next chunk: chunk data size + 4 CRC bytes.
469
#ifdef EXIV2_DEBUG_MESSAGES
470
    std::cout << "Exiv2::PngImage::readMetadata: Seek to offset: " << chunkLength + 4 << '\n';
471
#endif
472
92.3k
    io_->seek(chunkLength + 4, BasicIo::cur);
473
92.3k
    if (io_->error() || io_->eof()) {
474
0
      throw Error(ErrorCode::kerFailedToReadImageData);
475
0
    }
476
92.3k
  }
477
1.47k
}  // PngImage::readMetadata
478
479
0
void PngImage::writeMetadata() {
480
0
  if (io_->open() != 0) {
481
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
482
0
  }
483
0
  IoCloser closer(*io_);
484
0
  MemIo tempIo;
485
486
0
  doWriteMetadata(tempIo);  // may throw
487
0
  io_->close();
488
0
  io_->transfer(tempIo);  // may throw
489
490
0
}  // PngImage::writeMetadata
491
492
0
void PngImage::doWriteMetadata(BasicIo& outIo) {
493
0
  if (!io_->isopen())
494
0
    throw Error(ErrorCode::kerInputDataReadFailed);
495
0
  if (!outIo.isopen())
496
0
    throw Error(ErrorCode::kerImageWriteFailed);
497
498
#ifdef EXIV2_DEBUG_MESSAGES
499
  std::cout << "Exiv2::PngImage::doWriteMetadata: Writing PNG file " << io_->path() << "\n";
500
  std::cout << "Exiv2::PngImage::doWriteMetadata: tmp file created " << outIo.path() << "\n";
501
#endif
502
503
0
  if (!isPngType(*io_, true)) {
504
0
    throw Error(ErrorCode::kerNoImageInInputData);
505
0
  }
506
507
  // Write PNG Signature.
508
0
  if (outIo.write(pngSignature.data(), 8) != 8)
509
0
    throw Error(ErrorCode::kerImageWriteFailed);
510
511
0
  DataBuf cheaderBuf(8);  // Chunk header : 4 bytes (data size) + 4 bytes (chunk type).
512
513
0
  while (!io_->eof()) {
514
    // Read chunk header.
515
0
    size_t bufRead = io_->read(cheaderBuf.data(), 8);
516
0
    if (io_->error())
517
0
      throw Error(ErrorCode::kerFailedToReadImageData);
518
0
    if (bufRead != 8)
519
0
      throw Error(ErrorCode::kerInputDataReadFailed);
520
521
    // Decode chunk data length.
522
523
0
    uint32_t dataOffset = cheaderBuf.read_uint32(0, Exiv2::bigEndian);
524
0
    if (dataOffset > 0x7FFFFFFF)
525
0
      throw Exiv2::Error(ErrorCode::kerFailedToReadImageData);
526
527
    // Read whole chunk : Chunk header + Chunk data (not fixed size - can be null) + CRC (4 bytes).
528
529
0
    DataBuf chunkBuf(8 + dataOffset + 4);  // Chunk header (8 bytes) + Chunk data + CRC (4 bytes).
530
0
    std::copy(cheaderBuf.begin(), cheaderBuf.end(), chunkBuf.begin());  // Copy header.
531
0
    bufRead = io_->read(chunkBuf.data(8), dataOffset + 4);              // Extract chunk data + CRC
532
0
    if (io_->error())
533
0
      throw Error(ErrorCode::kerFailedToReadImageData);
534
0
    if (bufRead != dataOffset + 4)
535
0
      throw Error(ErrorCode::kerInputDataReadFailed);
536
537
0
    const std::string szChunk(cheaderBuf.begin() + 4, cheaderBuf.end());
538
539
0
    if (szChunk == "IEND") {
540
      // Last chunk found: we write it and done.
541
#ifdef EXIV2_DEBUG_MESSAGES
542
      std::cout << "Exiv2::PngImage::doWriteMetadata: Write IEND chunk (length: " << dataOffset << ")\n";
543
#endif
544
0
      if (outIo.write(chunkBuf.data(), chunkBuf.size()) != chunkBuf.size())
545
0
        throw Error(ErrorCode::kerImageWriteFailed);
546
0
      return;
547
0
    }
548
0
    if (szChunk == "eXIf" || szChunk == "iCCP") {
549
      // do nothing (strip): Exif metadata is written following IHDR
550
      // together with the ICC profile as fresh eXIf and iCCP chunks
551
#ifdef EXIV2_DEBUG_MESSAGES
552
      std::cout << "Exiv2::PngImage::doWriteMetadata: strip " << szChunk << " chunk (length: " << dataOffset << ")"
553
                << '\n';
554
#endif
555
0
    } else if (szChunk == "IHDR") {
556
#ifdef EXIV2_DEBUG_MESSAGES
557
      std::cout << "Exiv2::PngImage::doWriteMetadata: Write IHDR chunk (length: " << dataOffset << ")\n";
558
#endif
559
0
      if (outIo.write(chunkBuf.data(), chunkBuf.size()) != chunkBuf.size())
560
0
        throw Error(ErrorCode::kerImageWriteFailed);
561
562
      // Write all updated metadata here, just after IHDR.
563
0
      if (!comment_.empty()) {
564
        // Update Comment data to a new PNG chunk
565
0
        std::string chunk = PngChunk::makeMetadataChunk(comment_, mdComment);
566
0
        if (outIo.write(reinterpret_cast<const byte*>(chunk.data()), chunk.size()) != chunk.size()) {
567
0
          throw Error(ErrorCode::kerImageWriteFailed);
568
0
        }
569
0
      }
570
571
0
      if (!exifData_.empty()) {
572
        // Update Exif data to a new PNG chunk
573
0
        Blob blob;
574
0
        ExifParser::encode(blob, littleEndian, exifData_);
575
0
        if (!blob.empty()) {
576
0
          byte length[4];
577
0
          ul2Data(length, static_cast<uint32_t>(blob.size()), bigEndian);
578
579
          // calculate CRC
580
0
          uLong tmp = crc32(0L, Z_NULL, 0);
581
0
          tmp = crc32(tmp, typeExif, 4);
582
0
          tmp = crc32(tmp, blob.data(), static_cast<uint32_t>(blob.size()));
583
0
          byte crc[4];
584
0
          ul2Data(crc, tmp, bigEndian);
585
586
0
          if (outIo.write(length, 4) != 4 || outIo.write(typeExif, 4) != 4 ||
587
0
              outIo.write(blob.data(), blob.size()) != blob.size() || outIo.write(crc, 4) != 4) {
588
0
            throw Error(ErrorCode::kerImageWriteFailed);
589
0
          }
590
#ifdef EXIV2_DEBUG_MESSAGES
591
          std::cout << "Exiv2::PngImage::doWriteMetadata: build eXIf"
592
                    << " chunk (length: " << blob.size() << ")" << '\n';
593
#endif
594
0
        }
595
0
      }
596
597
0
      if (!iptcData_.empty()) {
598
        // Update IPTC data to a new PNG chunk
599
0
        DataBuf newPsData = Photoshop::setIptcIrb(nullptr, 0, iptcData_);
600
0
        if (!newPsData.empty()) {
601
0
          std::string rawIptc(newPsData.c_str(), newPsData.size());
602
0
          std::string chunk = PngChunk::makeMetadataChunk(rawIptc, mdIptc);
603
0
          if (outIo.write(reinterpret_cast<const byte*>(chunk.data()), chunk.size()) != chunk.size()) {
604
0
            throw Error(ErrorCode::kerImageWriteFailed);
605
0
          }
606
0
        }
607
0
      }
608
609
0
      if (iccProfileDefined()) {
610
0
        DataBuf compressed;
611
0
        enforce(iccProfile_.size() <= std::numeric_limits<uLongf>::max(), ErrorCode::kerCorruptedMetadata);
612
0
        if (zlibToCompressed(iccProfile_.c_data(), static_cast<uLongf>(iccProfile_.size()), compressed)) {
613
0
          const auto nameLength = static_cast<uint32_t>(profileName_.size());
614
0
          const uint32_t chunkLength = nameLength + 2 + static_cast<uint32_t>(compressed.size());
615
0
          byte length[4];
616
0
          ul2Data(length, chunkLength, bigEndian);
617
618
          // calculate CRC
619
0
          uLong tmp = crc32(0L, Z_NULL, 0);
620
0
          tmp = crc32(tmp, typeICCP, 4);
621
0
          tmp = crc32(tmp, reinterpret_cast<const Bytef*>(profileName_.data()), nameLength);
622
0
          tmp = crc32(tmp, nullComp, 2);
623
0
          tmp = crc32(tmp, compressed.c_data(), static_cast<uint32_t>(compressed.size()));
624
0
          byte crc[4];
625
0
          ul2Data(crc, tmp, bigEndian);
626
627
0
          if (outIo.write(length, 4) != 4 || outIo.write(typeICCP, 4) != 4 ||
628
0
              outIo.write(reinterpret_cast<const byte*>(profileName_.data()), nameLength) != nameLength ||
629
0
              outIo.write(nullComp, 2) != 2 ||
630
0
              outIo.write(compressed.c_data(), compressed.size()) != compressed.size() || outIo.write(crc, 4) != 4) {
631
0
            throw Error(ErrorCode::kerImageWriteFailed);
632
0
          }
633
#ifdef EXIV2_DEBUG_MESSAGES
634
          std::cout << "Exiv2::PngImage::doWriteMetadata: build iCCP"
635
                    << " chunk (length: " << chunkLength << ")" << '\n';
636
#endif
637
0
        }
638
0
      }
639
640
0
      if (!writeXmpFromPacket() && XmpParser::encode(xmpPacket_, xmpData_) > 1) {
641
0
#ifndef SUPPRESS_WARNINGS
642
0
        EXV_ERROR << "Failed to encode XMP metadata.\n";
643
0
#endif
644
0
      }
645
0
      if (!xmpPacket_.empty()) {
646
        // Update XMP data to a new PNG chunk
647
0
        std::string chunk = PngChunk::makeMetadataChunk(xmpPacket_, mdXmp);
648
0
        if (outIo.write(reinterpret_cast<const byte*>(chunk.data()), chunk.size()) != chunk.size()) {
649
0
          throw Error(ErrorCode::kerImageWriteFailed);
650
0
        }
651
0
      }
652
0
    } else if (szChunk == "tEXt" || szChunk == "zTXt" || szChunk == "iTXt") {
653
0
      DataBuf key = PngChunk::keyTXTChunk(chunkBuf, true);
654
0
      if (!key.empty() && (compare("Raw profile type exif", key) || compare("Raw profile type APP1", key) ||
655
0
                           compare("Raw profile type iptc", key) || compare("Raw profile type xmp", key) ||
656
0
                           compare("XML:com.adobe.xmp", key) || compare("Description", key))) {
657
#ifdef EXIV2_DEBUG_MESSAGES
658
        std::cout << "Exiv2::PngImage::doWriteMetadata: strip " << szChunk << " chunk (length: " << dataOffset << ")"
659
                  << '\n';
660
#endif
661
0
      } else {
662
#ifdef EXIV2_DEBUG_MESSAGES
663
        std::cout << "Exiv2::PngImage::doWriteMetadata: write " << szChunk << " chunk (length: " << dataOffset << ")"
664
                  << '\n';
665
#endif
666
0
        if (outIo.write(chunkBuf.c_data(), chunkBuf.size()) != chunkBuf.size())
667
0
          throw Error(ErrorCode::kerImageWriteFailed);
668
0
      }
669
0
    } else {
670
      // Write all others chunk as well.
671
#ifdef EXIV2_DEBUG_MESSAGES
672
      std::cout << "Exiv2::PngImage::doWriteMetadata:  copy " << szChunk << " chunk (length: " << dataOffset << ")"
673
                << '\n';
674
#endif
675
0
      if (outIo.write(chunkBuf.c_data(), chunkBuf.size()) != chunkBuf.size())
676
0
        throw Error(ErrorCode::kerImageWriteFailed);
677
0
    }
678
0
  }
679
680
0
}  // PngImage::doWriteMetadata
681
682
// *************************************************************************
683
// free functions
684
1.47k
Image::UniquePtr newPngInstance(BasicIo::UniquePtr io, bool create) {
685
1.47k
  auto image = std::make_unique<PngImage>(std::move(io), create);
686
1.47k
  if (!image->good()) {
687
0
    return nullptr;
688
0
  }
689
1.47k
  return image;
690
1.47k
}
691
692
16.2k
bool isPngType(BasicIo& iIo, bool advance) {
693
16.2k
  if (iIo.error() || iIo.eof()) {
694
301
    throw Error(ErrorCode::kerInputDataReadFailed);
695
301
  }
696
15.9k
  const int32_t len = 8;
697
15.9k
  std::array<byte, len> buf;
698
15.9k
  iIo.read(buf.data(), len);
699
15.9k
  if (iIo.error() || iIo.eof()) {
700
0
    return false;
701
0
  }
702
15.9k
  bool rc = buf == pngSignature;
703
15.9k
  if (!advance || !rc) {
704
14.4k
    iIo.seek(-len, BasicIo::cur);
705
14.4k
  }
706
707
15.9k
  return rc;
708
15.9k
}
709
}  // namespace Exiv2
710
#endif