Coverage Report

Created: 2026-10-17 07:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/exiv2/src/webpimage.cpp
Line
Count
Source
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
3
/*
4
  Google's WEBP container spec can be found at the link below:
5
  https://developers.google.com/speed/webp/docs/riff_container
6
*/
7
8
// included header files
9
#include "webpimage.hpp"
10
11
#include "basicio.hpp"
12
#include "config.h"
13
#include "convert.hpp"
14
#include "enforce.hpp"
15
#include "futils.hpp"
16
#include "image_int.hpp"
17
#include "safe_op.hpp"
18
#include "types.hpp"
19
20
#include <array>
21
#include <cstring>
22
#include <iostream>
23
24
#ifdef EXIV2_DEBUG_MESSAGES
25
namespace {
26
std::string binaryToHex(const uint8_t* data, size_t size) {
27
  std::stringstream hexOutput;
28
29
  auto tl = size / 16 * 16;
30
  auto tl_offset = size - tl;
31
32
  for (size_t loop = 0; loop < size; loop++) {
33
    if (data[loop] < 16) {
34
      hexOutput << "0";
35
    }
36
    hexOutput << std::hex << static_cast<int>(data[loop]);
37
    if ((loop % 8) == 7) {
38
      hexOutput << "  ";
39
    }
40
    if ((loop % 16) == 15 || loop == (tl + tl_offset - 1)) {
41
      int max = 15;
42
      if (loop >= tl) {
43
        max = static_cast<int>(tl_offset) - 1;
44
        for (int offset = 0; offset < static_cast<int>(16 - tl_offset); offset++) {
45
          if ((offset % 8) == 7) {
46
            hexOutput << "  ";
47
          }
48
          hexOutput << "   ";
49
        }
50
      }
51
      hexOutput << " ";
52
      for (int offset = max; offset >= 0; offset--) {
53
        if (offset == (max - 8)) {
54
          hexOutput << "  ";
55
        }
56
        uint8_t c = '.';
57
        if (data[loop - offset] >= 0x20 && data[loop - offset] <= 0x7E) {
58
          c = data[loop - offset];
59
        }
60
        hexOutput << static_cast<char>(c);
61
      }
62
      hexOutput << '\n';
63
    }
64
  }
65
66
  hexOutput << '\n' << '\n' << '\n';
67
68
  return hexOutput.str();
69
}
70
}  // namespace
71
#endif
72
73
// *****************************************************************************
74
// class member definitions
75
namespace Exiv2 {
76
77
WebPImage::WebPImage(BasicIo::UniquePtr io, const ImageCtorParams& params) :
78
180
    Image(ImageType::webp, mdNone, std::move(io), params) {
79
180
}  // WebPImage::WebPImage
80
81
0
std::string WebPImage::mimeType() const {
82
0
  return "image/webp";
83
0
}
84
85
0
void WebPImage::setIptcData(const IptcData& /*iptcData*/) {
86
  // not supported
87
  // just quietly ignore the request
88
  // throw(Error(ErrorCode::kerInvalidSettingForImage, "IPTC metadata", "WebP"));
89
0
}
90
91
0
void WebPImage::setComment(const std::string&) {
92
  // not supported
93
0
  throw(Error(ErrorCode::kerInvalidSettingForImage, "Image comment", "WebP"));
94
0
}
95
96
/* =========================================== */
97
98
52
void WebPImage::writeMetadata() {
99
52
  if (io_->open() != 0) {
100
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
101
0
  }
102
52
  IoCloser closer(*io_);
103
52
  MemIo tempIo;
104
105
52
  doWriteMetadata(tempIo);  // may throw
106
52
  io_->close();
107
52
  io_->transfer(tempIo);  // may throw
108
52
}  // WebPImage::writeMetadata
109
110
52
void WebPImage::doWriteMetadata(BasicIo& outIo) {
111
52
  if (!io_->isopen())
112
0
    throw Error(ErrorCode::kerInputDataReadFailed);
113
52
  if (!outIo.isopen())
114
0
    throw Error(ErrorCode::kerImageWriteFailed);
115
116
#ifdef EXIV2_DEBUG_MESSAGES
117
  std::cout << "Writing metadata" << '\n';
118
#endif
119
120
52
  byte data[WEBP_TAG_SIZE * 3];
121
52
  DataBuf chunkId(WEBP_TAG_SIZE + 1);
122
52
  chunkId.write_uint8(WEBP_TAG_SIZE, '\0');
123
124
52
  io_->readOrThrow(data, WEBP_TAG_SIZE * 3, Exiv2::ErrorCode::kerCorruptedMetadata);
125
52
  uint64_t filesize = Exiv2::getULong(data + WEBP_TAG_SIZE, littleEndian);
126
127
  /* Set up header */
128
52
  if (outIo.write(data, WEBP_TAG_SIZE * 3) != WEBP_TAG_SIZE * 3)
129
0
    throw Error(ErrorCode::kerImageWriteFailed);
130
131
  /* Parse Chunks */
132
52
  bool has_size = false;
133
52
  bool has_xmp = false;
134
52
  bool has_exif = false;
135
52
  bool has_vp8x = false;
136
52
  bool has_alpha = false;
137
52
  bool has_icc = iccProfileDefined();
138
139
52
  uint32_t width = 0;
140
52
  uint32_t height = 0;
141
142
52
  std::array<byte, WEBP_TAG_SIZE> size_buff;
143
52
  Blob blob;
144
145
52
  if (!exifData_.empty()) {
146
0
    ExifParser::encode(blob, littleEndian, exifData_);
147
0
    if (!blob.empty()) {
148
0
      has_exif = true;
149
0
    }
150
0
  }
151
152
52
  if (!xmpData_.empty() && !writeXmpFromPacket()) {
153
0
    XmpParser::encode(xmpPacket_, xmpData_, XmpParser::useCompactFormat | XmpParser::omitAllFormatting);
154
0
  }
155
52
  has_xmp = !xmpPacket_.empty();
156
52
  std::string xmp(xmpPacket_);
157
158
  /* Verify for a VP8X Chunk First before writing in
159
   case we have any exif or xmp data, also check
160
   for any chunks with alpha frame/layer set */
161
393
  while (!io_->eof() && io_->tell() < filesize) {
162
341
    io_->readOrThrow(chunkId.data(), WEBP_TAG_SIZE, Exiv2::ErrorCode::kerCorruptedMetadata);
163
341
    io_->readOrThrow(size_buff.data(), WEBP_TAG_SIZE, Exiv2::ErrorCode::kerCorruptedMetadata);
164
341
    const uint32_t size_u32 = Exiv2::getULong(size_buff.data(), littleEndian);
165
166
    // Check that `size_u32` is within bounds.
167
341
    Internal::enforce(size_u32 <= io_->size() - io_->tell(), Exiv2::ErrorCode::kerCorruptedMetadata);
168
169
341
    DataBuf payload(size_u32);
170
341
    if (!payload.empty()) {
171
341
      io_->readOrThrow(payload.data(), payload.size(), Exiv2::ErrorCode::kerCorruptedMetadata);
172
341
      if (payload.size() % 2) {
173
128
        byte c = 0;
174
128
        io_->readOrThrow(&c, 1, Exiv2::ErrorCode::kerCorruptedMetadata);
175
128
      }
176
341
    }
177
178
    /* Chunk with information about features
179
     used in the file. */
180
341
    if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_VP8X) && !has_vp8x) {
181
13
      has_vp8x = true;
182
13
    }
183
341
    if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_VP8X) && !has_size) {
184
13
      Internal::enforce(size_u32 >= 10, Exiv2::ErrorCode::kerCorruptedMetadata);
185
13
      has_size = true;
186
13
      std::array<byte, WEBP_TAG_SIZE> size_buf;
187
188
      // Fetch width - stored in 24bits
189
13
      std::copy_n(payload.begin() + 4, 3, size_buf.begin());
190
13
      size_buf.back() = 0;
191
13
      width = Exiv2::getULong(size_buf.data(), littleEndian) + 1;
192
193
      // Fetch height - stored in 24bits
194
13
      std::copy_n(payload.begin() + 7, 3, size_buf.begin());
195
13
      size_buf.back() = 0;
196
13
      height = Exiv2::getULong(size_buf.data(), littleEndian) + 1;
197
13
    }
