Coverage Report

Created: 2025-11-11 06:55

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