Coverage Report

Created: 2026-09-13 06:10

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
70
void PngChunk::decodeIHDRChunk(const DataBuf& data, uint32_t* outWidth, uint32_t* outHeight) {
46
  // Extract image width and height from IHDR chunk.
47
70
  *outWidth = data.read_uint32(0, bigEndian);
48
70
  *outHeight = data.read_uint32(4, bigEndian);
49
70
}
50
51
4.86k
void PngChunk::decodeTXTChunk(Image* pImage, const DataBuf& data, TxtChunkType type, const DecodeParams& dp) {
52
4.86k
  DataBuf key = keyTXTChunk(data);
53
4.86k
  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
4.86k
  if (!key.empty())
59
4.48k
    parseChunkContent(pImage, key.c_data(), key.size(), arr, dp);
60
4.86k
}
61
62
0
DataBuf PngChunk::decodeTXTChunk(const DataBuf& data, TxtChunkType type) {
63
0
  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
0
  return parseTXTChunk(data, key.size(), type);
69
0
}
70
71
4.86k
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
4.86k
  const size_t offset = stripHeader ? 8ul : 0ul;
74
4.86k
  if (data.size() <= offset)
75
11
    throw Error(ErrorCode::kerFailedToReadImageData);
76
77
4.84k
  auto it = std::find(data.begin() + offset, data.end(), 0);
78
4.84k
  if (it == data.end())
79
13
    throw Error(ErrorCode::kerFailedToReadImageData);
80
81
4.83k
  return {data.c_data() + offset, std::distance(data.begin(), it) - offset};
82
4.84k
}
83
84
4.83k
DataBuf PngChunk::parseTXTChunk(const DataBuf& data, size_t keysize, TxtChunkType type) {
85
4.83k
  DataBuf arr;
86
87
4.83k
  if (type == zTXt_Chunk) {
88
166
    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
166
    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
16
      throw Error(ErrorCode::kerFailedToReadImageData);
99
16
    }
100
101
    // compressed string after the compression technique spec
102
150
    size_t compressedTextSize = data.size() - keysize - nullSeparators;
103
150
    if (compressedTextSize) {
104
66
      const byte* compressedText = data.c_data(keysize + nullSeparators);
105
66
      enforce(compressedTextSize < data.size(), ErrorCode::kerCorruptedMetadata);
106
107
66
      zlibUncompress(compressedText, static_cast<uint32_t>(compressedTextSize), arr);
108
66
    }
109
4.67k
  } else if (type == tEXt_Chunk) {
110
1.21k
    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
1.21k
    size_t textsize = data.size() - keysize - 1;
115
1.21k
    if (textsize) {
116
1.03k
      const byte* text = data.c_data(keysize + 1);
117
118
1.03k
      arr = DataBuf(text, textsize);
119
1.03k
    }
120
3.45k
  } else if (type == iTXt_Chunk) {
121
3.45k
    enforce(data.size() > Safe::add(keysize, std::size_t{3}), ErrorCode::kerCorruptedMetadata);
122
3.45k
    const size_t nullCount = std::count(data.c_data(keysize + 3), data.c_data(data.size() - 1), '\0');
123
3.45k
    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
3.45k
    const byte compressionFlag = data.read_uint8(keysize + 1);
129
    // we get the compression method after the compression flag
130
3.45k
    const byte compressionMethod = data.read_uint8(keysize + 2);
131
132
3.45k
    enforce(compressionFlag == 0x00 || compressionFlag == 0x01, ErrorCode::kerCorruptedMetadata);
133
3.45k
    if (compressionFlag == 0x01)
134
68
      enforce(compressionMethod == 0x00, ErrorCode::kerFailedToReadImageData);
135
136
    // language description string after the compression technique spec
137
3.45k
    const size_t languageTextMaxSize = data.size() - keysize - 3;
138
3.45k
    std::string languageText = string_from_unterminated(data.c_str(keysize + 3), languageTextMaxSize);
139
3.45k
    const size_t languageTextSize = languageText.size();
140
141
3.45k
    enforce(data.size() >= Safe::add(Safe::add(keysize, std::size_t{4}), languageTextSize),
142
3.45k
            ErrorCode::kerCorruptedMetadata);
143
    // translated keyword string after the language description
144
3.45k
    std::string translatedKeyText = string_from_unterminated(data.c_str(keysize + 3 + languageTextSize + 1),
145
3.45k
                                                             data.size() - (keysize + 3 + languageTextSize + 1));
146
3.45k
    const size_t translatedKeyTextSize = translatedKeyText.size();
147
148
3.45k
    enforce(Safe::add(keysize + 3 + languageTextSize + 1, Safe::add(translatedKeyTextSize, size_t{1})) <= data.size(),
149
3.45k
            ErrorCode::kerCorruptedMetadata);
150
151
3.45k
    const auto textsize =
152
3.45k
        static_cast<long>(data.size() - (keysize + 3 + languageTextSize + 1 + translatedKeyTextSize + 1));
153
3.45k
    if (textsize) {
154
3.37k
      const byte* text = data.c_data(keysize + 3 + languageTextSize + 1 + translatedKeyTextSize + 1);
155
156
3.37k
      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
3.31k
        arr = DataBuf(text, textsize);
162
3.31k
      } 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
56
        zlibUncompress(text, textsize, arr);
170
56
      }
