1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
media / parsers / webp_parser.cc [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "media/parsers/webp_parser.h"
#include <limits.h>
#include <stddef.h>
#include <string.h>
#include "base/bits.h"
#include "base/check_op.h"
#include "base/numerics/safe_conversions.h"
#include "build/build_config.h"
#include "media/parsers/vp8_parser.h"
#if !defined(ARCH_CPU_LITTLE_ENDIAN)
#error Big-Endian architecture not supported.
#endif
namespace media {
namespace {
// The byte position storing the size of the file.
constexpr size_t kFileSizeBytePosition = 4u;
// The byte position in which the WebP image data begins.
constexpr size_t kWebPFileBeginBytePosition = 8u;
// The byte position storing the size of the VP8 frame.
constexpr size_t kVp8FrameSizePosition = 16u;
// The 12 bytes that include the FourCC "WEBPVP8 " plus the VP8 chunk size info.
constexpr size_t kWebPFileHeaderByteSize = 12u;
// A valid WebP image header and VP8 chunk header require 20 bytes.
// The VP8 Key Frame's payload also begins at byte 20.
constexpr size_t kWebPFileAndVp8ChunkHeaderSizeInBytes = 20u;
// The max WebP file size is (2^32 - 10) per the WebP spec:
// https://developers.google.com/speed/webp/docs/riff_container#webp_file_header
constexpr uint32_t kMaxWebPFileSize = (1ull << 32) - 10u;
constexpr size_t kSizeOfUint32t = sizeof(uint32_t);
} // namespace
bool IsLossyWebPImage(base::span<const uint8_t> encoded_data) {
if (encoded_data.size() < kWebPFileAndVp8ChunkHeaderSizeInBytes)
return false;
DCHECK(encoded_data.data());
return !memcmp(encoded_data.data(), "RIFF", 4) &&
!memcmp(encoded_data.data() + kWebPFileBeginBytePosition, "WEBPVP8 ",
8);
}
std::unique_ptr<Vp8FrameHeader> ParseWebPImage(
base::span<const uint8_t> encoded_data) {
if (!IsLossyWebPImage(encoded_data))
return nullptr;
static_assert(CHAR_BIT == 8, "Size of a char is not 8 bits.");
static_assert(kSizeOfUint32t == 4u, "Size of uint32_t is not 4 bytes.");
// Try to acquire the WebP file size. IsLossyWebPImage() has ensured
// that we have enough data to read the file size.
DCHECK_GE(encoded_data.size(), kFileSizeBytePosition + kSizeOfUint32t);
// No need to worry about endianness because we assert little-endianness.
const uint32_t file_size = *reinterpret_cast<const uint32_t*>(
encoded_data.data() + kFileSizeBytePosition);
// Check that |file_size| is even, per the WebP spec:
// https://developers.google.com/speed/webp/docs/riff_container#webp_file_header
if (file_size % 2 != 0)
return nullptr;
// Check that |file_size| <= 2^32 - 10, per the WebP spec:
// https://developers.google.com/speed/webp/docs/riff_container#webp_file_header
if (file_size > kMaxWebPFileSize)
return nullptr;
// Check that the file size in the header matches the encoded data's size.
if (base::strict_cast<size_t>(file_size) !=
encoded_data.size() - kWebPFileBeginBytePosition) {
return nullptr;
}
// Try to acquire the VP8 key frame size and validate that it fits within the
// encoded data's size.
DCHECK_GE(encoded_data.size(), kVp8FrameSizePosition + kSizeOfUint32t);
const uint32_t vp8_frame_size = *reinterpret_cast<const uint32_t*>(
encoded_data.data() + kVp8FrameSizePosition);
// Check that the VP8 frame size is bounded by the WebP size.
if (base::strict_cast<size_t>(file_size) - kWebPFileHeaderByteSize <
base::strict_cast<size_t>(vp8_frame_size)) {
return nullptr;
}
// Check that the size of the encoded data is consistent.
const size_t vp8_padded_frame_size =
base::bits::AlignUp(size_t{vp8_frame_size}, size_t{2});
if (encoded_data.size() - kWebPFileAndVp8ChunkHeaderSizeInBytes !=
vp8_padded_frame_size) {
return nullptr;
}
// Check that the last byte is 0 if |vp8_frame_size| is odd per WebP specs:
// https://developers.google.com/speed/webp/docs/riff_container#riff_file_format
if (vp8_frame_size % 2 &&
encoded_data.data()[encoded_data.size() - 1] != 0u) {
return nullptr;
}
// Attempt to parse the VP8 frame.
Vp8Parser vp8_parser;
auto result = std::make_unique<Vp8FrameHeader>();
if (vp8_parser.ParseFrame(
encoded_data.data() + kWebPFileAndVp8ChunkHeaderSizeInBytes,
base::strict_cast<size_t>(vp8_frame_size), result.get())) {
return result;
}
return nullptr;
}
} // namespace media