198
199
    /* Chunk with animation control data. */
200
#ifdef __CHECK_FOR_ALPHA__  // Maybe in the future
201
    if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_ANIM) && !has_alpha) {
202
      has_alpha = true;
203
    }
204
#endif
205
206
    /* Chunk with lossy image data. */
207
#ifdef __CHECK_FOR_ALPHA__  // Maybe in the future
208
    if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_VP8) && !has_alpha) {
209
      has_alpha = true;
210
    }
211
#endif
212
341
    if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_VP8) && !has_size) {
213
4
      Internal::enforce(size_u32 >= 10, Exiv2::ErrorCode::kerCorruptedMetadata);
214
4
      has_size = true;
215
4
      std::array<byte, 2> size_buf;
216
217
      /* Refer to this https://tools.ietf.org/html/rfc6386
218
         for height and width reference for VP8 chunks */
219
220
      // Fetch width - stored in 16bits
221
4
      std::copy_n(payload.begin() + 6, 2, size_buf.begin());
222
4
      width = Exiv2::getUShort(size_buf.data(), littleEndian) & 0x3fff;
223
224
      // Fetch height - stored in 16bits
225
4
      std::copy_n(payload.begin() + 8, 2, size_buf.begin());
226
4
      height = Exiv2::getUShort(size_buf.data(), littleEndian) & 0x3fff;
227
4
    }
228
229
    /* Chunk with lossless image data. */
230
341
    if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_VP8L) && !has_alpha) {
231
11
      Internal::enforce(size_u32 >= 5, Exiv2::ErrorCode::kerCorruptedMetadata);
232
11
      if ((payload.read_uint8(4) & WEBP_VP8X_ALPHA_BIT) == WEBP_VP8X_ALPHA_BIT) {
233
2
        has_alpha = true;
234
2
      }
235
11
    }