171
3.37k
    }
172
3.45k
  } 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
4.82k
  return arr;
180
4.83k
}
181
182
void PngChunk::parseChunkContent(Image* pImage, const byte* key, size_t keySize, const DataBuf& arr,
183
4.48k
                                 const DecodeParams& dp) {
184
  // We look if an ImageMagick EXIF raw profile exist.
185
186
4.48k
  if (keySize >= 21 &&
187
2.61k
      (memcmp("Raw profile type exif", key, 21) == 0 || memcmp("Raw profile type APP1", key, 21) == 0) &&
188
1.29k
      pImage->exifData().empty()) {
189
1.22k
    DataBuf exifData = readRawProfile(arr, false);
190
1.22k
    size_t length = exifData.size();
191
192
1.22k
    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
49
      const std::array<byte, 4> tiffHeaderLE{0x49, 0x49, 0x2A, 0x00};  // "II*\0"
196
49
      const std::array<byte, 4> tiffHeaderBE{0x4D, 0x4D, 0x00, 0x2A};  // "MM\0*"
197
49
      size_t pos = std::numeric_limits<size_t>::max();
198
199
      /// \todo Find substring inside an string
200
135
      for (size_t i = 0; i < length - tiffHeaderLE.size(); i++) {
201
86
        if (0 == exifData.cmpBytes(i, tiffHeaderLE.data(), tiffHeaderLE.size()) ||
202
86
            0 == exifData.cmpBytes(i, tiffHeaderBE.data(), tiffHeaderBE.size())) {
203
0
          pos = i;
204
0
          break;
205
0
        }
206
86
      }
207
208
      // If found it, store only these data at from this place.
209
210
49
      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
49
      } else {
218
49
#ifndef SUPPRESS_WARNINGS
219
49
        EXV_WARNING << "Failed to decode Exif metadata.\n";
220
49
#endif
221
49
        pImage->exifData().clear();
222
49
      }
223
49
    }
224
1.22k
  }
225
226
  // We look if an ImageMagick IPTC raw profile exist.
227
228
4.48k
  if (keySize >= 21 && memcmp("Raw profile type iptc", key, 21) == 0 && pImage->iptcData().empty()) {
229
216
    DataBuf psData = readRawProfile(arr, false);
230
216
    if (!psData.empty()) {
231
147
      Blob iptcBlob;
232
147
      const byte* record = nullptr;
233
147
      uint32_t sizeIptc = 0;
234
147
      uint32_t sizeHdr = 0;
235
236
147
      const byte* pEnd = psData.c_data(psData.size() - 1);
237
147
      const byte* pCur = psData.c_data();
238
147
      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
147
      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
147
      if (iptcBlob.empty() && IptcParser::decode(pImage->iptcData(), psData.c_data(), psData.size())) {
256
18
#ifndef SUPPRESS_WARNINGS
257
18
        EXV_WARNING << "Failed to decode IPTC metadata.\n";
258
18
#endif
259
18
        pImage->clearIptcData();
260
18
      }
261
147
    }  // if (psData.size() > 0)
262
216
  }
263
264
  // We look if an ImageMagick XMP raw profile exist.
