/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 |