Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
SkJpegXmp.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2023 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
9
14#include "src/core/SkMD5.h"
15#include "src/xml/SkDOM.h"
16
17#include <string>
18#include <tuple>
19
20constexpr size_t kGuidAsciiSize = 32;
21
22/*
23 * Extract standard XMP metadata. The decoderApp1Params must outlive the returned SkData.
24 *
25 * See XMP Specification Part 3: Storage in files, Section 1.1.3: JPEG.
26 */
27static sk_sp<SkData> read_xmp_standard(const std::vector<sk_sp<SkData>>& decoderApp1Params) {
28 constexpr size_t kSigSize = sizeof(kXMPStandardSig);
29 // Iterate through the image's segments.
30 for (const auto& params : decoderApp1Params) {
31 // Skip segments that don't have the right marker, signature, or are too small.
32 if (params->size() <= kSigSize) {
33 continue;
34 }
35 if (memcmp(params->bytes(), kXMPStandardSig, kSigSize) != 0) {
36 continue;
37 }
38 return SkData::MakeWithoutCopy(params->bytes() + kSigSize, params->size() - kSigSize);
39 }
40 return nullptr;
41}
42
43/*
44 * Extract and validate extended XMP metadata.
45 *
46 * See XMP Specification Part 3: Storage in files, Section 1.1.3.1: Extended XMP in JPEG:
47 * Each chunk is written into the JPEG file within a separate APP1 marker segment. Each ExtendedXMP
48 * marker segment contains:
49 * - A null-terminated signature string
50 * - A 128-bit GUID stored as a 32-byte ASCII hex string, capital A-F, no null termination. The
51 * GUID is a 128-bit MD5 digest of the full ExtendedXMP serialization.
52 * - The full length of the ExtendedXMP serialization as a 32-bit unsigned integer.
53 * - The offset of this portion as a 32-bit unsigned integer.
54 * - The portion of the ExtendedXMP
55 */
56static sk_sp<SkData> read_xmp_extended(const std::vector<sk_sp<SkData>>& decoderApp1Params,
57 const char* guidAscii) {
58 constexpr size_t kSigSize = sizeof(kXMPExtendedSig);
59 constexpr size_t kFullLengthSize = 4;
60 constexpr size_t kOffsetSize = 4;
61 constexpr size_t kHeaderSize = kSigSize + kGuidAsciiSize + kFullLengthSize + kOffsetSize;
62
63 // Validate the provided ASCII guid.
64 if (strlen(guidAscii) != kGuidAsciiSize) {
65 SkCodecPrintf("Invalid ASCII GUID size.\n");
66 return nullptr;
67 }
68 SkMD5::Digest guidAsDigest;
69 for (size_t i = 0; i < kGuidAsciiSize; ++i) {
70 uint8_t digit = 0;
71 if (guidAscii[i] >= '0' && guidAscii[i] <= '9') {
72 digit = guidAscii[i] - '0';
73 } else if (guidAscii[i] >= 'A' && guidAscii[i] <= 'F') {
74 digit = guidAscii[i] - 'A' + 10;
75 } else {
76 SkCodecPrintf("GUID is not upper-case hex.\n");
77 return nullptr;
78 }
79 if (i % 2 == 0) {
80 guidAsDigest.data[i / 2] = 16 * digit;
81 } else {
82 guidAsDigest.data[i / 2] += digit;
83 }
84 }
85
86 // Iterate through the image's segments.
87 uint32_t fullLength = 0;
88 using Part = std::tuple<uint32_t, sk_sp<SkData>>;
89 std::vector<Part> parts;
90 for (const auto& params : decoderApp1Params) {
91 // Skip segments that don't have the right marker, signature, or are too small.
92 if (params->size() <= kHeaderSize) {
93 continue;
94 }
95 if (memcmp(params->bytes(), kXMPExtendedSig, kSigSize) != 0) {
96 continue;
97 }
98
99 // Ignore parts that do not match the expected GUID.
100 const uint8_t* partGuidAscii = params->bytes() + kSigSize;
101 if (memcmp(guidAscii, partGuidAscii, kGuidAsciiSize) != 0) {
102 SkCodecPrintf("Ignoring unexpected GUID.\n");
103 continue;
104 }
105
106 // Read the full length and the offset for this part.
107 uint32_t partFullLength = 0;
108 uint32_t partOffset = 0;
109 const uint8_t* partFullLengthBytes = params->bytes() + kSigSize + kGuidAsciiSize;
110 const uint8_t* partOffsetBytes =
111 params->bytes() + kSigSize + kGuidAsciiSize + kFullLengthSize;
112 for (size_t i = 0; i < 4; ++i) {
113 partFullLength *= 256;
114 partOffset *= 256;
115 partFullLength += partFullLengthBytes[i];
116 partOffset += partOffsetBytes[i];
117 }
118
119 // If this is the first part, set our global full length size.
120 if (parts.empty()) {
121 fullLength = partFullLength;
122 }
123
124 // Ensure all parts agree on the full length.
125 if (partFullLength != fullLength) {
126 SkCodecPrintf("Multiple parts had different total lengths.\n");
127 return nullptr;
128 }
129
130 // Add it to the list.
131 auto partData = SkData::MakeWithoutCopy(params->bytes() + kHeaderSize,
132 params->size() - kHeaderSize);
133 parts.push_back({partOffset, partData});
134 }
135 if (parts.empty() || fullLength == 0) {
136 return nullptr;
137 }
138
139 // Sort the list of parts by offset.
140 std::sort(parts.begin(), parts.end(), [](const Part& a, const Part& b) {
141 return std::get<0>(a) < std::get<0>(b);
142 });
143
144 // Stitch the parts together. Fail if we find that they are not contiguous.
145 auto xmpExtendedData = SkData::MakeUninitialized(fullLength);
146 uint8_t* xmpExtendedBase = reinterpret_cast<uint8_t*>(xmpExtendedData->writable_data());
147 uint8_t* xmpExtendedCurrent = xmpExtendedBase;
148 SkMD5 md5;
149 for (const auto& part : parts) {
150 uint32_t currentOffset = static_cast<uint32_t>(xmpExtendedCurrent - xmpExtendedBase);
151 uint32_t partOffset = std::get<0>(part);
152 const sk_sp<SkData>& partData = std::get<1>(part);
153 // Make sure the data is contiguous and doesn't overflow the buffer.
154 if (partOffset != currentOffset) {
155 SkCodecPrintf("XMP extension parts not contiguous\n");
156 return nullptr;
157 }
158 if (partData->size() > fullLength - currentOffset) {
159 SkCodecPrintf("XMP extension parts overflow\n");
160 return nullptr;
161 }
162 memcpy(xmpExtendedCurrent, partData->data(), partData->size());
163 xmpExtendedCurrent += partData->size();
164 }
165 // Make sure we wrote the full buffer.
166 if (static_cast<uint32_t>(xmpExtendedCurrent - xmpExtendedBase) != fullLength) {
167 SkCodecPrintf("XMP extension did not match full length.\n");
168 return nullptr;
169 }
170
171 // Make sure the MD5 hash of the extended data matched the GUID.
172 md5.write(xmpExtendedData->data(), xmpExtendedData->size());
173 if (md5.finish() != guidAsDigest) {
174 SkCodecPrintf("XMP extension did not hash to GUID.\n");
175 return nullptr;
176 }
177
178 return xmpExtendedData;
179}
180
181std::unique_ptr<SkXmp> SkJpegMakeXmp(const std::vector<sk_sp<SkData>>& decoderApp1Params) {
182 auto xmpStandard = read_xmp_standard(decoderApp1Params);
183 if (!xmpStandard) {
184 return nullptr;
185 }
186
187 std::unique_ptr<SkXmp> xmp = SkXmp::Make(xmpStandard);
188 if (!xmp) {
189 return nullptr;
190 }
191
192 // Extract the GUID (the MD5 hash) of the extended metadata.
193 const char* extendedGuid = xmp->getExtendedXmpGuid();
194 if (!extendedGuid) {
195 return xmp;
196 }
197
198 // Extract and validate the extended metadata from the JPEG structure.
199 auto xmpExtended = read_xmp_extended(decoderApp1Params, extendedGuid);
200 if (!xmpExtended) {
201 SkCodecPrintf("Extended XMP was indicated but failed to read or validate.\n");
202 return xmp;
203 }
204
205 return SkXmp::Make(xmpStandard, xmpExtended);
206}
static SkMD5::Digest md5(const SkBitmap &bm)
Definition CodecTest.cpp:77
#define SkCodecPrintf(...)
Definition SkCodecPriv.h:23
static constexpr size_t kHeaderSize
static constexpr uint8_t kXMPStandardSig[]
static constexpr uint8_t kXMPExtendedSig[]
static sk_sp< SkData > read_xmp_standard(const std::vector< sk_sp< SkData > > &decoderApp1Params)
Definition SkJpegXmp.cpp:27
constexpr size_t kGuidAsciiSize
Definition SkJpegXmp.cpp:20
static sk_sp< SkData > read_xmp_extended(const std::vector< sk_sp< SkData > > &decoderApp1Params, const char *guidAscii)
Definition SkJpegXmp.cpp:56
std::unique_ptr< SkXmp > SkJpegMakeXmp(const std::vector< sk_sp< SkData > > &decoderApp1Params)
static sk_sp< SkData > MakeWithoutCopy(const void *data, size_t length)
Definition SkData.h:116
static sk_sp< SkData > MakeUninitialized(size_t length)
Definition SkData.cpp:116
Definition SkMD5.h:19
static std::unique_ptr< SkXmp > Make(sk_sp< SkData > xmpData)
Definition SkXmp.cpp:644
const EmbeddedViewParams * params
static bool b
struct MyStruct a[10]
uint8_t data[16]
Definition SkMD5.h:39