265
266
4.48k
  if (keySize >= 20 && memcmp("Raw profile type xmp", key, 20) == 0 && pImage->xmpData().empty()) {
267
1.24k
    DataBuf xmpBuf = readRawProfile(arr, false);
268
1.24k
    size_t length = xmpBuf.size();
269
270
1.24k
    if (length > 0) {
271
960
      std::string& xmpPacket = pImage->xmpPacket();
272
960
      xmpPacket.assign(xmpBuf.c_str(), length);
273
960
      if (auto idx = xmpPacket.find_first_of('<'); idx != std::string::npos && idx > 0) {
274
45
#ifndef SUPPRESS_WARNINGS
275
45
        EXV_WARNING << "Removing " << idx << " characters from the beginning of the XMP packet\n";
276
45
#endif
277
45
        xmpPacket = xmpPacket.substr(idx);
278
45
      }
279
960
      if (XmpParser::decode(pImage->xmpData(), xmpPacket, dp)) {
280
960
#ifndef SUPPRESS_WARNINGS
281
960
        EXV_WARNING << "Failed to decode XMP metadata.\n";
282
960
#endif
283
960
      }
284
960
    }
285
1.24k
  }
286
287
  // We look if an Adobe XMP string exist.
288
289
4.48k
  if (keySize >= 17 && memcmp("XML:com.adobe.xmp", key, 17) == 0 && pImage->xmpData().empty() && !arr.empty()) {
290
746
    std::string& xmpPacket = pImage->xmpPacket();
291
746
    xmpPacket.assign(arr.c_str(), arr.size());
292
746
    if (auto idx = xmpPacket.find_first_of('<'); idx != std::string::npos && idx > 0) {
293
314
#ifndef SUPPRESS_WARNINGS
294
314
      EXV_WARNING << "Removing " << idx << " characters "
295
0
                  << "from the beginning of the XMP packet\n";
296
314
#endif
297
314
      xmpPacket = xmpPacket.substr(idx);
298
314
    }
299
746
    if (XmpParser::decode(pImage->xmpData(), xmpPacket, dp)) {
300
185
#ifndef SUPPRESS_WARNINGS
301
185
      EXV_WARNING << "Failed to decode XMP metadata.\n";
302
185
#endif
303
185
    }
304
746
  }
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
4.48k
  if (keySize >= 11 && memcmp("Description", key, 11) == 0 && pImage->comment().empty()) {
310
74
    pImage->setComment(std::string(arr.c_str(), arr.size()));
311
74
  }
312
313
4.48k
}  // PngChunk::parseChunkContent
314
315
0
std::string PngChunk::makeMetadataChunk(std::string_view metadata, MetadataId type) {
316
0
  std::string rawProfile;
317
318
0
  switch (type) {
319
0
    case mdComment:
320
0
      return makeUtf8TxtChunk("Description", metadata, true);
321
0
    case mdIptc:
322
0
      rawProfile = writeRawProfile(metadata, "iptc");
323
0
      return makeAsciiTxtChunk("Raw profile type iptc", rawProfile, true);
324
0
    case mdXmp:
325
0
      return makeUtf8TxtChunk("XML:com.adobe.xmp", metadata, false);
326
0
    case mdExif:
327
0
    case mdIccProfile:
328
0
    case mdNone:
329
0
      return {};
330
0
  }
331
332
0
  return {};
333
334
0
}  // PngChunk::makeMetadataChunk
335
336
122
void PngChunk::zlibUncompress(const byte* compressedText, unsigned int compressedTextSize, DataBuf& arr) {
337
122
  uLongf uncompressedLen = compressedTextSize * 2;  // just a starting point
338
122
  int zlibResult = Z_BUF_ERROR;
339
122
  int dos = 0;
340
341
337
  while (zlibResult == Z_BUF_ERROR) {
342
290
    arr.alloc(uncompressedLen);
343
290
    zlibResult = uncompress(arr.data(), &uncompressedLen, compressedText, compressedTextSize);
344
290
    if (zlibResult == Z_OK) {
345
47
      arr.resize(uncompressedLen);
346
243
    } else if (zlibResult == Z_BUF_ERROR) {
347
      // the uncompressedArray needs to be larger
348
178
      uncompressedLen *= 2;
349
      // DoS protection. can't be bigger than 64k
350
178
      if (uncompressedLen > 131072) {
351
27
        if (++dos > 1)
352
10
          break;
353
17
        uncompressedLen = 131072;
354
17
      }
355
178
    } else {
356
      // something bad happened
357
65
      throw Error(ErrorCode::kerFailedToReadImageData);
358
65
    }
359
290
  }
360
361
57
  if (zlibResult != Z_OK) {
362
10
    throw Error(ErrorCode::kerFailedToReadImageData);
363
10
  }
364
57
}  // PngChunk::zlibUncompress
365
366
0
std::string PngChunk::zlibCompress(std::string_view text) {
367
0
  auto compressedLen = static_cast<uLongf>(text.size() * 2);  // just a starting point
368
0
  int zlibResult = Z_BUF_ERROR;
369
370
0
  DataBuf arr;
371
0
  while (zlibResult == Z_BUF_ERROR) {
372
0
    arr.resize(compressedLen);
373
0
    zlibResult = compress2(arr.data(), &compressedLen, reinterpret_cast<const Bytef*>(text.data()),
374
0
                           static_cast<uLong>(text.size()), Z_BEST_COMPRESSION);
375
376
0
    switch (zlibResult) {
377
0
      case Z_OK:
378
0
        arr.resize(compressedLen);
379
0
        break;
380
0
      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
0
        compressedLen *= 2;
386
        // DoS protection. Cap max compressed size
387
0
        if (compressedLen > 131072)
388
0
          throw Error(ErrorCode::kerFailedToReadImageData);
389
0
        break;
390
0
      default:
391
        // Something bad happened
392
0
        throw Error(ErrorCode::kerFailedToReadImageData);
393
0
    }
394
0
  }
395
396
0
  return {arr.c_str(), arr.size()};
397
398
0
}  // PngChunk::zlibCompress
399
400
0
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
0
  auto chunkData = std::string(keyword) + '\0';