236
341
    if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_VP8L) && !has_size) {
237
9
      Internal::enforce(size_u32 >= 5, Exiv2::ErrorCode::kerCorruptedMetadata);
238
9
      has_size = true;
239
9
      std::array<byte, 2> size_buf_w;
240
9
      std::array<byte, 3> size_buf_h;
241
242
      /* For VP8L chunks width & height are stored in 28 bits
243
         of a 32 bit field requires bitshifting to get actual
244
         sizes. Width and height are split even into 14 bits
245
         each. Refer to this https://goo.gl/bpgMJf */
246
247
      // Fetch width - 14 bits wide
248
9
      std::copy_n(payload.begin() + 1, 2, size_buf_w.begin());
249
9
      size_buf_w.back() &= 0x3F;
250
9
      width = Exiv2::getUShort(size_buf_w.data(), littleEndian) + 1;
251
252
      // Fetch height - 14 bits wide
253
9
      std::copy_n(payload.begin() + 2, 3, size_buf_h.begin());
254
9
      size_buf_h[0] = ((size_buf_h[0] >> 6) & 0x3) | ((size_buf_h[1] & 0x3FU) << 0x2);
255
9
      size_buf_h[1] = ((size_buf_h[1] >> 6) & 0x3) | ((size_buf_h[2] & 0xFU) << 0x2);
256
9
      height = Exiv2::getUShort(size_buf_h.data(), littleEndian) + 1;
257
9
    }
258
259
    /* Chunk with animation frame. */
260
341
    if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_ANMF) && !has_alpha) {
261
9
      Internal::enforce(size_u32 >= 6, Exiv2::ErrorCode::kerCorruptedMetadata);
262
9
      if ((payload.read_uint8(5) & 0x2) == 0x2) {
263
1
        has_alpha = true;
264
1
      }
265
9
    }
266
341
    if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_ANMF) && !has_size) {
267
3
      Internal::enforce(size_u32 >= 12, Exiv2::ErrorCode::kerCorruptedMetadata);
268
3
      has_size = true;
269
3
      std::array<byte, WEBP_TAG_SIZE> size_buf;
270
271
      // Fetch width - stored in 24bits
272
3
      std::copy_n(payload.begin() + 6, 3, size_buf.begin());
273
3
      size_buf.back() = 0;
274
3
      width = Exiv2::getULong(size_buf.data(), littleEndian) + 1;
275
276
      // Fetch height - stored in 24bits
277
3
      std::copy_n(payload.begin() + 9, 3, size_buf.begin());
278
3
      size_buf.back() = 0;
279
3
      height = Exiv2::getULong(size_buf.data(), littleEndian) + 1;
280
3
    }
281
282
    /* Chunk with alpha data. */
283
341
    if (equalsWebPTag(chunkId, "ALPH") && !has_alpha) {
284
0
      has_alpha = true;
285
0
    }
286
341
  }
287
288
  /* Inject a VP8X chunk if one isn't available. */
289
52
  if (!has_vp8x) {
290
31
    inject_VP8X(outIo, has_xmp, has_exif, has_alpha, has_icc, width, height);
291
31
  }
292
293
52
  io_->seek(12, BasicIo::beg);
294
324
  while (!io_->eof() && io_->tell() < filesize) {
295
272
    io_->readOrThrow(chunkId.data(), 4, Exiv2::ErrorCode::kerCorruptedMetadata);
296
272
    io_->readOrThrow(size_buff.data(), 4, Exiv2::ErrorCode::kerCorruptedMetadata);
297
298
272
    const uint32_t size_u32 = Exiv2::getULong(size_buff.data(), littleEndian);
299
300
    // Check that `size_u32` is within bounds.
301
272
    Internal::enforce(size_u32 <= io_->size() - io_->tell(), Exiv2::ErrorCode::kerCorruptedMetadata);
302
303
272
    DataBuf payload(size_u32);
304
272
    io_->readOrThrow(payload.data(), size_u32, Exiv2::ErrorCode::kerCorruptedMetadata);
305
272
    if (io_->tell() % 2)
306
102
      io_->seek(+1, BasicIo::cur);  // skip pad
307
308
272
    if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_VP8X)) {
309
28
      Internal::enforce(size_u32 >= 1, Exiv2::ErrorCode::kerCorruptedMetadata);
310
28
      if (has_icc) {
311
1
        const uint8_t x = payload.read_uint8(0);
312
1
        payload.write_uint8(0, x | WEBP_VP8X_ICC_BIT);
313
27
      } else {
314
27
        const uint8_t x = payload.read_uint8(0);
315
27
        payload.write_uint8(0, x & ~WEBP_VP8X_ICC_BIT);
316
27
      }
317
318
28
      if (has_xmp) {
319
12
        const uint8_t x = payload.read_uint8(0);
320
12
        payload.write_uint8(0, x | WEBP_VP8X_XMP_BIT);
321
16
      } else {
322
16
        const uint8_t x = payload.read_uint8(0);
323
16
        payload.write_uint8(0, x & ~WEBP_VP8X_XMP_BIT);
324
16
      }
325
326
28
      if (has_exif) {
327
0
        const uint8_t x = payload.read_uint8(0);
328
0
        payload.write_uint8(0, x | WEBP_VP8X_EXIF_BIT);
329
28
      } else {
330
28
        const uint8_t x = payload.read_uint8(0);
331
28
        payload.write_uint8(0, x & ~WEBP_VP8X_EXIF_BIT);
332
28
      }
333
334
28
      if (outIo.write(chunkId.c_data(), WEBP_TAG_SIZE) != WEBP_TAG_SIZE)
335
0
        throw Error(ErrorCode::kerImageWriteFailed);
336
28
      if (outIo.write(size_buff.data(), WEBP_TAG_SIZE) != WEBP_TAG_SIZE)
337
0
        throw Error(ErrorCode::kerImageWriteFailed);
338
28
      if (outIo.write(payload.c_data(), payload.size()) != payload.size())
339
0
        throw Error(ErrorCode::kerImageWriteFailed);
340
28
      if (outIo.tell() % 2 && outIo.write(&WEBP_PAD_ODD, 1) != 1)
341
0
        throw Error(ErrorCode::kerImageWriteFailed);
342
343
28
      if (has_icc) {
344
1
        if (outIo.write(reinterpret_cast<const byte*>(WEBP_CHUNK_HEADER_ICCP), WEBP_TAG_SIZE) != WEBP_TAG_SIZE)
345
0
          throw Error(ErrorCode::kerImageWriteFailed);
346
1
        ul2Data(data, static_cast<uint32_t>(iccProfile_.size()), littleEndian);
347
1
        if (outIo.write(data, WEBP_TAG_SIZE) != WEBP_TAG_SIZE)
348
0
          throw Error(ErrorCode::kerImageWriteFailed);
349
1
        if (outIo.write(iccProfile_.c_data(), iccProfile_.size()) != iccProfile_.size()) {
350
0
          throw Error(ErrorCode::kerImageWriteFailed);
351
0
        }
352
1
        has_icc = false;
353
1
      }
354
244
    } else if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_ICCP)) {
355
      // Skip it altogether handle it prior to here :)
356
241
    } else if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_EXIF)) {
357
      // Skip and add new data afterwards
358
241
    } else if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_XMP)) {
359
      // Skip and add new data afterwards
360
232
    } else {
361
232
      if (outIo.write(chunkId.c_data(), WEBP_TAG_SIZE) != WEBP_TAG_SIZE)
362
0
        throw Error(ErrorCode::kerImageWriteFailed);
363
232
      if (outIo.write(size_buff.data(), WEBP_TAG_SIZE) != WEBP_TAG_SIZE)
364
0
        throw Error(ErrorCode::kerImageWriteFailed);
365
232
      if (outIo.write(payload.c_data(), payload.size()) != payload.size())
366
0
        throw Error(ErrorCode::kerImageWriteFailed);
367
232
    }
