Coverage Report

Created: 2026-02-26 07:48

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