413
0
  std::string chunkType;
414
0
  if (compress) {
415
0
    chunkData += '\0' + zlibCompress(text);
416
0
    chunkType = "zTXt";
417
0
  } else {
418
0
    chunkData += text;
419
0
    chunkType = "tEXt";
420
0
  }
421
  // Determine length of the chunk data
422
0
  byte length[4];
423
0
  ul2Data(length, static_cast<uint32_t>(chunkData.size()), bigEndian);
424
  // Calculate CRC on chunk type and chunk data
425
0
  std::string crcData = chunkType + chunkData;
426
0
  uLong tmp = crc32(0L, Z_NULL, 0);
427
0
  tmp = crc32(tmp, reinterpret_cast<const Bytef*>(crcData.data()), static_cast<uInt>(crcData.size()));
428
0
  byte crc[4];
429
0
  ul2Data(crc, tmp, bigEndian);
430
  // Assemble the chunk
431
0
  return std::string(reinterpret_cast<const char*>(length), 4) + chunkType + chunkData +
432
0
         std::string(reinterpret_cast<const char*>(crc), 4);
433
434
0
}  // PngChunk::makeAsciiTxtChunk
435
436
0
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
0
  auto chunkData = std::string(keyword);
447
0
  if (compress) {
448
0
    static const char flags[] = {0x00, 0x01, 0x00, 0x00, 0x00};
449
0
    chunkData += std::string(flags, 5) + zlibCompress(text);
450
0
  } else {
451
0
    static const char flags[] = {0x00, 0x00, 0x00, 0x00, 0x00};
452
0
    chunkData += std::string(flags, 5) + text.data();
453
0
  }
454
  // Determine length of the chunk data
455
0
  byte length[4];
456
0
  ul2Data(length, static_cast<uint32_t>(chunkData.size()), bigEndian);
457
  // Calculate CRC on chunk type and chunk data
458
0
  std::string chunkType = "iTXt";
459
0
  std::string crcData = chunkType + chunkData;
460
0
  uLong tmp = crc32(0L, Z_NULL, 0);
461
0
  tmp = crc32(tmp, reinterpret_cast<const Bytef*>(crcData.data()), static_cast<uInt>(crcData.size()));
462
0
  byte crc[4];
463
0
  ul2Data(crc, tmp, bigEndian);
464
  // Assemble the chunk
465
0
  return std::string(reinterpret_cast<const char*>(length), 4) + chunkType + chunkData +
466
0
         std::string(reinterpret_cast<const char*>(crc), 4);
