Coverage Report

Created: 2026-08-31 06:56

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/exiv2/src/pngchunk_int.cpp
Line
Count
Source
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
3
// included header files
4
#include "pngchunk_int.hpp"
5
#include "config.h"
6
7
#ifdef EXV_HAVE_LIBZ
8
#include <zlib.h>  // To uncompress or compress text chunk
9
10
#include "enforce.hpp"
11
#include "error.hpp"
12
#include "exif.hpp"
13
#include "helper_functions.hpp"
14
#include "image.hpp"
15
#include "image_int.hpp"
16
#include "iptc.hpp"
17
#include "photoshop.hpp"
18
#include "safe_op.hpp"
19
#include "tiffimage.hpp"
20
21
// standard includes
22
#include <algorithm>
23
#include <array>
24
#include <cstdio>
25
#include <cstring>
26
#include <iostream>
27
#include <string>
28
29
/*
30
31
URLs to find information about PNG chunks :
32
33
tEXt and zTXt chunks : http://www.vias.org/pngguide/chapter11_04.html
34
iTXt chunk           : http://www.vias.org/pngguide/chapter11_05.html
35
PNG tags             : http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PNG.html#TextualData
36
37
*/
38
namespace {
39
constexpr size_t nullSeparators = 2;
40
}  // namespace
41
42
// *****************************************************************************
43
// class member definitions
44
namespace Exiv2::Internal {
45
274
void PngChunk::decodeIHDRChunk(const DataBuf& data, uint32_t* outWidth, uint32_t* outHeight) {
46
  // Extract image width and height from IHDR chunk.
47
274
  *outWidth = data.read_uint32(0, bigEndian);
48
274
  *outHeight = data.read_uint32(4, bigEndian);
49
274
}
50
51
18.9k
void PngChunk::decodeTXTChunk(Image* pImage, const DataBuf& data, TxtChunkType type, const DecodeParams& dp) {
52
18.9k
  DataBuf key = keyTXTChunk(data);
53
18.9k
  DataBuf arr = parseTXTChunk(data, key.size(), type);
54
55
#ifdef EXIV2_DEBUG_MESSAGES
56
  std::cout << "Exiv2::PngChunk::decodeTXTChunk: TXT chunk data: " << std::string(arr.c_str(), arr.size()) << '\n';
57
#endif
58
18.9k
  if (!key.empty())
59
17.4k
    parseChunkContent(pImage, key.c_data(), key.size(), arr, dp);
60
18.9k
}
61
62
11
DataBuf PngChunk::decodeTXTChunk(const DataBuf& data, TxtChunkType type) {
63
11
  DataBuf key = keyTXTChunk(data);
64
65
#ifdef EXIV2_DEBUG_MESSAGES
66
  std::cout << "Exiv2::PngChunk::decodeTXTChunk: TXT chunk key: " << std::string(key.c_str(), key.size()) << '\n';
67
#endif
68
11
  return parseTXTChunk(data, key.size(), type);
69
11
}
70
71
22.1k
DataBuf PngChunk::keyTXTChunk(const DataBuf& data, bool stripHeader) {
72
  // From a tEXt, zTXt, or iTXt chunk, we get the keyword which is null terminated.
73
22.1k
  const size_t offset = stripHeader ? 8ul : 0ul;
74
22.1k
  if (data.size() <= offset)
75
40
    throw Error(ErrorCode::kerFailedToReadImageData);
76
77
22.1k
  auto it = std::find(data.begin() + offset, data.end(), 0);
78
22.1k
  if (it == data.end())
79
60
    throw Error(ErrorCode::kerFailedToReadImageData);
80
81
22.0k
  return {data.c_data() + offset, std::distance(data.begin(), it) - offset};
82
22.1k
}
83
84
18.8k
DataBuf PngChunk::parseTXTChunk(const DataBuf& data, size_t keysize, TxtChunkType type) {
85
18.8k
  DataBuf arr;
86
87
18.8k
  if (type == zTXt_Chunk) {
88
654
    enforce(data.size() >= Safe::add(keysize, nullSeparators), ErrorCode::kerCorruptedMetadata);
89
90
    // Extract a deflate compressed Latin-1 text chunk
91
92
    // we get the compression method after the key
93
654
    if (*data.c_data(keysize + 1) != 0x00) {
94
      // then it isn't zlib compressed and we are sunk
95
#ifdef EXIV2_DEBUG_MESSAGES
96
      std::cerr << "Exiv2::PngChunk::parseTXTChunk: Non-standard zTXt compression method.\n";
97
#endif
98
50
      throw Error(ErrorCode::kerFailedToReadImageData);
99
50
    }
100
101
    // compressed string after the compression technique spec
102
604
    size_t compressedTextSize = data.size() - keysize - nullSeparators;
103
604
    if (compressedTextSize) {
104
179
      const byte* compressedText = data.c_data(keysize + nullSeparators);
105
179
      enforce(compressedTextSize < data.size(), ErrorCode::kerCorruptedMetadata);
106
107
179
      zlibUncompress(compressedText, static_cast<uint32_t>(compressedTextSize), arr);
108
179
    }
109
18.2k
  } else if (type == tEXt_Chunk) {
110
7.84k
    enforce(data.size() >= Safe::add(keysize, std::size_t{1}), ErrorCode::kerCorruptedMetadata);
111
    // Extract a non-compressed Latin-1 text chunk
112
113
    // the text comes after the key, but isn't null terminated
114
7.84k
    size_t textsize = data.size() - keysize - 1;
115
7.84k
    if (textsize) {
116
6.34k
      const byte* text = data.c_data(keysize + 1);
117
118
6.34k
      arr = DataBuf(text, textsize);
119
6.34k
    }
120
10.3k
  } else if (type == iTXt_Chunk) {
121
10.3k
    enforce(data.size() > Safe::add(keysize, std::size_t{3}), ErrorCode::kerCorruptedMetadata);
122
10.3k
    const size_t nullCount = std::count(data.c_data(keysize + 3), data.c_data(data.size() - 1), '\0');
123
10.3k
    enforce(nullCount >= nullSeparators, ErrorCode::kerCorruptedMetadata);
124
125
    // Extract a deflate compressed or uncompressed UTF-8 text chunk
126
127
    // we get the compression flag after the key
128
10.3k
    const byte compressionFlag = data.read_uint8(keysize + 1);
129
    // we get the compression method after the compression flag
130
10.3k
    const byte compressionMethod = data.read_uint8(keysize + 2);
131
132
10.3k
    enforce(compressionFlag == 0x00 || compressionFlag == 0x01, ErrorCode::kerCorruptedMetadata);
133
10.3k
    if (compressionFlag == 0x01)
134
286
      enforce(compressionMethod == 0x00, ErrorCode::kerFailedToReadImageData);
135
136
    // language description string after the compression technique spec
137
10.3k
    const size_t languageTextMaxSize = data.size() - keysize - 3;
138
10.3k
    std::string languageText = string_from_unterminated(data.c_str(keysize + 3), languageTextMaxSize);
139
10.3k
    const size_t languageTextSize = languageText.size();
140
141
10.3k
    enforce(data.size() >= Safe::add(Safe::add(keysize, std::size_t{4}), languageTextSize),
142
10.3k
            ErrorCode::kerCorruptedMetadata);
143
    // translated keyword string after the language description
144
10.3k
    std::string translatedKeyText = string_from_unterminated(data.c_str(keysize + 3 + languageTextSize + 1),
145
10.3k
                                                             data.size() - (keysize + 3 + languageTextSize + 1));
146
10.3k
    const size_t translatedKeyTextSize = translatedKeyText.size();
147
148
10.3k
    enforce(Safe::add(keysize + 3 + languageTextSize + 1, Safe::add(translatedKeyTextSize, size_t{1})) <= data.size(),
149
10.3k
            ErrorCode::kerCorruptedMetadata);
150
151
10.3k
    const auto textsize =
152
10.3k
        static_cast<long>(data.size() - (keysize + 3 + languageTextSize + 1 + translatedKeyTextSize + 1));
153
10.3k
    if (textsize) {
154
10.0k
      const byte* text = data.c_data(keysize + 3 + languageTextSize + 1 + translatedKeyTextSize + 1);
155
156
10.0k
      if (compressionFlag == 0x00) {
157
        // then it's an uncompressed iTXt chunk
158
#ifdef EXIV2_DEBUG_MESSAGES
159
        std::cout << "Exiv2::PngChunk::parseTXTChunk: We found an uncompressed iTXt field\n";
160
#endif
161
9.77k
        arr = DataBuf(text, textsize);
162
9.77k
      } else {
163
        // then it's a zlib compressed iTXt chunk
164
#ifdef EXIV2_DEBUG_MESSAGES
165
        std::cout << "Exiv2::PngChunk::parseTXTChunk: We found a zlib compressed iTXt field\n";
166
#endif
167
168
        // the compressed text comes after the translated keyword, but isn't null terminated
169
225
        zlibUncompress(text, textsize, arr);
170
225
      }
171
10.0k
    }
172
10.3k
  } else {
173
#ifdef DEBUG
174
    std::cerr << "Exiv2::PngChunk::parseTXTChunk: We found a field, not expected though\n";
175
#endif
176
0
    throw Error(ErrorCode::kerFailedToReadImageData);
177
0
  }
178
179
18.8k
  return arr;
180
18.8k
}
181
182
void PngChunk::parseChunkContent(Image* pImage, const byte* key, size_t keySize, const DataBuf& arr,
183
17.4k
                                 const DecodeParams& dp) {
184
  // We look if an ImageMagick EXIF raw profile exist.
185
186
17.4k
  if (keySize >= 21 &&
187
8.67k
      (memcmp("Raw profile type exif", key, 21) == 0 || memcmp("Raw profile type APP1", key, 21) == 0) &&
188
2.91k
      pImage->exifData().empty()) {
189
2.84k
    DataBuf exifData = readRawProfile(arr, false);
190
2.84k
    size_t length = exifData.size();
191
192
2.84k
    if (length >= 4) {  // length should have at least the size of TIFF header
193
      // Find the position of TIFF header in bytes array.
194
      // Forgives the absence of the expected Exif\0 APP1 prefix.
195
536
      const std::array<byte, 4> tiffHeaderLE{0x49, 0x49, 0x2A, 0x00};  // "II*\0"
196
536
      const std::array<byte, 4> tiffHeaderBE{0x4D, 0x4D, 0x00, 0x2A};  // "MM\0*"
197
536
      size_t pos = std::numeric_limits<size_t>::max();
198
199
      /// \todo Find substring inside an string
200
1.34k
      for (size_t i = 0; i < length - tiffHeaderLE.size(); i++) {
201
808
        if (0 == exifData.cmpBytes(i, tiffHeaderLE.data(), tiffHeaderLE.size()) ||
202
808
            0 == exifData.cmpBytes(i, tiffHeaderBE.data(), tiffHeaderBE.size())) {
203
0
          pos = i;
204
0
          break;
205
0
        }
206
808
      }
207
208
      // If found it, store only these data at from this place.
209
210
536
      if (pos != std::numeric_limits<size_t>::max()) {
211
#ifdef EXIV2_DEBUG_MESSAGES
212
        std::cout << "Exiv2::PngChunk::parseChunkContent: TIFF header found at position " << pos << "\n";
213
#endif
214
0
        ByteOrder bo = TiffParser::decode(pImage->exifData(), pImage->iptcData(), pImage->xmpData(),
215
0
                                          exifData.c_data(pos), length - pos, dp);
216
0
        pImage->setByteOrder(bo);
217
536
      } else {
218
536
#ifndef SUPPRESS_WARNINGS
219
536
        EXV_WARNING << "Failed to decode Exif metadata.\n";
220
536
#endif
221
536
        pImage->exifData().clear();
222
536
      }
223
536
    }
224
2.84k
  }
225
226
  // We look if an ImageMagick IPTC raw profile exist.
227
228
17.4k
  if (keySize >= 21 && memcmp("Raw profile type iptc", key, 21) == 0 && pImage->iptcData().empty()) {
229
1.56k
    DataBuf psData = readRawProfile(arr, false);
230
1.56k
    if (!psData.empty()) {
231
311
      Blob iptcBlob;
232
311
      const byte* record = nullptr;
233
311
      uint32_t sizeIptc = 0;
234
311
      uint32_t sizeHdr = 0;
235
236
311
      const byte* pEnd = psData.c_data(psData.size() - 1);
237
311
      const byte* pCur = psData.c_data();
238
311
      while (pCur < pEnd && 0 == Photoshop::locateIptcIrb(pCur, pEnd - pCur, &record, sizeHdr, sizeIptc)) {
239
0
        if (sizeIptc) {
240
#ifdef EXIV2_DEBUG_MESSAGES
241
          std::cerr << "Found IPTC IRB, size = " << sizeIptc << "\n";
242
#endif
243
0
          append(iptcBlob, record + sizeHdr, sizeIptc);
244
0
        }
245
0
        pCur = record + sizeHdr + sizeIptc;
246
0
        pCur += (sizeIptc & 1);
247
0
      }
248
311
      if (!iptcBlob.empty() && IptcParser::decode(pImage->iptcData(), iptcBlob.data(), iptcBlob.size())) {
249
0
#ifndef SUPPRESS_WARNINGS
250
0
        EXV_WARNING << "Failed to decode IPTC metadata.\n";
251
0
#endif
252
0
        pImage->clearIptcData();
253
0
      }
254
      // If there is no IRB, try to decode the complete chunk data
255
311
      if (iptcBlob.empty() && IptcParser::decode(pImage->iptcData(), psData.c_data(), psData.size())) {
256
135
#ifndef SUPPRESS_WARNINGS
257
135
        EXV_WARNING << "Failed to decode IPTC metadata.\n";
258
135
#endif
259
135
        pImage->clearIptcData();
260
135
      }
261
311
    }  // if (psData.size() > 0)
262
1.56k
  }
263
264
  // We look if an ImageMagick XMP raw profile exist.
265
266
17.4k
  if (keySize >= 20 && memcmp("Raw profile type xmp", key, 20) == 0 && pImage->xmpData().empty()) {
267
2.83k
    DataBuf xmpBuf = readRawProfile(arr, false);
268
2.83k
    size_t length = xmpBuf.size();
269
270
2.83k
    if (length > 0) {
271
2.38k
      std::string& xmpPacket = pImage->xmpPacket();
272
2.38k
      xmpPacket.assign(xmpBuf.c_str(), length);
273
2.38k
      if (auto idx = xmpPacket.find_first_of('<'); idx != std::string::npos && idx > 0) {
274
62
#ifndef SUPPRESS_WARNINGS
275
62
        EXV_WARNING << "Removing " << idx << " characters from the beginning of the XMP packet\n";
276
62
#endif
277
62
        xmpPacket = xmpPacket.substr(idx);
278
62
      }
279
2.38k
      if (XmpParser::decode(pImage->xmpData(), xmpPacket, dp)) {
280
2.38k
#ifndef SUPPRESS_WARNINGS
281
2.38k
        EXV_WARNING << "Failed to decode XMP metadata.\n";
282
2.38k
#endif
283
2.38k
      }
284
2.38k
    }
285
2.83k
  }
286
287
  // We look if an Adobe XMP string exist.
288
289
17.4k
  if (keySize >= 17 && memcmp("XML:com.adobe.xmp", key, 17) == 0 && pImage->xmpData().empty() && !arr.empty()) {
290
2.06k
    std::string& xmpPacket = pImage->xmpPacket();
291
2.06k
    xmpPacket.assign(arr.c_str(), arr.size());
292
2.06k
    if (auto idx = xmpPacket.find_first_of('<'); idx != std::string::npos && idx > 0) {
293
824
#ifndef SUPPRESS_WARNINGS
294
824
      EXV_WARNING << "Removing " << idx << " characters "
295
0
                  << "from the beginning of the XMP packet\n";
296
824
#endif
297
824
      xmpPacket = xmpPacket.substr(idx);
298
824
    }
299
2.06k
    if (XmpParser::decode(pImage->xmpData(), xmpPacket, dp)) {
300
872
#ifndef SUPPRESS_WARNINGS
301
872
      EXV_WARNING << "Failed to decode XMP metadata.\n";
302
872
#endif
303
872
    }
304
2.06k
  }
305
306
  // We look if a comments string exist. Note than we use only 'Description' keyword which
307
  // is dedicated to store long comments. 'Comment' keyword is ignored.
308
309
17.4k
  if (keySize >= 11 && memcmp("Description", key, 11) == 0 && pImage->comment().empty()) {
310
253
    pImage->setComment(std::string(arr.c_str(), arr.size()));
311
253
  }
312
313
17.4k
}  // PngChunk::parseChunkContent
314
315
1.17k
std::string PngChunk::makeMetadataChunk(std::string_view metadata, MetadataId type) {
316
1.17k
  std::string rawProfile;
317
318
1.17k
  switch (type) {
319
438
    case mdComment:
320
438
      return makeUtf8TxtChunk("Description", metadata, true);
321
102
    case mdIptc:
322
102
      rawProfile = writeRawProfile(metadata, "iptc");
323
102
      return makeAsciiTxtChunk("Raw profile type iptc", rawProfile, true);
324
631
    case mdXmp:
325
631
      return makeUtf8TxtChunk("XML:com.adobe.xmp", metadata, false);
326
0
    case mdExif:
327
0
    case mdIccProfile:
328
0
    case mdNone:
329
0
      return {};
330
1.17k
  }
331
332
0
  return {};
333
334
1.17k
}  // PngChunk::makeMetadataChunk
335
336
404
void PngChunk::zlibUncompress(const byte* compressedText, unsigned int compressedTextSize, DataBuf& arr) {
337
404
  uLongf uncompressedLen = compressedTextSize * 2;  // just a starting point
338
404
  int zlibResult = Z_BUF_ERROR;
339
404
  int dos = 0;
340
341
829
  while (zlibResult == Z_BUF_ERROR) {
342
670
    arr.alloc(uncompressedLen);
343
670
    zlibResult = uncompress(arr.data(), &uncompressedLen, compressedText, compressedTextSize);
344
670
    if (zlibResult == Z_OK) {
345
159
      arr.resize(uncompressedLen);
346
511
    } else if (zlibResult == Z_BUF_ERROR) {
347
      // the uncompressedArray needs to be larger
348
277
      uncompressedLen *= 2;
349
      // DoS protection. can't be bigger than 64k
350
277
      if (uncompressedLen > 131072) {
351
31
        if (++dos > 1)
352
11
          break;
353
20
        uncompressedLen = 131072;
354
20
      }
355
277
    } else {
356
      // something bad happened
357
234
      throw Error(ErrorCode::kerFailedToReadImageData);
358
234
    }
359
670
  }
360
361
170
  if (zlibResult != Z_OK) {
362
11
    throw Error(ErrorCode::kerFailedToReadImageData);
363
11
  }
364
170
}  // PngChunk::zlibUncompress
365
366
540
std::string PngChunk::zlibCompress(std::string_view text) {
367
540
  auto compressedLen = static_cast<uLongf>(text.size() * 2);  // just a starting point
368
540
  int zlibResult = Z_BUF_ERROR;
369
370
540
  DataBuf arr;
371
1.49k
  while (zlibResult == Z_BUF_ERROR) {
372
950
    arr.resize(compressedLen);
373
950
    zlibResult = compress2(arr.data(), &compressedLen, reinterpret_cast<const Bytef*>(text.data()),
374
950
                           static_cast<uLong>(text.size()), Z_BEST_COMPRESSION);
375
376
950
    switch (zlibResult) {
377
540
      case Z_OK:
378
540
        arr.resize(compressedLen);
379
540
        break;
380
410
      case Z_BUF_ERROR:
381
        // The compressed array needs to be larger
382
#ifdef EXIV2_DEBUG_MESSAGES
383
        std::cout << "Exiv2::PngChunk::parsePngChunk: doubling size for compression.\n";
384
#endif
385
410
        compressedLen *= 2;
386
        // DoS protection. Cap max compressed size
387
410
        if (compressedLen > 131072)
388
0
          throw Error(ErrorCode::kerFailedToReadImageData);
389
410
        break;
390
410
      default:
391
        // Something bad happened
392
0
        throw Error(ErrorCode::kerFailedToReadImageData);
393
950
    }
394
950
  }
395
396
540
  return {arr.c_str(), arr.size()};
397
398
540
}  // PngChunk::zlibCompress
399
400
102
std::string PngChunk::makeAsciiTxtChunk(std::string_view keyword, std::string_view text, bool compress) {
401
  // Chunk structure: length (4 bytes) + chunk type + chunk data + CRC (4 bytes)
402
  // Length is the size of the chunk data
403
  // CRC is calculated on chunk type + chunk data
404
405
  // Compressed text chunk using zlib.
406
  // Chunk data format : keyword + 0x00 + compression method (0x00) + compressed text
407
408
  // Not Compressed text chunk.
409
  // Chunk data format : keyword + 0x00 + text
410
411
  // Build chunk data, determine chunk type
412
102
  auto chunkData = std::string(keyword) + '\0';
413
102
  std::string chunkType;
414
102
  if (compress) {
415
102
    chunkData += '\0' + zlibCompress(text);
416
102
    chunkType = "zTXt";
417
102
  } else {
418
0
    chunkData += text;
419
0
    chunkType = "tEXt";
420
0
  }
421
  // Determine length of the chunk data
422
102
  byte length[4];
423
102
  ul2Data(length, static_cast<uint32_t>(chunkData.size()), bigEndian);
424
  // Calculate CRC on chunk type and chunk data
425
102
  std::string crcData = chunkType + chunkData;
426
102
  uLong tmp = crc32(0L, Z_NULL, 0);
427
102
  tmp = crc32(tmp, reinterpret_cast<const Bytef*>(crcData.data()), static_cast<uInt>(crcData.size()));
428
102
  byte crc[4];
429
102
  ul2Data(crc, tmp, bigEndian);
430
  // Assemble the chunk
431
102
  return std::string(reinterpret_cast<const char*>(length), 4) + chunkType + chunkData +
432
102
         std::string(reinterpret_cast<const char*>(crc), 4);
433
434
102
}  // PngChunk::makeAsciiTxtChunk
435
436
1.06k
std::string PngChunk::makeUtf8TxtChunk(std::string_view keyword, std::string_view text, bool compress) {
437
  // Chunk structure: length (4 bytes) + chunk type + chunk data + CRC (4 bytes)
438
  // Length is the size of the chunk data
439
  // CRC is calculated on chunk type + chunk data
440
441
  // Chunk data format : keyword + 0x00 + compression flag (0x00: uncompressed - 0x01: compressed)
442
  //                     + compression method (0x00: zlib format) + language tag (null) + 0x00
443
  //                     + translated keyword (null) + 0x00 + text (compressed or not)
444
445
  // Build chunk data, determine chunk type
446
1.06k
  auto chunkData = std::string(keyword);
447
1.06k
  if (compress) {
448
438
    static const char flags[] = {0x00, 0x01, 0x00, 0x00, 0x00};
449
438
    chunkData += std::string(flags, 5) + zlibCompress(text);
450
631
  } else {
451
631
    static const char flags[] = {0x00, 0x00, 0x00, 0x00, 0x00};
452
631
    chunkData += std::string(flags, 5) + text.data();
453
631
  }
454
  // Determine length of the chunk data
455
1.06k
  byte length[4];
456
1.06k
  ul2Data(length, static_cast<uint32_t>(chunkData.size()), bigEndian);
457
  // Calculate CRC on chunk type and chunk data
458
1.06k
  std::string chunkType = "iTXt";
459
1.06k
  std::string crcData = chunkType + chunkData;
460
1.06k
  uLong tmp = crc32(0L, Z_NULL, 0);
461
1.06k
  tmp = crc32(tmp, reinterpret_cast<const Bytef*>(crcData.data()), static_cast<uInt>(crcData.size()));
462
1.06k
  byte crc[4];
463
1.06k
  ul2Data(crc, tmp, bigEndian);
464
  // Assemble the chunk
465
1.06k
  return std::string(reinterpret_cast<const char*>(length), 4) + chunkType + chunkData +
466
1.06k
         std::string(reinterpret_cast<const char*>(crc), 4);
467
468
1.06k
}  // PngChunk::makeUtf8TxtChunk
469
470
7.27k
DataBuf PngChunk::readRawProfile(const DataBuf& text, bool iTXt) {
471
7.27k
  DataBuf info;
472
7.27k
  if (text.size() <= 1) {
473
256
    return info;
474
256
  }
475
476
7.01k
  const unsigned char unhex[103] = {
477
7.01k
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0,  0,  0,  0,  0,  0, 0,
478
7.01k
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0,  0,  0,  0,  0,  0,  0, 0,
479
7.01k
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15,
480
7.01k
  };
481
482
7.01k
  if (iTXt) {
483
13
    info.alloc(text.size());
484
13
    std::copy(text.begin(), text.end(), info.begin());
485
13
    return info;
486
13
  }
487
488
7.00k
  const char* sp = text.c_str(1);                 // current byte (space pointer)
489
7.00k
  const char* eot = text.c_str(text.size() - 1);  // end of text
490
491
7.00k
  if (sp >= eot) {
492
252
    return info;
493
252
  }
494
495
  // Look for newline
496
88.1k
  while (*sp != '\n') {
497
81.8k
    sp++;
498
81.8k
    if (sp == eot) {
499
435
      return info;
500
435
    }
501
81.8k
  }
502
6.31k
  sp++;  // step over '\n'
503
6.31k
  if (sp == eot) {
504
101
    return info;
505
101
  }
506
507
  // Look for length
508
91.9k
  while (*sp == '\0' || *sp == ' ' || *sp == '\n') {
509
85.8k
    sp++;
510
85.8k
    if (sp == eot) {
511
58
      return info;
512
58
    }
513
85.8k
  }
514
515
  // Parse the length.
516
6.15k
  size_t length = 0;
517
12.0k
  while ('0' <= *sp && *sp <= '9') {
518
    // Compute the new length using unsigned long, so that we can check for overflow.
519
5.98k
    const size_t newlength = (10 * length) + (*sp - '0');
520
5.98k
    length = newlength;
521
5.98k
    sp++;
522
5.98k
    if (sp == eot) {
523
125
      return info;
524
125
    }
525
5.98k
  }
526
6.03k
  sp++;  // step over '\n'
527
6.03k
  if (sp == eot) {
528
962
    return info;
529
962
  }
530
531
5.06k
  enforce(length <= static_cast<size_t>(eot - sp) / 2, Exiv2::ErrorCode::kerCorruptedMetadata);
532
533
  // Allocate space
534
5.06k
  if (length == 0) {
535
#ifdef EXIV2_DEBUG_MESSAGES
536
    std::cerr << "Exiv2::PngChunk::readRawProfile: Unable To Copy Raw Profile: invalid profile length\n";
537
#endif
538
1.12k
  }
539
5.06k
  info.alloc(length);
540
5.06k
  if (info.size() != length) {
541
#ifdef EXIV2_DEBUG_MESSAGES
542
    std::cerr << "Exiv2::PngChunk::readRawProfile: Unable To Copy Raw Profile: cannot allocate memory\n";
543
#endif
544
0
    return info;
545
0
  }
546
547
5.06k
  if (info.empty())  // Early return
548
1.12k
    return info;
549
550
  // Copy profile, skipping white space and column 1 "=" signs
551
3.94k
  unsigned char* dp = info.data();  // decode pointer
552
3.94k
  size_t nibbles = length * 2;
553
554
47.4k
  for (size_t i = 0; i < nibbles; i++) {
555
44.0k
    enforce(sp < eot, Exiv2::ErrorCode::kerCorruptedMetadata);
556
72.2k
    while (*sp < '0' || (*sp > '9' && *sp < 'a') || *sp > 'f') {
557
28.7k
      if (*sp == '\0') {
558
#ifdef EXIV2_DEBUG_MESSAGES
559
        std::cerr << "Exiv2::PngChunk::readRawProfile: Unable To Copy Raw Profile: ran out of data\n";
560
#endif
561
489
        return {};
562
489
      }
563
564
28.2k
      sp++;
565
28.2k
      enforce(sp < eot, Exiv2::ErrorCode::kerCorruptedMetadata);
566
28.2k
    }
567
568
43.5k
    if (i % 2 == 0)
569
21.7k
      *dp = static_cast<unsigned char>(16 * unhex[static_cast<size_t>(*sp++)]);
570
21.7k
    else
571
21.7k
      (*dp++) += unhex[static_cast<size_t>(*sp++)];
572
43.5k
  }
573
574
3.45k
  return info;
575
576
3.94k
}  // PngChunk::readRawProfile
577
578
102
std::string PngChunk::writeRawProfile(std::string_view profileData, const char* profileType) {
579
102
  static const byte hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
580
581
102
  auto ss = stringFormat("\n{}\n{:08}", profileType, profileData.size());
582
102
  auto sp = reinterpret_cast<const byte*>(profileData.data());
583
491k
  for (std::string::size_type i = 0; i < profileData.size(); ++i) {
584
490k
    if (i % 36 == 0)
585
13.6k
      ss += '\n';
586
490k
    ss += hex[*sp >> 4 & 0x0fU];
587
490k
    ss += hex[*sp++ & 0x0fU];
588
490k
  }
589
102
  ss += '\n';
590
102
  return ss;
591
592
102
}  // PngChunk::writeRawProfile
593
594
}  // namespace Exiv2::Internal
595
#endif  // ifdef EXV_HAVE_LIBZ