Coverage Report

Created: 2026-08-31 06:56

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