467
468
0
}  // PngChunk::makeUtf8TxtChunk
469
470
2.69k
DataBuf PngChunk::readRawProfile(const DataBuf& text, bool iTXt) {
471
2.69k
  DataBuf info;
472
2.69k
  if (text.size() <= 1) {
473
35
    return info;
474
35
  }
475
476
2.65k
  const unsigned char unhex[103] = {
477
2.65k
      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
2.65k
      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
2.65k
      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
2.65k
  };
481
482
2.65k
  if (iTXt) {
483
0
    info.alloc(text.size());
484
0
    std::copy(text.begin(), text.end(), info.begin());
485
0
    return info;
486
0
  }
487
488
2.65k
  const char* sp = text.c_str(1);                 // current byte (space pointer)
489
2.65k
  const char* eot = text.c_str(text.size() - 1);  // end of text
490
491
2.65k
  if (sp >= eot) {
492
69
    return info;
493
69
  }
494
495
  // Look for newline
496
39.1k
  while (*sp != '\n') {
497
36.6k
    sp++;
498
36.6k
    if (sp == eot) {
499
64
      return info;
500
64
    }
501
36.6k
  }
502
2.52k
  sp++;  // step over '\n'
503
2.52k
  if (sp == eot) {
504
13
    return info;
505
13
  }
506
507
  // Look for length
508
54.1k
  while (*sp == '\0' || *sp == ' ' || *sp == '\n') {
509
51.7k
    sp++;
510
51.7k
    if (sp == eot) {
511
23
      return info;
512
23
    }
513
51.7k
  }
514
515
  // Parse the length.
516
2.48k
  size_t length = 0;
517
4.53k
  while ('0' <= *sp && *sp <= '9') {
518
    // Compute the new length using unsigned long, so that we can check for overflow.
519
2.14k
    const size_t newlength = (10 * length) + (*sp - '0');
520
2.14k
    length = newlength;
521
2.14k
    sp++;
522
2.14k
    if (sp == eot) {
523
97
      return info;
524
97
    }
525
2.14k
  }
526
2.38k
  sp++;  // step over '\n'
527
2.38k
  if (sp == eot) {
528
274
    return info;
529
274
  }
530
531
2.11k
  enforce(length <= static_cast<size_t>(eot - sp) / 2, Exiv2::ErrorCode::kerCorruptedMetadata);
532
533
  // Allocate space
534
2.11k
  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
598
  }
539
2.11k
  info.alloc(length);
540
2.11k
  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
2.11k
  if (info.empty())  // Early return
548
598
    return info;
549
550
  // Copy profile, skipping white space and column 1 "=" signs
551
1.51k
  unsigned char* dp = info.data();  // decode pointer
552
1.51k
  size_t nibbles = length * 2;
553
554
13.7k
  for (size_t i = 0; i < nibbles; i++) {
555
12.5k
    enforce(sp < eot, Exiv2::ErrorCode::kerCorruptedMetadata);
556
20.8k
    while (*sp < '0' || (*sp > '9' && *sp < 'a') || *sp > 'f') {
557
8.58k
      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
288
        return {};
562
288
      }
563
564
8.29k
      sp++;
565
8.29k
      enforce(sp < eot, Exiv2::ErrorCode::kerCorruptedMetadata);
566
8.29k
    }
567
568
12.2k
    if (i % 2 == 0)
569
6.11k
      *dp = static_cast<unsigned char>(16 * unhex[static_cast<size_t>(*sp++)]);
570
6.10k
    else
571
6.10k
      (*dp++) += unhex[static_cast<size_t>(*sp++)];
572
12.2k
  }
573
574
1.22k
  return info;
575
576
1.51k
}  // PngChunk::readRawProfile
577
578
0
std::string PngChunk::writeRawProfile(std::string_view profileData, const char* profileType) {
579
0
  static const byte hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
580
581
0
  auto ss = stringFormat("\n{}\n{:08}", profileType, profileData.size());
582
0
  auto sp = reinterpret_cast<const byte*>(profileData.data());
583
0
  for (std::string::size_type i = 0; i < profileData.size(); ++i) {
584
0
    if (i % 36 == 0)
585
0
      ss += '\n';
586
0
    ss += hex[*sp >> 4 & 0x0fU];
587
0
    ss += hex[*sp++ & 0x0fU];
588
0
  }
589
0
  ss += '\n';
590
0
  return ss;
591
592
0
}  // PngChunk::writeRawProfile
593
594
}  // namespace Exiv2::Internal
595
#endif  // ifdef EXV_HAVE_LIBZ