368
369
    // Encoder required to pad odd sized data with a null byte
370
272
    if (outIo.tell() % 2 && outIo.write(&WEBP_PAD_ODD, 1) != 1)
371
0
      throw Error(ErrorCode::kerImageWriteFailed);
372
272
  }
373
374
52
  if (has_exif) {
375
0
    if (outIo.write(reinterpret_cast<const byte*>(WEBP_CHUNK_HEADER_EXIF), WEBP_TAG_SIZE) != WEBP_TAG_SIZE)
376
0
      throw Error(ErrorCode::kerImageWriteFailed);
377
0
    us2Data(data, static_cast<uint16_t>(blob.size()) + 8, bigEndian);
378
0
    ul2Data(data, static_cast<uint32_t>(blob.size()), littleEndian);
379
0
    if (outIo.write(data, WEBP_TAG_SIZE) != WEBP_TAG_SIZE)
380
0
      throw Error(ErrorCode::kerImageWriteFailed);
381
0
    if (outIo.write(blob.data(), blob.size()) != blob.size()) {
382
0
      throw Error(ErrorCode::kerImageWriteFailed);
383
0
    }
384
0
    if (outIo.tell() % 2 && outIo.write(&WEBP_PAD_ODD, 1) != 1)
385
0
      throw Error(ErrorCode::kerImageWriteFailed);
386
0
  }
387
388
52
  if (has_xmp) {
389
6
    if (outIo.write(reinterpret_cast<const byte*>(WEBP_CHUNK_HEADER_XMP), WEBP_TAG_SIZE) != WEBP_TAG_SIZE)
390
0
      throw Error(ErrorCode::kerImageWriteFailed);
391
6
    ul2Data(data, static_cast<uint32_t>(xmpPacket().size()), littleEndian);
392
6
    if (outIo.write(data, WEBP_TAG_SIZE) != WEBP_TAG_SIZE)
393
0
      throw Error(ErrorCode::kerImageWriteFailed);
394
6
    if (outIo.write(reinterpret_cast<const byte*>(xmp.data()), xmp.size()) != xmp.size()) {
395
0
      throw Error(ErrorCode::kerImageWriteFailed);
396
0
    }
397
6
    if (outIo.tell() % 2 && outIo.write(&WEBP_PAD_ODD, 1) != 1)
398
0
      throw Error(ErrorCode::kerImageWriteFailed);
399
6
  }
400
401
  // Fix File Size Payload Data
402
52
  outIo.seek(0, BasicIo::beg);
403
52
  filesize = outIo.size() - 8;
404
52
  outIo.seek(4, BasicIo::beg);
405
52
  ul2Data(data, static_cast<uint32_t>(filesize), littleEndian);
406
52
  if (outIo.write(data, WEBP_TAG_SIZE) != WEBP_TAG_SIZE)
407
0
    throw Error(ErrorCode::kerImageWriteFailed);
