xref: /aosp_15_r20/external/skia/src/codec/SkJpegXmp.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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 
8 #include "src/codec/SkJpegXmp.h"
9 
10 #include "include/private/SkGainmapInfo.h"
11 #include "include/utils/SkParse.h"
12 #include "src/codec/SkCodecPriv.h"
13 #include "src/codec/SkJpegConstants.h"
14 #include "src/core/SkMD5.h"
15 #include "src/xml/SkDOM.h"
16 
17 #include <string>
18 #include <tuple>
19 
20 constexpr 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  */
read_xmp_standard(const std::vector<sk_sp<SkData>> & decoderApp1Params)27 static 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  */
read_xmp_extended(const std::vector<sk_sp<SkData>> & decoderApp1Params,const char * guidAscii)56 static 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 
SkJpegMakeXmp(const std::vector<sk_sp<SkData>> & decoderApp1Params)181 std::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 }
207