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