408
409
52
}  // WebPImage::writeMetadata
410
411
/* =========================================== */
412
413
422
void WebPImage::printStructure(std::ostream& out, PrintStructureOption option, size_t depth) {
414
422
  if (io_->open() != 0) {
415
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
416
0
  }
417
  // Ensure this is the correct image type
418
422
  if (!isWebPType(*io_, true)) {
419
0
    if (io_->error() || io_->eof())
420
0
      throw Error(ErrorCode::kerFailedToReadImageData);
421
0
    throw Error(ErrorCode::kerNotAnImage, "WEBP");
422
0
  }
423
424
422
  bool bPrint = option == kpsBasic || option == kpsRecursive;
425
422
  if (bPrint || option == kpsXMP || option == kpsIccProfile || option == kpsIptcErase) {
426
334
    byte data[WEBP_TAG_SIZE * 2];
427
334
    io_->read(data, WEBP_TAG_SIZE * 2);
428
334
    uint64_t filesize = Exiv2::getULong(data + WEBP_TAG_SIZE, littleEndian);
429
334
    DataBuf chunkId(5);
430
334
    chunkId.write_uint8(4, '\0');
431
432
334
    if (bPrint) {
433
159
      out << Internal::indent(depth) << "STRUCTURE OF WEBP FILE: " << io().path() << '\n';
434
159
      out << Internal::indent(depth) << " Chunk |   Length |   Offset | Payload" << '\n';
435
159
    }
436
437
334
    io_->seek(0, BasicIo::beg);  // rewind
438
2.63k
    while (!io_->eof() && io_->tell() < filesize) {
439
2.29k
      auto offset = io_->tell();
440
2.29k
      byte size_buff[WEBP_TAG_SIZE];
441
2.29k
      io_->read(chunkId.data(), WEBP_TAG_SIZE);
442
2.29k
      io_->read(size_buff, WEBP_TAG_SIZE);
443
2.29k
      const uint32_t size = Exiv2::getULong(size_buff, littleEndian);
444
445
      // Check that `size` is within bounds.
446
2.29k
      Internal::enforce(!offset || size <= io_->size() - io_->tell(), Exiv2::ErrorCode::kerCorruptedMetadata);
447
448
2.29k
      DataBuf payload(offset ? size : WEBP_TAG_SIZE);  // header is different from chunks
449
2.29k
      io_->read(payload.data(), payload.size());
450
451
2.29k
      if (bPrint) {
452
995
        out << Internal::indent(depth) << stringFormat("  {} | {:8} | {:8} | ", chunkId.c_str(), size, offset)
453
995
            << Internal::binaryToString(makeSlice(payload, 0, payload.size() > 32 ? 32 : payload.size())) << '\n';
454
995
      }
455
456
2.29k
      if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_EXIF) && option == kpsRecursive) {
457
        // create memio object with the payload, then print the structure
458
19
        MemIo p(payload.c_data(), payload.size());
459
19
        printTiffStructure(p, out, option, depth + 1);
460
19
      }
461
462
2.29k
      bool bPrintPayload = (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_XMP) && option == kpsXMP) ||
463
2.23k
                           (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_ICCP) && option == kpsIccProfile);
464
2.29k
      if (bPrintPayload) {
465
30
        out.write(payload.c_str(), payload.size());
466
30
      }
467
468
2.29k
      if (offset && io_->tell() % 2)
469
725
        io_->seek(+1, BasicIo::cur);  // skip padding byte on sub-chunks
470
2.29k
    }
471
334
  }
472
422
}
473
474
/* =========================================== */
475
476
180
void WebPImage::readMetadata() {
477
180
  if (io_->open() != 0)
478
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
479
180
  IoCloser closer(*io_);
480
  // Ensure that this is the correct image type
481
180
  if (!isWebPType(*io_, true)) {
482
0
    if (io_->error() || io_->eof())
483
0
      throw Error(ErrorCode::kerFailedToReadImageData);
484
0
    throw Error(ErrorCode::kerNotAJpeg);
485
0
  }
486
180
  clearMetadata();
487
488
180
  byte data[12];
489
180
  DataBuf chunkId(5);
490
180
  chunkId.write_uint8(4, '\0');
491
492
180
  io_->readOrThrow(data, WEBP_TAG_SIZE * 3, Exiv2::ErrorCode::kerCorruptedMetadata);
493
494
180
  const uint32_t filesize = Safe::add(Exiv2::getULong(data + WEBP_TAG_SIZE, littleEndian), 8U);
495
180
  Internal::enforce(filesize <= io_->size(), Exiv2::ErrorCode::kerCorruptedMetadata);
496
497
180
  WebPImage::decodeChunks(filesize);
498
499
180
}  // WebPImage::readMetadata
500
501
165
void WebPImage::decodeChunks(uint32_t filesize) {
502
165
  DataBuf chunkId(5);
503
165
  std::array<byte, WEBP_TAG_SIZE> size_buff;
504
165
  bool has_canvas_data = false;
505
506
#ifdef EXIV2_DEBUG_MESSAGES
507
  std::cout << "Reading metadata" << '\n';
508
#endif
509
510
165
  chunkId.write_uint8(4, '\0');
511
1.67k
  while (!io_->eof() && io_->tell() < filesize) {
512
1.51k
    io_->readOrThrow(chunkId.data(), WEBP_TAG_SIZE, Exiv2::ErrorCode::kerCorruptedMetadata);
513
1.51k
    io_->readOrThrow(size_buff.data(), WEBP_TAG_SIZE, Exiv2::ErrorCode::kerCorruptedMetadata);
514
515
1.51k
    const uint32_t size = Exiv2::getULong(size_buff.data(), littleEndian);
516
517
    // Check that `size` is within bounds.
518
1.51k
    Internal::enforce(io_->tell() <= filesize, Exiv2::ErrorCode::kerCorruptedMetadata);
519
1.51k
    Internal::enforce(size <= (filesize - io_->tell()), Exiv2::ErrorCode::kerCorruptedMetadata);
520
521
1.51k
    if (DataBuf payload(size); payload.empty()) {
522
689
      io_->seek(size, BasicIo::cur);
523
825
    } else if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_VP8X) && !has_canvas_data) {
524
25
      Internal::enforce(size >= 10, Exiv2::ErrorCode::kerCorruptedMetadata);
525
526
25
      has_canvas_data = true;
527
25
      std::array<byte, WEBP_TAG_SIZE> size_buf;
528
529
25
      io_->readOrThrow(payload.data(), payload.size(), Exiv2::ErrorCode::kerCorruptedMetadata);
530
531
      // Fetch width
532
25
      std::copy_n(payload.begin() + 4, 3, size_buf.begin());
533
25
      size_buf.back() = 0;
534
25
      pixelWidth_ = Exiv2::getULong(size_buf.data(), littleEndian) + 1;
535
536
      // Fetch height
537
25
      std::copy_n(payload.begin() + 7, 3, size_buf.begin());
538
25
      size_buf.back() = 0;
539
25
      pixelHeight_ = Exiv2::getULong(size_buf.data(), littleEndian) + 1;
540
800
    } else if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_VP8) && !has_canvas_data) {
541
6
      Internal::enforce(size >= 10, Exiv2::ErrorCode::kerCorruptedMetadata);
542
543
6
      has_canvas_data = true;
544
6
      io_->readOrThrow(payload.data(), payload.size(), Exiv2::ErrorCode::kerCorruptedMetadata);
545
6
      std::array<byte, WEBP_TAG_SIZE> size_buf;
546
547
      // Fetch width""
548
6
      std::copy_n(payload.begin() + 6, 2, size_buf.begin());
549
6
      size_buf[2] = 0;
550
6
      size_buf[3] = 0;
551
6
      pixelWidth_ = Exiv2::getULong(size_buf.data(), littleEndian) & 0x3fff;
552
553
      // Fetch height
554
6
      std::copy_n(payload.begin() + 8, 2, size_buf.begin());
555
6
      size_buf[2] = 0;
556
6
      size_buf[3] = 0;
557
6
      pixelHeight_ = Exiv2::getULong(size_buf.data(), littleEndian) & 0x3fff;
558
794
    } else if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_VP8L) && !has_canvas_data) {
559
14
      Internal::enforce(size >= 5, Exiv2::ErrorCode::kerCorruptedMetadata);
560
561
14
      has_canvas_data = true;
562
14
      std::array<byte, 2> size_buf_w;
563
14
      std::array<byte, 3> size_buf_h;
564
565
14
      io_->readOrThrow(payload.data(), payload.size(), Exiv2::ErrorCode::kerCorruptedMetadata);
566
567
      // Fetch width
568
14
      std::copy_n(payload.begin() + 1, 2, size_buf_w.begin());
569
14
      size_buf_w.back() &= 0x3F;
570
14
      pixelWidth_ = Exiv2::getUShort(size_buf_w.data(), littleEndian) + 1;
571
572
      // Fetch height
573
14
      std::copy_n(payload.begin() + 2, 3, size_buf_h.begin());
574
14
      size_buf_h[0] = ((size_buf_h[0] >> 6) & 0x3) | ((size_buf_h[1] & 0x3FU) << 0x2);
575
14
      size_buf_h[1] = ((size_buf_h[1] >> 6) & 0x3) | ((size_buf_h[2] & 0xFU) << 0x2);
576
14
      pixelHeight_ = Exiv2::getUShort(size_buf_h.data(), littleEndian) + 1;
577
780
    } else if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_ANMF) && !has_canvas_data) {
578
5
      Internal::enforce(size >= 12, Exiv2::ErrorCode::kerCorruptedMetadata);
579
580
5
      has_canvas_data = true;
581
5
      std::array<byte, WEBP_TAG_SIZE> size_buf;
582
583
5
      io_->readOrThrow(payload.data(), payload.size(), Exiv2::ErrorCode::kerCorruptedMetadata);
584
585
      // Fetch width
586
5
      std::copy_n(payload.begin() + 6, 3, size_buf.begin());
587
5
      size_buf.back() = 0;
588
5
      pixelWidth_ = Exiv2::getULong(size_buf.data(), littleEndian) + 1;
589
590
      // Fetch height
591
5
      std::copy_n(payload.begin() + 9, 3, size_buf.begin());
592
5
      size_buf.back() = 0;
593
5
      pixelHeight_ = Exiv2::getULong(size_buf.data(), littleEndian) + 1;
594
775
    } else if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_ICCP)) {
595
8
      io_->readOrThrow(payload.data(), payload.size(), Exiv2::ErrorCode::kerCorruptedMetadata);
596
8
      this->setIccProfile(std::move(payload));
597
767
    } else if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_EXIF)) {
598
45
      io_->readOrThrow(payload.data(), payload.size(), Exiv2::ErrorCode::kerCorruptedMetadata);
599
600
45
      std::array<byte, 2> size_buff2;
601
      // 4 meaningful bytes + 2 padding bytes
602
45
      auto exifLongHeader = std::array<byte, 6>{0xFF, 0x01, 0xFF, 0xE1, 0x00, 0x00};
603
45
      auto exifShortHeader = std::array<byte, 6>{0x45, 0x78, 0x69, 0x66, 0x00, 0x00};
604
45
      const byte exifTiffLEHeader[] = {0x49, 0x49, 0x2A};        // "MM*"
605
45
      const byte exifTiffBEHeader[] = {0x4D, 0x4D, 0x00, 0x2A};  // "II\0*"
606
45
      size_t offset = 0;
607
45
      bool s_header = false;
608
45
      bool le_header = false;
609
45
      bool be_header = false;
610
45
      size_t pos = getHeaderOffset(payload.c_data(), payload.size(), exifLongHeader.data(), 4);
611
612
45
      if (pos == std::string::npos) {
613
41
        pos = getHeaderOffset(payload.c_data(), payload.size(), exifLongHeader.data(), 6);
614
41
        if (pos != std::string::npos) {
615
0
          s_header = true;
616
0
        }
617
41
      }
618
45
      if (pos == std::string::npos) {
619
41
        pos = getHeaderOffset(payload.c_data(), payload.size(), exifTiffLEHeader, 3);
620
41
        if (pos != std::string::npos) {
621
1
          le_header = true;
622
1
        }
623
41
      }
624
45
      if (pos == std::string::npos) {
625
40
        pos = getHeaderOffset(payload.c_data(), payload.size(), exifTiffBEHeader, 4);
626
40
        if (pos != std::string::npos) {
627
7
          be_header = true;
628
7
        }
629
40
      }
630
631
45
      if (s_header) {
632
0
        offset += 6;
633
0
      }
634
45
      if (be_header || le_header) {
635
8
        offset += 12;
636
8
      }
637
638
45
      const size_t sizePayload = Safe::add(payload.size(), offset);
639
45
      DataBuf rawExifData(sizePayload);
640
641
45
      if (s_header) {
642
0
        us2Data(size_buff2.data(), static_cast<uint16_t>(sizePayload - 6), bigEndian);
643
0
        std::copy_n(exifLongHeader.begin(), 4, rawExifData.begin());
644
0
        std::copy(size_buff2.begin(), size_buff2.end(), rawExifData.begin() + 4);
645
0
      }
646
647
45
      if (be_header || le_header) {
648
8
        us2Data(size_buff2.data(), static_cast<uint16_t>(sizePayload - 6), bigEndian);
649
8
        std::copy_n(exifLongHeader.begin(), 4, rawExifData.begin());
650
8
        std::copy(size_buff2.begin(), size_buff2.end(), rawExifData.begin() + 4);
651
8
        std::copy(exifShortHeader.begin(), exifShortHeader.end(), rawExifData.begin() + 6);
652
8
      }
653
654
45
      std::copy(payload.begin(), payload.end(), rawExifData.begin() + offset);
655
656
#ifdef EXIV2_DEBUG_MESSAGES
657
      std::cout << "Display Hex Dump [size:" << sizePayload << "]" << '\n';
658
      std::cout << binaryToHex(rawExifData.c_data(), sizePayload);
659
#endif
660
661
45
      if (pos != std::string::npos) {
662
12
        XmpData xmpData;
663
12
        const DecodeParams dp(max_recursion_depth_);
664
12
        ByteOrder bo = ExifParser::decode(exifData_, payload.c_data(pos), payload.size() - pos, dp);
665
12
        setByteOrder(bo);
666
33
      } else {
667
33
#ifndef SUPPRESS_WARNINGS
668
33
        EXV_WARNING << "Failed to decode Exif metadata." << '\n';
669
33
#endif
670
33
        exifData_.clear();
671
33
      }
672
722
    } else if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_XMP)) {
673
69
      io_->readOrThrow(payload.data(), payload.size(), Exiv2::ErrorCode::kerCorruptedMetadata);
674
69
      xmpPacket_.assign(payload.c_str(), payload.size());
675
69
      const DecodeParams dp(max_recursion_depth_);
676
69
      if (!xmpPacket_.empty() && XmpParser::decode(xmpData_, xmpPacket_, dp)) {
677
69
#ifndef SUPPRESS_WARNINGS
678
69
        EXV_WARNING << "Failed to decode XMP metadata." << '\n';
679
69
#endif
680
69
      } else {
681
#ifdef EXIV2_DEBUG_MESSAGES
682
        std::cout << "Display Hex Dump [size:" << payload.size() << "]" << '\n';
683
        std::cout << binaryToHex(payload.c_data(), payload.size());
684
#endif
685
0
      }
686
653
    } else {
687
653
      io_->seek(size, BasicIo::cur);
688
653
    }
689
690
1.51k
    if (io_->tell() % 2)
691
272
      io_->seek(+1, BasicIo::cur);
692
1.51k
  }
693
165
}
694
695
/* =========================================== */
696
697
180
Image::UniquePtr newWebPInstance(BasicIo::UniquePtr io, const ImageCtorParams& params) {
698
180
  auto image = std::make_unique<WebPImage>(std::move(io), params);
699
180
  if (!image->good()) {
700
0
    return nullptr;
701
0
  }
702
180
  return image;
703
180
}
704
705
24.6k
bool isWebPType(BasicIo& iIo, bool /*advance*/) {
706
24.6k
  if (iIo.size() < 12) {
707
122
    return false;
708
122
  }
709
24.5k
  const int32_t len = 4;
710
24.5k
  constexpr std::array<byte, len> RiffImageId{'R', 'I', 'F', 'F'};
711
24.5k
  constexpr std::array<byte, len> WebPImageId{'W', 'E', 'B', 'P'};
712
24.5k
  std::array<byte, len> webp;
713
24.5k
  std::array<byte, len> data;
714
24.5k
  std::array<byte, len> riff;
715
24.5k
  iIo.readOrThrow(riff.data(), len, Exiv2::ErrorCode::kerCorruptedMetadata);
716
24.5k
  iIo.readOrThrow(data.data(), len, Exiv2::ErrorCode::kerCorruptedMetadata);
717
24.5k
  iIo.readOrThrow(webp.data(), len, Exiv2::ErrorCode::kerCorruptedMetadata);
718
24.5k
  bool matched_riff = riff == RiffImageId;
719
24.5k
  bool matched_webp = webp == WebPImageId;
720
24.5k
  iIo.seek(-12, BasicIo::cur);
721
24.5k
  return matched_riff && matched_webp;
722
24.6k
}
723
724
/*!
725
 @brief Function used to check equality of a Tags with a
726
 particular string (ignores case while comparing).
727
 @param buf Data buffer that will contain Tag to compare
728
 @param str char* Pointer to string
729
 @return Returns true if the buffer value is equal to string.
730
 */
731
15.5k
bool WebPImage::equalsWebPTag(const Exiv2::DataBuf& buf, const char* str) {
732
23.3k
  for (int i = 0; i < 4; i++)
733
22.7k
    if (toupper(buf.read_uint8(i)) != str[i])
734
14.9k
      return false;
735
584
  return true;
736
15.5k
}
737
738
/*!
739
 @brief Function used to add missing EXIF & XMP flags
740
 to the feature section.
741
 @param  iIo get BasicIo pointer to inject data
742
 @param has_xmp Verify if we have xmp data and set required flag
743
 @param has_exif Verify if we have exif data and set required flag
744
 */
745
void WebPImage::inject_VP8X(BasicIo& iIo, bool has_xmp, bool has_exif, bool has_alpha, bool has_icc, uint32_t width,
746
31
                            uint32_t height) const {
747
31
  const byte size[4] = {0x0A, 0x00, 0x00, 0x00};
748
31
  byte data[10] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
749
31
  iIo.write(reinterpret_cast<const byte*>(WEBP_CHUNK_HEADER_VP8X), WEBP_TAG_SIZE);
750
31
  iIo.write(size, WEBP_TAG_SIZE);
751
752
31
  if (has_alpha) {
753
3
    data[0] |= WEBP_VP8X_ALPHA_BIT;
754
3
  }
755
756
31
  if (has_icc) {
757
1
    data[0] |= WEBP_VP8X_ICC_BIT;
758
1
  }
759
760
31
  if (has_xmp) {
761
4
    data[0] |= WEBP_VP8X_XMP_BIT;
762
4
  }
763
764
31
  if (has_exif) {
765
0
    data[0] |= WEBP_VP8X_EXIF_BIT;
766
0
  }
767
768
  /* set width - stored in 24bits*/
769
31
  Internal::enforce(width > 0, Exiv2::ErrorCode::kerCorruptedMetadata);
770
31
  uint32_t w = width - 1;
771
31
  data[4] = w & 0xFF;
772
31
  data[5] = (w >> 8) & 0xFF;
773
31
  data[6] = (w >> 16) & 0xFF;
774
775
  /* set height - stored in 24bits */
776
31
  Internal::enforce(height > 0, Exiv2::ErrorCode::kerCorruptedMetadata);
777
31
  uint32_t h = height - 1;
778
31
  data[7] = h & 0xFF;
779
31
  data[8] = (h >> 8) & 0xFF;
780
31
  data[9] = (h >> 16) & 0xFF;
781
782
31
  iIo.write(data, 10);
783
784
  /* Handle inject an icc profile right after VP8X chunk */
785
31
  if (has_icc) {
786
1
    byte size_buff[WEBP_TAG_SIZE];
787
1
    ul2Data(size_buff, static_cast<uint32_t>(iccProfile_.size()), littleEndian);
788
1
    if (iIo.write(reinterpret_cast<const byte*>(WEBP_CHUNK_HEADER_ICCP), WEBP_TAG_SIZE) != WEBP_TAG_SIZE)
789
0
      throw Error(ErrorCode::kerImageWriteFailed);
790
1
    if (iIo.write(size_buff, WEBP_TAG_SIZE) != WEBP_TAG_SIZE)
791
0
      throw Error(ErrorCode::kerImageWriteFailed);
792
1
    if (iIo.write(iccProfile_.c_data(), iccProfile_.size()) != iccProfile_.size())
793
0
      throw Error(ErrorCode::kerImageWriteFailed);
794
1
    if (iIo.tell() % 2 && iIo.write(&WEBP_PAD_ODD, 1) != 1)
795
0
      throw Error(ErrorCode::kerImageWriteFailed);
796
1
  }
797
31
}
798
799
167
size_t WebPImage::getHeaderOffset(const byte* data, size_t data_size, const byte* header, size_t header_size) {
800
167
  size_t pos = std::string::npos;  // error value
801
167
  if (data_size < header_size) {
802
42
    return pos;
803
42
  }
804
2.88k
  for (size_t i = 0; i < data_size - header_size; i++) {
805
2.77k
    if (memcmp(header, &data[i], header_size) == 0) {
806
12
      pos = i;
807
12
      break;
808
12
    }
809
2.77k
  }
810
125
  return pos;
811
167
}
812
813
}  // namespace Exiv2