Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | |
3 | | #include "config.h" |
4 | | |
5 | | // included header files |
6 | | #include "error.hpp" |
7 | | #include "exif.hpp" |
8 | | #include "properties.hpp" |
9 | | #include "types.hpp" |
10 | | #include "value.hpp" |
11 | | #include "xmp_exiv2.hpp" |
12 | | |
13 | | // + standard includes |
14 | | #include <algorithm> |
15 | | #include <iostream> |
16 | | #include <mutex> |
17 | | #include <thread> |
18 | | |
19 | | // Adobe XMP Toolkit |
20 | | #ifdef EXV_HAVE_XMP_TOOLKIT |
21 | | #include <expat.h> |
22 | | #include "utils.hpp" |
23 | | #define TXMP_STRING_TYPE std::string |
24 | | #include "xmp_lifecycle.hpp" |
25 | | #ifdef EXV_ADOBE_XMPSDK |
26 | | #include <XMP.hpp> |
27 | | #else |
28 | | #include <XMPSDK.hpp> |
29 | | #endif |
30 | | #include <XMP.incl_cpp> |
31 | | #endif // EXV_HAVE_XMP_TOOLKIT |
32 | | |
33 | | #ifdef EXV_HAVE_XMP_TOOLKIT |
34 | | // This anonymous namespace contains a class named XMLValidator, which uses |
35 | | // libexpat to do a basic validation check on an XML document. This is to |
36 | | // reduce the chance of hitting a bug in the (third-party) xmpsdk |
37 | | // library. For example, it is easy to a trigger a stack overflow in xmpsdk |
38 | | // with a deeply nested tree. |
39 | | namespace { |
40 | | using namespace Exiv2; |
41 | | |
42 | | class XMLValidator { |
43 | | size_t element_depth_ = 0; |
44 | | size_t namespace_depth_ = 0; |
45 | | |
46 | | // These fields are used to record whether an error occurred during |
47 | | // parsing. Why do we need to store the error for later, rather |
48 | | // than throw an exception immediately? Because expat is a C |
49 | | // library, so it isn't designed to be able to handle exceptions |
50 | | // thrown by the callback functions. Throwing exceptions during |
51 | | // parsing is an example of one of the things that xmpsdk does |
52 | | // wrong, leading to problems like https://github.com/Exiv2/exiv2/issues/1821. |
53 | | bool haserror_ = false; |
54 | | std::string errmsg_; |
55 | | XML_Size errlinenum_ = 0; |
56 | | XML_Size errcolnum_ = 0; |
57 | | |
58 | | // Very deeply nested XML trees can cause a stack overflow in |
59 | | // xmpsdk. They are also very unlikely to be valid XMP, so we |
60 | | // error out if the depth exceeds this limit. |
61 | | const size_t max_recursion_depth_; |
62 | | |
63 | | XML_Parser parser_; |
64 | | |
65 | | public: |
66 | | // Runs an XML parser on `buf`. Throws an exception if the XML is invalid. |
67 | 71.6k | static void check(const char* buf, size_t buflen, const DecodeParams& dp) { |
68 | 71.6k | XMLValidator validator(dp.max_recursion_depth()); |
69 | 71.6k | validator.check_internal(buf, buflen); |
70 | 71.6k | } |
71 | | |
72 | | XMLValidator(const XMLValidator&) = delete; |
73 | | XMLValidator& operator=(const XMLValidator&) = delete; |
74 | | |
75 | | private: |
76 | | // Private constructor, because this class is only constructed by |
77 | | // the (static) check method. |
78 | | explicit XMLValidator(size_t max_recursion_depth) : |
79 | 71.6k | max_recursion_depth_(max_recursion_depth), parser_(XML_ParserCreateNS(nullptr, '@')) { |
80 | 71.6k | if (!parser_) { |
81 | 0 | throw Error(ErrorCode::kerXMPToolkitError, "Could not create expat parser"); |
82 | 0 | } |
83 | 71.6k | } |
84 | | |
85 | 71.6k | ~XMLValidator() { |
86 | 71.6k | XML_ParserFree(parser_); |
87 | 71.6k | } |
88 | | |
89 | 104k | void setError(const char* msg) { |
90 | 104k | #if XML_MAJOR_VERSION > 2 || (XML_MAJOR_VERSION == 2 && XML_MINOR_VERSION >= 9) |
91 | | // This is a workaround for XML_GetCurrentLineNumber and XML_GetCurrentColumnNumber |
92 | | // getting deprecated and replaced with 64-bit versions in Expat 2.9.0. Until all |
93 | | // the platforms support the latest version Expat, we need to keep this code which |
94 | | // uses the old versions. |
95 | 104k | const uint64_t errlinenum = XML_GetCurrentLineNumber64(parser_); |
96 | 104k | const uint64_t errcolnum = XML_GetCurrentColumnNumber64(parser_); |
97 | | #else |
98 | | const XML_Size errlinenum = XML_GetCurrentLineNumber(parser_); |
99 | | const XML_Size errcolnum = XML_GetCurrentColumnNumber(parser_); |
100 | | #endif |
101 | | |
102 | 104k | #ifndef SUPPRESS_WARNINGS |
103 | 104k | EXV_INFO << "Invalid XML at line " << errlinenum << ", column " << errcolnum << ": " << msg << "\n"; |
104 | 104k | #endif |
105 | | // If this is the first error, then save it. |
106 | 104k | if (!haserror_) { |
107 | 70.0k | haserror_ = true; |
108 | 70.0k | errmsg_ = msg; |
109 | 70.0k | errlinenum_ = errlinenum; |
110 | 70.0k | errcolnum_ = errcolnum; |
111 | 70.0k | } |
112 | 104k | } |
113 | | |
114 | 71.6k | void check_internal(const char* buf, size_t buflen) { |
115 | 71.6k | if (buflen > static_cast<size_t>(std::numeric_limits<int>::max())) { |
116 | 0 | throw Error(ErrorCode::kerXMPToolkitError, "Buffer length is greater than INT_MAX"); |
117 | 0 | } |
118 | | |
119 | 71.6k | XML_SetUserData(parser_, this); |
120 | 71.6k | XML_SetElementHandler(parser_, startElement_cb, endElement_cb); |
121 | 71.6k | XML_SetNamespaceDeclHandler(parser_, startNamespace_cb, endNamespace_cb); |
122 | 71.6k | XML_SetStartDoctypeDeclHandler(parser_, startDTD_cb); |
123 | | |
124 | 71.6k | if (XML_Parse(parser_, buf, static_cast<int>(buflen), true) == XML_STATUS_ERROR) { |
125 | 70.0k | setError(XML_ErrorString(XML_GetErrorCode(parser_))); |
126 | 70.0k | } |
127 | | |
128 | 71.6k | if (haserror_) { |
129 | 70.0k | throw XMP_Error(kXMPErr_BadXML, "Error in XMLValidator"); |
130 | 70.0k | } |
131 | 71.6k | } |
132 | | |
133 | 190k | void startElement(const XML_Char*, const XML_Char**) noexcept { |
134 | 190k | if (element_depth_ > max_recursion_depth_) { |
135 | 34.5k | setError("Too deeply nested"); |
136 | 34.5k | } |
137 | 190k | ++element_depth_; |
138 | 190k | } |
139 | | |
140 | 104k | void endElement(const XML_Char*) noexcept { |
141 | 104k | if (element_depth_ > 0) { |
142 | 104k | --element_depth_; |
143 | 104k | } else { |
144 | 0 | setError("Negative depth"); |
145 | 0 | } |
146 | 104k | } |
147 | | |
148 | 23.9k | void startNamespace(const XML_Char*, const XML_Char*) noexcept { |
149 | 23.9k | if (namespace_depth_ > max_recursion_depth_) { |
150 | 0 | setError("Too deeply nested"); |
151 | 0 | } |
152 | 23.9k | ++namespace_depth_; |
153 | 23.9k | } |
154 | | |
155 | 17.1k | void endNamespace(const XML_Char*) noexcept { |
156 | 17.1k | if (namespace_depth_ > 0) { |
157 | 17.1k | --namespace_depth_; |
158 | 17.1k | } else { |
159 | 0 | setError("Negative depth"); |
160 | 0 | } |
161 | 17.1k | } |
162 | | |
163 | 364 | void startDTD(const XML_Char*, const XML_Char*, const XML_Char*, int) noexcept { |
164 | | // DOCTYPE is used for XXE attacks. |
165 | 364 | setError("DOCTYPE not supported"); |
166 | 364 | } |
167 | | |
168 | | // This callback function is called by libexpat. It's a static wrapper |
169 | | // around startElement(). |
170 | 190k | static void XMLCALL startElement_cb(void* userData, const XML_Char* name, const XML_Char** attrs) noexcept { |
171 | 190k | static_cast<XMLValidator*>(userData)->startElement(name, attrs); |
172 | 190k | } |
173 | | |
174 | | // This callback function is called by libexpat. It's a static wrapper |
175 | | // around endElement(). |
176 | 104k | static void XMLCALL endElement_cb(void* userData, const XML_Char* name) noexcept { |
177 | 104k | static_cast<XMLValidator*>(userData)->endElement(name); |
178 | 104k | } |
179 | | |
180 | | // This callback function is called by libexpat. It's a static wrapper |
181 | | // around startNamespace(). |
182 | 23.9k | static void XMLCALL startNamespace_cb(void* userData, const XML_Char* prefix, const XML_Char* uri) noexcept { |
183 | 23.9k | static_cast<XMLValidator*>(userData)->startNamespace(prefix, uri); |
184 | 23.9k | } |
185 | | |
186 | | // This callback function is called by libexpat. It's a static wrapper |
187 | | // around endNamespace(). |
188 | 17.1k | static void XMLCALL endNamespace_cb(void* userData, const XML_Char* prefix) noexcept { |
189 | 17.1k | static_cast<XMLValidator*>(userData)->endNamespace(prefix); |
190 | 17.1k | } |
191 | | |
192 | | static void XMLCALL startDTD_cb(void* userData, const XML_Char* doctypeName, const XML_Char* sysid, |
193 | 364 | const XML_Char* pubid, int has_internal_subset) noexcept { |
194 | 364 | static_cast<XMLValidator*>(userData)->startDTD(doctypeName, sysid, pubid, has_internal_subset); |
195 | 364 | } |
196 | | }; |
197 | | } // namespace |
198 | | #endif // EXV_HAVE_XMP_TOOLKIT |
199 | | |
200 | | // ***************************************************************************** |
201 | | // local declarations |
202 | | namespace { |
203 | | //! Unary predicate that matches an Xmpdatum by key |
204 | | class FindXmpdatum { |
205 | | public: |
206 | | //! Constructor, initializes the object with key |
207 | 811k | explicit FindXmpdatum(const Exiv2::XmpKey& key) : key_(key.key()) { |
208 | 811k | } |
209 | | /*! |
210 | | @brief Returns true if prefix and property of the argument |
211 | | Xmpdatum are equal to that of the object. |
212 | | */ |
213 | 6.25M | bool operator()(const Exiv2::Xmpdatum& xmpdatum) const { |
214 | 6.25M | return key_ == xmpdatum.key(); |
215 | 6.25M | } |
216 | | |
217 | | private: |
218 | | std::string key_; |
219 | | |
220 | | }; // class FindXmpdatum |
221 | | |
222 | | #ifdef EXV_HAVE_XMP_TOOLKIT |
223 | | //! Convert XMP Toolkit struct option bit to Value::XmpStruct |
224 | | Exiv2::XmpValue::XmpStruct xmpStruct(XMP_OptionBits opt); |
225 | | |
226 | | //! Convert Value::XmpStruct to XMP Toolkit array option bits |
227 | | XMP_OptionBits xmpArrayOptionBits(Exiv2::XmpValue::XmpStruct xs); |
228 | | |
229 | | //! Convert XMP Toolkit array option bits to array TypeId |
230 | | Exiv2::TypeId arrayValueTypeId(XMP_OptionBits opt); |
231 | | |
232 | | //! Convert XMP Toolkit array option bits to Value::XmpArrayType |
233 | | Exiv2::XmpValue::XmpArrayType xmpArrayType(XMP_OptionBits opt); |
234 | | |
235 | | //! Convert Value::XmpArrayType to XMP Toolkit array option bits |
236 | | XMP_OptionBits xmpArrayOptionBits(Exiv2::XmpValue::XmpArrayType xat); |
237 | | |
238 | | //! Convert XmpFormatFlags to XMP Toolkit format option bits |
239 | | XMP_OptionBits xmpFormatOptionBits(Exiv2::XmpParser::XmpFormatFlags flags); |
240 | | |
241 | | //! Print information about a parsed XMP node |
242 | | void printNode(const std::string& schemaNs, const std::string& propPath, const std::string& propValue, |
243 | | XMP_OptionBits opt); |
244 | | |
245 | | #endif // EXV_HAVE_XMP_TOOLKIT |
246 | | } // namespace |
247 | | |
248 | | // ***************************************************************************** |
249 | | // class member definitions |
250 | | namespace Exiv2 { |
251 | | //! Internal Pimpl structure of class Xmpdatum. |
252 | | struct Xmpdatum::Impl { |
253 | | Impl(const XmpKey& key, const Value* pValue); //!< Constructor |
254 | | Impl(const Impl& rhs); //!< Copy constructor |
255 | | Impl& operator=(const Impl& rhs); //!< Assignment |
256 | 352k | ~Impl() = default; |
257 | | |
258 | | // DATA |
259 | | XmpKey::UniquePtr key_; //!< Key |
260 | | Value::UniquePtr value_; //!< Value |
261 | | }; |
262 | | |
263 | 119k | Xmpdatum::Impl::Impl(const XmpKey& key, const Value* pValue) : key_(key.clone()) { |
264 | 119k | if (pValue) |
265 | 71.6k | value_ = pValue->clone(); |
266 | 119k | } |
267 | | |
268 | 232k | Xmpdatum::Impl::Impl(const Impl& rhs) { |
269 | 232k | if (rhs.key_) |
270 | 232k | key_ = rhs.key_->clone(); // deep copy |
271 | 232k | if (rhs.value_) |
272 | 232k | value_ = rhs.value_->clone(); // deep copy |
273 | 232k | } |
274 | | |
275 | 0 | Xmpdatum::Impl& Xmpdatum::Impl::operator=(const Impl& rhs) { |
276 | 0 | if (this == &rhs) |
277 | 0 | return *this; |
278 | 0 | key_.reset(); |
279 | 0 | if (rhs.key_) |
280 | 0 | key_ = rhs.key_->clone(); // deep copy |
281 | 0 | value_.reset(); |
282 | 0 | if (rhs.value_) |
283 | 0 | value_ = rhs.value_->clone(); // deep copy |
284 | 0 | return *this; |
285 | 0 | } |
286 | | |
287 | 119k | Xmpdatum::Xmpdatum(const XmpKey& key, const Value* pValue) : p_(std::make_unique<Impl>(key, pValue)) { |
288 | 119k | } |
289 | | |
290 | 232k | Xmpdatum::Xmpdatum(const Xmpdatum& rhs) : p_(std::make_unique<Impl>(*rhs.p_)) { |
291 | 232k | } |
292 | | |
293 | 0 | Xmpdatum& Xmpdatum::operator=(const Xmpdatum& rhs) { |
294 | 0 | if (this == &rhs) |
295 | 0 | return *this; |
296 | 0 | *p_ = *rhs.p_; |
297 | 0 | return *this; |
298 | 0 | } |
299 | | |
300 | 352k | Xmpdatum::~Xmpdatum() = default; |
301 | | |
302 | 6.25M | std::string Xmpdatum::key() const { |
303 | 6.25M | return p_->key_ ? p_->key_->key() : ""; |
304 | 6.25M | } |
305 | | |
306 | 0 | const char* Xmpdatum::familyName() const { |
307 | 0 | return p_->key_ ? p_->key_->familyName() : ""; |
308 | 0 | } |
309 | | |
310 | 0 | std::string Xmpdatum::groupName() const { |
311 | 0 | return p_->key_ ? p_->key_->groupName() : ""; |
312 | 0 | } |
313 | | |
314 | 0 | std::string Xmpdatum::tagName() const { |
315 | 0 | return p_->key_ ? p_->key_->tagName() : ""; |
316 | 0 | } |
317 | | |
318 | 0 | std::string Xmpdatum::tagLabel() const { |
319 | 0 | return p_->key_ ? p_->key_->tagLabel() : ""; |
320 | 0 | } |
321 | | |
322 | 0 | std::string Xmpdatum::tagDesc() const { |
323 | 0 | return p_->key_ ? p_->key_->tagDesc() : ""; |
324 | 0 | } |
325 | | |
326 | 0 | uint16_t Xmpdatum::tag() const { |
327 | 0 | return p_->key_ ? p_->key_->tag() : 0; |
328 | 0 | } |
329 | | |
330 | 0 | TypeId Xmpdatum::typeId() const { |
331 | 0 | return p_->value_ ? p_->value_->typeId() : invalidTypeId; |
332 | 0 | } |
333 | | |
334 | 0 | const char* Xmpdatum::typeName() const { |
335 | 0 | return TypeInfo::typeName(typeId()); |
336 | 0 | } |
337 | | |
338 | 0 | size_t Xmpdatum::typeSize() const { |
339 | 0 | return 0; |
340 | 0 | } |
341 | | |
342 | 0 | size_t Xmpdatum::count() const { |
343 | 0 | return p_->value_ ? p_->value_->count() : 0; |
344 | 0 | } |
345 | | |
346 | 0 | size_t Xmpdatum::size() const { |
347 | 0 | return p_->value_ ? p_->value_->size() : 0; |
348 | 0 | } |
349 | | |
350 | 709 | std::string Xmpdatum::toString() const { |
351 | 709 | return p_->value_ ? p_->value_->toString() : ""; |
352 | 709 | } |
353 | | |
354 | 0 | std::string Xmpdatum::toString(size_t n) const { |
355 | 0 | return p_->value_ ? p_->value_->toString(n) : ""; |
356 | 0 | } |
357 | | |
358 | 0 | int64_t Xmpdatum::toInt64(size_t n) const { |
359 | 0 | return p_->value_ ? p_->value_->toInt64(n) : -1; |
360 | 0 | } |
361 | | |
362 | 0 | float Xmpdatum::toFloat(size_t n) const { |
363 | 0 | return p_->value_ ? p_->value_->toFloat(n) : -1; |
364 | 0 | } |
365 | | |
366 | 0 | Rational Xmpdatum::toRational(size_t n) const { |
367 | 0 | return p_->value_ ? p_->value_->toRational(n) : Rational(-1, 1); |
368 | 0 | } |
369 | | |
370 | 0 | Value::UniquePtr Xmpdatum::getValue() const { |
371 | 0 | return p_->value_ ? p_->value_->clone() : nullptr; |
372 | 0 | } |
373 | | |
374 | 0 | const Value& Xmpdatum::value() const { |
375 | 0 | if (!p_->value_) |
376 | 0 | throw Error(ErrorCode::kerValueNotSet, key()); |
377 | 0 | return *p_->value_; |
378 | 0 | } |
379 | | |
380 | 0 | size_t Xmpdatum::copy(byte* /*buf*/, ByteOrder /*byteOrder*/) const { |
381 | 0 | throw Error(ErrorCode::kerFunctionNotSupported, "Xmpdatum::copy"); |
382 | 0 | } |
383 | | |
384 | 0 | std::ostream& Xmpdatum::write(std::ostream& os, const ExifData*) const { |
385 | 0 | return XmpProperties::printProperty(os, key(), value()); |
386 | 0 | } |
387 | | |
388 | 0 | void Xmpdatum::setValue(const Value* pValue) { |
389 | 0 | p_->value_.reset(); |
390 | 0 | if (pValue) |
391 | 0 | p_->value_ = pValue->clone(); |
392 | 0 | } |
393 | | |
394 | 346k | int Xmpdatum::setValue(const std::string& value) { |
395 | 346k | XmpProperties::XmpLock lock; |
396 | 346k | if (!p_->value_) { |
397 | 47.7k | TypeId type = xmpText; |
398 | 47.7k | if (p_->key_) { |
399 | 47.7k | type = XmpProperties::propertyTypeUnlocked(*p_->key_.get(), lock); |
400 | 47.7k | } |
401 | 47.7k | p_->value_ = Value::create(type); |
402 | 47.7k | } |
403 | 346k | return p_->value_->read(value); |
404 | 346k | } |
405 | | |
406 | 347k | Xmpdatum& XmpData::operator[](const std::string& key) { |
407 | 347k | XmpProperties::XmpLock lock; |
408 | 347k | XmpKey xmpKey(key, lock); |
409 | 347k | auto pos = std::find_if(xmpMetadata_.begin(), xmpMetadata_.end(), FindXmpdatum(xmpKey)); |
410 | 347k | if (pos == xmpMetadata_.end()) { |
411 | 47.7k | return xmpMetadata_.emplace_back(xmpKey); |
412 | 47.7k | } |
413 | 299k | return *pos; |
414 | 347k | } |
415 | | |
416 | 2.42k | int XmpData::add(const XmpKey& key, const Value* value) { |
417 | 2.42k | XmpProperties::XmpLock lock; |
418 | 2.42k | return addUnlocked(key, value, lock); |
419 | 2.42k | } |
420 | | |
421 | 71.6k | int XmpData::addUnlocked(const XmpKey& key, const Value* value, const XmpProperties::XmpLock&) { |
422 | 71.6k | xmpMetadata_.emplace_back(key, value); |
423 | 71.6k | return 0; |
424 | 71.6k | } |
425 | | |
426 | 0 | int XmpData::add(const Xmpdatum& xmpDatum) { |
427 | 0 | XmpProperties::XmpLock lock; |
428 | 0 | return addUnlocked(xmpDatum, lock); |
429 | 0 | } |
430 | | |
431 | 0 | int XmpData::addUnlocked(const Xmpdatum& xmpDatum, const XmpProperties::XmpLock&) { |
432 | 0 | xmpMetadata_.push_back(xmpDatum); |
433 | 0 | return 0; |
434 | 0 | } |
435 | | |
436 | 518 | XmpData::const_iterator XmpData::findKey(const XmpKey& key) const { |
437 | 518 | XmpProperties::XmpLock lock; |
438 | 518 | return std::find_if(xmpMetadata_.begin(), xmpMetadata_.end(), FindXmpdatum(key)); |
439 | 518 | } |
440 | | |
441 | 463k | XmpData::iterator XmpData::findKey(const XmpKey& key) { |
442 | 463k | XmpProperties::XmpLock lock; |
443 | 463k | return std::find_if(xmpMetadata_.begin(), xmpMetadata_.end(), FindXmpdatum(key)); |
444 | 463k | } |
445 | | |
446 | 141k | void XmpData::clear() { |
447 | 141k | XmpProperties::XmpLock lock; |
448 | 141k | clearUnlocked(lock); |
449 | 141k | } |
450 | | |
451 | 212k | void XmpData::clearUnlocked(const XmpProperties::XmpLock&) { |
452 | 212k | xmpMetadata_.clear(); |
453 | 212k | nsBindings_.clear(); |
454 | 212k | } |
455 | | |
456 | 0 | void XmpData::sortByKey() { |
457 | 0 | XmpProperties::XmpLock lock; |
458 | 0 | sortByKeyUnlocked(lock); |
459 | 0 | } |
460 | | |
461 | 0 | void XmpData::sortByKeyUnlocked(const XmpProperties::XmpLock&) { |
462 | 0 | std::sort(xmpMetadata_.begin(), xmpMetadata_.end(), cmpMetadataByKey); |
463 | 0 | } |
464 | | |
465 | 0 | XmpData::const_iterator XmpData::begin() const { |
466 | 0 | return xmpMetadata_.begin(); |
467 | 0 | } |
468 | | |
469 | 518 | XmpData::const_iterator XmpData::end() const { |
470 | 518 | return xmpMetadata_.end(); |
471 | 518 | } |
472 | | |
473 | 4.72k | bool XmpData::empty() const { |
474 | 4.72k | XmpProperties::XmpLock lock; |
475 | 4.72k | return emptyUnlocked(lock); |
476 | 4.72k | } |
477 | | |
478 | 8.54k | bool XmpData::emptyUnlocked(const XmpProperties::XmpLock&) const { |
479 | 8.54k | return xmpMetadata_.empty(); |
480 | 8.54k | } |
481 | | |
482 | 0 | long XmpData::count() const { |
483 | 0 | XmpProperties::XmpLock lock; |
484 | 0 | return countUnlocked(lock); |
485 | 0 | } |
486 | | |
487 | 0 | long XmpData::countUnlocked(const XmpProperties::XmpLock&) const { |
488 | 0 | return static_cast<long>(xmpMetadata_.size()); |
489 | 0 | } |
490 | | |
491 | 3.68k | XmpData::iterator XmpData::begin() { |
492 | 3.68k | return xmpMetadata_.begin(); |
493 | 3.68k | } |
494 | | |
495 | 467k | XmpData::iterator XmpData::end() { |
496 | 467k | return xmpMetadata_.end(); |
497 | 467k | } |
498 | | |
499 | 0 | XmpData::iterator XmpData::erase(XmpData::iterator pos) { |
500 | 0 | XmpProperties::XmpLock lock; |
501 | 0 | return xmpMetadata_.erase(pos); |
502 | 0 | } |
503 | | |
504 | 0 | void XmpData::eraseFamily(XmpData::iterator& pos) { |
505 | | // https://github.com/Exiv2/exiv2/issues/521 |
506 | | // delete 'children' of XMP composites (XmpSeq and XmpBag) |
507 | | |
508 | | // I build a StringVector of keys to remove |
509 | | // Then I remove them with erase(....) |
510 | | // erase() has nasty side effects on its argument |
511 | | // The side effects are avoided by the two-step approach |
512 | | // https://github.com/Exiv2/exiv2/issues/560 |
513 | 0 | std::string key(pos->key()); |
514 | 0 | std::vector<std::string> keys; |
515 | 0 | while (pos != xmpMetadata_.end()) { |
516 | 0 | if (!pos->key().starts_with(key)) |
517 | 0 | break; |
518 | 0 | keys.push_back(pos->key()); |
519 | 0 | pos++; |
520 | 0 | } |
521 | | // now erase the family! |
522 | 0 | for (const auto& k : keys) { |
523 | 0 | erase(findKey(Exiv2::XmpKey(k))); |
524 | 0 | } |
525 | 0 | } |
526 | | |
527 | | // We use XmpProperties::getMutex() as the single "Giant Lock" for the entire XMP subsystem. |
528 | | // See src/properties.cpp for the definition. |
529 | | |
530 | | // Lock Hierarchy: |
531 | | // 1. XmpProperties::getMutex() |
532 | | // - Protects XMP Toolkit lifecycle (initialize/terminate) |
533 | | // - Protects XMP Toolkit usage (encode/decode) via serialization |
534 | | // - Protects XMP Namespace Registry (XmpProperties::nsRegistry_) |
535 | | // - Protects XMP SDK internal state (via exclusive access) |
536 | | // |
537 | | // Facade Pattern: |
538 | | // - Public methods (encode, decode, registerNs, etc...) acquire the lock |
539 | | // and call corresponding private static *Impl* methods. |
540 | | // - *Impl* methods assert/assume lock is held and perform the work. |
541 | | // - *Impl* methods can call other *Impl* methods or *Unsafe* methods in XmpProperties |
542 | | // without fear of deadlock or recursive locking issues. |
543 | | |
544 | | // Default locking implementation removed as we use Giant Lock |
545 | | |
546 | | #ifdef EXV_HAVE_XMP_TOOLKIT |
547 | | |
548 | 75.4k | void xmpToolkitEnsureInitialized() { |
549 | 75.4k | static XmpToolkitLifetimeManager instance; |
550 | 75.4k | (void)instance; |
551 | 75.4k | } |
552 | | |
553 | 0 | void XmpParser::registerNsImpl(const std::string& ns, const std::string& prefix) { |
554 | 0 | xmpToolkitEnsureInitialized(); |
555 | 0 | try { |
556 | 0 | std::string existingPrefix; |
557 | 0 | if (SXMPMeta::GetNamespacePrefix(ns.c_str(), &existingPrefix)) { |
558 | 0 | if (!existingPrefix.empty() && existingPrefix.back() == ':') { |
559 | 0 | existingPrefix.pop_back(); |
560 | 0 | } |
561 | 0 | if (existingPrefix == prefix) { |
562 | | // Already registered correctly, skip overhead |
563 | 0 | return; |
564 | 0 | } |
565 | 0 | } |
566 | | |
567 | 0 | SXMPMeta::DeleteNamespace(ns.c_str()); |
568 | | #ifdef EXV_ADOBE_XMPSDK |
569 | | SXMPMeta::RegisterNamespace(ns.c_str(), prefix.c_str(), nullptr); |
570 | | #else |
571 | 0 | SXMPMeta::RegisterNamespace(ns.c_str(), prefix.c_str()); |
572 | 0 | #endif |
573 | 0 | } catch (const XMP_Error& /* e */) { |
574 | | // throw Error(ErrorCode::kerXMPToolkitError, e.GetID(), e.GetErrMsg()); |
575 | 0 | } |
576 | 0 | } |
577 | | #else |
578 | | void XmpParser::registerNsImpl(const std::string& /*ns*/, const std::string& /*prefix*/) { |
579 | | } |
580 | | #endif |
581 | | |
582 | | #ifdef EXV_HAVE_XMP_TOOLKIT |
583 | 0 | static XMP_Status nsDumper(void* refCon, XMP_StringPtr buffer, XMP_StringLen bufferSize) { |
584 | 0 | XMP_Status result = 0; |
585 | 0 | std::string out(buffer, bufferSize); |
586 | | |
587 | | // remove blanks: http://stackoverflow.com/questions/83439/remove-spaces-from-stdstring-in-c |
588 | 0 | std::erase_if(out, [](unsigned char c) { return c < 32 || c > 126; }); |
589 | |
|
590 | 0 | bool bURI = Internal::contains(out, "http://"); |
591 | 0 | bool bNS = Internal::contains(out, ':') && !bURI; |
592 | | |
593 | | // pop trailing ':' on a namespace |
594 | 0 | if (bNS && !out.empty() && out.back() == ':') |
595 | 0 | out.pop_back(); |
596 | |
|
597 | 0 | if (bURI || bNS) { |
598 | 0 | auto p = static_cast<std::map<std::string, std::string>*>(refCon); |
599 | 0 | auto& m = *p; |
600 | |
|
601 | 0 | std::string b; |
602 | 0 | if (bNS) { // store the NS in dict[""] |
603 | 0 | m[b] = std::move(out); |
604 | 0 | } else if (m.contains(b)) { // store dict[uri] = dict[""] |
605 | 0 | m[m[b]] = std::move(out); |
606 | 0 | m.erase(b); |
607 | 0 | } |
608 | 0 | } |
609 | 0 | return result; |
610 | 0 | } |
611 | | #endif |
612 | | |
613 | | #ifdef EXV_HAVE_XMP_TOOLKIT |
614 | 0 | void XmpParser::registeredNamespaces(Exiv2::Dictionary& dict) { |
615 | 0 | try { |
616 | 0 | XmpProperties::XmpLock lock; |
617 | 0 | registeredNamespacesUnlocked(dict, lock); |
618 | 0 | } catch (const XMP_Error& e) { |
619 | 0 | throw Error(ErrorCode::kerXMPToolkitError, e.GetID(), e.GetErrMsg()); |
620 | 0 | } |
621 | 0 | } |
622 | | |
623 | 0 | void XmpParser::registeredNamespacesUnlocked(Exiv2::Dictionary& dict, const XmpProperties::XmpLock&) { |
624 | 0 | xmpToolkitEnsureInitialized(); |
625 | 0 | SXMPMeta::DumpNamespaces(nsDumper, &dict); |
626 | 0 | } |
627 | | #else |
628 | | void XmpParser::registeredNamespaces(Exiv2::Dictionary&) { |
629 | | } |
630 | | |
631 | | void XmpParser::registeredNamespacesUnlocked(Exiv2::Dictionary&, const XmpProperties::XmpLock&) { |
632 | | } |
633 | | #endif |
634 | | |
635 | 0 | void XmpParser::clearCustomNamespaces() { |
636 | 0 | XmpProperties::XmpLock lock; |
637 | 0 | XmpProperties::unregisterNsUnlocked(lock); |
638 | 0 | } |
639 | | |
640 | | #ifdef EXV_HAVE_XMP_TOOLKIT |
641 | 0 | void XmpParser::registerNs(const std::string& ns, const std::string& prefix) { |
642 | 0 | try { |
643 | 0 | XmpProperties::XmpLock lock; |
644 | 0 | registerNsImpl(ns, prefix); |
645 | 0 | } catch (const XMP_Error& /* e */) { |
646 | | // throw Error(ErrorCode::kerXMPToolkitError, e.GetID(), e.GetErrMsg()); |
647 | 0 | } |
648 | 0 | } |
649 | | #else |
650 | | void XmpParser::registerNs(const std::string& /*ns*/, const std::string& /*prefix*/) { |
651 | | } |
652 | | #endif |
653 | | |
654 | 0 | void XmpParser::unregisterNs(const std::string& /*ns*/) { |
655 | 0 | #ifdef EXV_HAVE_XMP_TOOLKIT |
656 | 0 | try { |
657 | | // Throws XMP Toolkit error 8: Unimplemented method XMPMeta::DeleteNamespace |
658 | | // SXMPMeta::DeleteNamespace(ns.c_str()); |
659 | 0 | } catch (const XMP_Error& e) { |
660 | 0 | throw Error(ErrorCode::kerXMPToolkitError, e.GetID(), e.GetErrMsg()); |
661 | 0 | } |
662 | 0 | #endif |
663 | 0 | } // XmpParser::unregisterNs |
664 | | |
665 | 0 | bool XmpParser::initialize(void (*)(void*, bool), void*) { |
666 | 0 | return true; |
667 | 0 | } |
668 | | |
669 | 0 | void XmpParser::terminate() { |
670 | 0 | } |
671 | | |
672 | | #ifdef EXV_HAVE_XMP_TOOLKIT |
673 | 73.4k | int XmpParser::decode(XmpData& xmpData, const std::string& xmpPacket, const DecodeParams& dp) { |
674 | 73.4k | try { |
675 | 73.4k | xmpData.setPacket(xmpPacket); |
676 | 73.4k | if (xmpPacket.empty()) { |
677 | 1.76k | xmpData.clear(); |
678 | 1.76k | return 0; |
679 | 1.76k | } |
680 | | |
681 | | // Acquire Giant Lock |
682 | 71.6k | XmpProperties::XmpLock lock; |
683 | 71.6k | try { |
684 | 71.6k | xmpToolkitEnsureInitialized(); |
685 | 71.6k | } catch (const Error&) { |
686 | 0 | #ifndef SUPPRESS_WARNINGS |
687 | 0 | EXV_ERROR << "XMP toolkit initialization failed.\n"; |
688 | 0 | #endif |
689 | 0 | return 2; |
690 | 0 | } |
691 | | |
692 | 71.6k | xmpData.clearUnlocked(lock); |
693 | | |
694 | | // Make sure the unterminated substring is used |
695 | 71.6k | size_t len = xmpPacket.size(); |
696 | 1.07M | while (len > 0 && 0 == xmpPacket[len - 1]) |
697 | 1.00M | --len; |
698 | | |
699 | 71.6k | XMLValidator::check(xmpPacket.data(), len, dp); |
700 | 71.6k | SXMPMeta meta(xmpPacket.data(), static_cast<XMP_StringLen>(len)); |
701 | 71.6k | SXMPIterator iter(meta); |
702 | 71.6k | std::string schemaNs; |
703 | 71.6k | std::string propPath; |
704 | 71.6k | std::string propValue; |
705 | 71.6k | XMP_OptionBits opt = 0; |
706 | 146k | while (iter.Next(&schemaNs, &propPath, &propValue, &opt)) { |
707 | 74.5k | printNode(schemaNs, propPath, propValue, opt); |
708 | 74.5k | if (XMP_PropIsAlias(opt)) { |
709 | 0 | throw Error(ErrorCode::kerAliasesNotSupported, schemaNs, propPath, propValue); |
710 | 0 | } |
711 | 74.5k | if (XMP_NodeIsSchema(opt)) { |
712 | | // Register unknown namespaces with Exiv2 |
713 | | // (Namespaces are automatically registered with the XMP Toolkit) |
714 | 5.15k | std::string prefix = XmpProperties::prefixUnlocked(schemaNs, lock); |
715 | 5.15k | if (prefix.empty()) { |
716 | 3.30k | if (!SXMPMeta::GetNamespacePrefix(schemaNs.c_str(), &prefix)) |
717 | 0 | throw Error(ErrorCode::kerSchemaNamespaceNotRegistered, schemaNs); |
718 | 3.30k | prefix.pop_back(); |
719 | | // Hardening: a parsed packet must not silently rebind a prefix that is |
720 | | // already globally bound -- a built-in standard prefix, or one a prior |
721 | | // explicit registerNs() established -- to a different URI. Letting it |
722 | | // would poison every later write and lookup in this process (the |
723 | | // namespace-leak bug). The packet's own binding is still captured |
724 | | // per-instance below, so this object round-trips faithfully; we just |
725 | | // don't pollute the global registry on a read. |
726 | 3.30k | if (!XmpProperties::prefixIsBoundUnlocked(prefix, lock)) |
727 | 3 | XmpProperties::registerNsUnlocked(schemaNs, prefix, lock); |
728 | 3.30k | } |
729 | | // Record the prefix->URI binding exactly as declared by THIS packet so |
730 | | // encode() can resolve each property's URI from the data it came from, |
731 | | // rather than from the process-global registry which another image may |
732 | | // have rebound (e.g. to a corrupted URI). See XmpData::nsBindings(). |
733 | 5.15k | xmpData.nsBindings_[prefix] = schemaNs; |
734 | 5.15k | continue; |
735 | 5.15k | } |
736 | 69.3k | auto key = makeXmpKey(schemaNs, propPath, lock); |
737 | 69.3k | if (XMP_ArrayIsAltText(opt)) { |
738 | | // Read Lang Alt property |
739 | 2.47k | auto val = std::make_unique<LangAltValue>(); |
740 | 2.47k | XMP_Index count = meta.CountArrayItems(schemaNs.c_str(), propPath.c_str()); |
741 | 4.94k | while (count-- > 0) { |
742 | | // Get the text |
743 | 2.47k | bool haveNext = iter.Next(&schemaNs, &propPath, &propValue, &opt); |
744 | 2.47k | printNode(schemaNs, propPath, propValue, opt); |
745 | 2.47k | if (!haveNext || !XMP_PropIsSimple(opt) || !XMP_PropHasLang(opt)) { |
746 | 0 | throw Error(ErrorCode::kerDecodeLangAltPropertyFailed, propPath, opt); |
747 | 0 | } |
748 | 2.47k | std::string text = propValue; |
749 | | // Get the language qualifier |
750 | 2.47k | haveNext = iter.Next(&schemaNs, &propPath, &propValue, &opt); |
751 | 2.47k | printNode(schemaNs, propPath, propValue, opt); |
752 | 2.47k | if (!haveNext || !XMP_PropIsSimple(opt) || !XMP_PropIsQualifier(opt) || |
753 | 2.47k | propPath.substr(propPath.size() - 8, 8) != "xml:lang") { |
754 | 0 | throw Error(ErrorCode::kerDecodeLangAltQualifierFailed, propPath, opt); |
755 | 0 | } |
756 | 2.47k | val->value_[propValue] = std::move(text); |
757 | 2.47k | } |
758 | 2.47k | xmpData.addUnlocked(*key, val.get(), lock); |
759 | 2.47k | continue; |
760 | 2.47k | } |
761 | 66.9k | if (XMP_PropIsArray(opt) && !XMP_PropHasQualifiers(opt) && !XMP_ArrayIsAltText(opt)) { |
762 | | // Check if all elements are simple |
763 | 7.03k | bool simpleArray = true; |
764 | 7.03k | SXMPIterator aIter(meta, schemaNs.c_str(), propPath.c_str()); |
765 | 7.03k | std::string aSchemaNs; |
766 | 7.03k | std::string aPropPath; |
767 | 7.03k | std::string aPropValue; |
768 | 7.03k | XMP_OptionBits aOpt = 0; |
769 | 33.0k | while (aIter.Next(&aSchemaNs, &aPropPath, &aPropValue, &aOpt)) { |
770 | 26.6k | if (propPath == aPropPath) |
771 | 7.03k | continue; |
772 | 19.6k | if (!XMP_PropIsSimple(aOpt) || XMP_PropHasQualifiers(aOpt) || XMP_PropIsQualifier(aOpt) || |
773 | 19.6k | XMP_NodeIsSchema(aOpt) || XMP_PropIsAlias(aOpt)) { |
774 | 595 | simpleArray = false; |
775 | 595 | break; |
776 | 595 | } |
777 | 19.6k | } |
778 | 7.03k | if (simpleArray) { |
779 | | // Read the array into an XmpArrayValue |
780 | 6.44k | auto val = std::make_unique<XmpArrayValue>(arrayValueTypeId(opt)); |
781 | 6.44k | XMP_Index count = meta.CountArrayItems(schemaNs.c_str(), propPath.c_str()); |
782 | 25.4k | while (count-- > 0) { |
783 | 19.0k | iter.Next(&schemaNs, &propPath, &propValue, &opt); |
784 | 19.0k | printNode(schemaNs, propPath, propValue, opt); |
785 | 19.0k | val->read(propValue); |
786 | 19.0k | } |
787 | 6.44k | xmpData.addUnlocked(*key, val.get(), lock); |
788 | 6.44k | continue; |
789 | 6.44k | } |
790 | 7.03k | } |
791 | | |
792 | 60.4k | auto val = std::make_unique<XmpTextValue>(); |
793 | 60.4k | if (XMP_PropIsStruct(opt) || XMP_PropIsArray(opt)) { |
794 | | // Create a metadatum with only XMP options |
795 | 4.07k | val->setXmpArrayType(xmpArrayType(opt)); |
796 | 4.07k | val->setXmpStruct(xmpStruct(opt)); |
797 | 4.07k | xmpData.addUnlocked(*key, val.get(), lock); |
798 | 4.07k | continue; |
799 | 4.07k | } |
800 | 56.3k | if (XMP_PropIsSimple(opt) || XMP_PropIsQualifier(opt)) { |
801 | 56.3k | val->read(propValue); |
802 | 56.3k | xmpData.addUnlocked(*key, val.get(), lock); |
803 | 56.3k | continue; |
804 | 56.3k | } |
805 | | // Don't let any node go by unnoticed |
806 | 0 | throw Error(ErrorCode::kerUnhandledXmpNode, key->key(), opt); |
807 | 56.3k | } // iterate through all XMP nodes |
808 | | |
809 | 71.6k | return 0; |
810 | 71.6k | } |
811 | 73.4k | #ifndef SUPPRESS_WARNINGS |
812 | 73.4k | catch (const Error& e) { |
813 | 133 | if (e.code() == ErrorCode::kerXMPToolkitError) { |
814 | | // Initialization failures caught above, this handles other XMP errors if any wrapped in Error |
815 | 0 | } |
816 | 133 | throw; |
817 | 70.6k | } catch (const XMP_Error& e) { |
818 | 70.6k | EXV_ERROR << Error(ErrorCode::kerXMPToolkitError, e.GetID(), e.GetErrMsg()) << "\n"; |
819 | 70.6k | xmpData.clear(); |
820 | 70.6k | return 3; |
821 | 70.6k | } |
822 | | #else |
823 | | catch (const XMP_Error&) { |
824 | | xmpData.clear(); |
825 | | return 3; |
826 | | } |
827 | | #endif // SUPPRESS_WARNINGS |
828 | 73.4k | } // XmpParser::decode |
829 | | #else |
830 | | int XmpParser::decode(XmpData& xmpData, const std::string& xmpPacket, const DecodeParams&) { |
831 | | xmpData.clear(); |
832 | | if (!xmpPacket.empty()) { |
833 | | #ifndef SUPPRESS_WARNINGS |
834 | | EXV_WARNING << "XMP toolkit support not compiled in.\n"; |
835 | | #endif |
836 | | } |
837 | | return 1; |
838 | | } // XmpParser::decode |
839 | | #endif // !EXV_HAVE_XMP_TOOLKIT |
840 | | |
841 | | #ifdef EXV_HAVE_XMP_TOOLKIT |
842 | 3.82k | int XmpParser::encode(std::string& xmpPacket, const XmpData& xmpData, uint16_t formatFlags, uint32_t padding) { |
843 | 3.82k | try { |
844 | | // Acquire Giant Lock |
845 | 3.82k | XmpProperties::XmpLock lock; |
846 | 3.82k | try { |
847 | 3.82k | xmpToolkitEnsureInitialized(); |
848 | 3.82k | } catch (const Error&) { |
849 | 0 | #ifndef SUPPRESS_WARNINGS |
850 | 0 | EXV_ERROR << "XMP toolkit initialization failed.\n"; |
851 | 0 | #endif |
852 | 0 | return 2; |
853 | 0 | } |
854 | | |
855 | 3.82k | if (xmpData.emptyUnlocked(lock)) { |
856 | 3.82k | xmpPacket.clear(); |
857 | 3.82k | return 0; |
858 | 3.82k | } // We are holding the Giant Lock, so we can iterate nsRegistry_ safely. |
859 | | // XmpProperties interactions must go through Unlocked/impl methods to avoid deadlocks. |
860 | 0 | for (const auto& [xmp, uri] : XmpProperties::nsRegistry_) { |
861 | | #ifdef EXIV2_DEBUG_MESSAGES |
862 | | std::cerr << "Registering " << uri.prefix_ << " : " << xmp << "\n"; |
863 | | #endif |
864 | | // registerNsImpl is safe since we hold the lock |
865 | 0 | registerNsImpl(xmp, uri.prefix_); |
866 | 0 | } |
867 | | |
868 | | // Note: we deliberately do NOT re-register the per-instance bindings with |
869 | | // the toolkit here. Every URI they contain was already registered while its |
870 | | // packet was parsed in decode() (custom ones are also in nsRegistry_, handled |
871 | | // above), so SetProperty resolves them. Re-registering would overwrite the |
872 | | // toolkit's URI->prefix mapping whenever our preferred prefix differs from the |
873 | | // one the file used for that URI (e.g. iptc vs Iptc4xmpCore), which breaks |
874 | | // serialization of struct fields that still carry the file's prefix. |
875 | 0 | SXMPMeta meta; |
876 | 0 | for (const auto& xmp : xmpData) { |
877 | 0 | const std::string ns = resolveNamespace(xmpData, xmp.groupName(), lock); |
878 | 0 | XMP_OptionBits options = 0; |
879 | |
|
880 | 0 | if (xmp.typeId() == langAlt) { |
881 | | // Encode Lang Alt property |
882 | 0 | const auto la = dynamic_cast<const LangAltValue*>(&xmp.value()); |
883 | 0 | if (!la) |
884 | 0 | throw Error(ErrorCode::kerEncodeLangAltPropertyFailed, xmp.key()); |
885 | | |
886 | 0 | int idx = 1; |
887 | 0 | for (const auto& [lang, specs] : la->value_) { |
888 | 0 | if (!specs.empty()) { // remove lang specs with no value |
889 | 0 | printNode(ns, xmp.tagName(), specs, 0); |
890 | 0 | meta.AppendArrayItem(ns.c_str(), xmp.tagName().c_str(), kXMP_PropArrayIsAlternate, specs.c_str()); |
891 | 0 | const std::string item = xmp.tagName() + "[" + toString(idx++) + "]"; |
892 | 0 | meta.SetQualifier(ns.c_str(), item.c_str(), kXMP_NS_XML, "lang", lang.c_str()); |
893 | 0 | } |
894 | 0 | } |
895 | 0 | continue; |
896 | 0 | } |
897 | | |
898 | | // Todo: Xmpdatum should have an XmpValue, not a Value |
899 | 0 | const auto val = dynamic_cast<const XmpValue*>(&xmp.value()); |
900 | 0 | if (!val) |
901 | 0 | throw Error(ErrorCode::kerInvalidKeyXmpValue, xmp.key(), xmp.typeName()); |
902 | 0 | options = xmpArrayOptionBits(val->xmpArrayType()) | xmpArrayOptionBits(val->xmpStruct()); |
903 | 0 | if (xmp.typeId() == xmpBag || xmp.typeId() == xmpSeq || xmp.typeId() == xmpAlt) { |
904 | 0 | printNode(ns, xmp.tagName(), "", options); |
905 | 0 | meta.SetProperty(ns.c_str(), xmp.tagName().c_str(), nullptr, options); |
906 | 0 | for (size_t idx = 0; idx < xmp.count(); ++idx) { |
907 | 0 | const std::string item = xmp.tagName() + "[" + toString(idx + 1) + "]"; |
908 | 0 | printNode(ns, item, xmp.toString(static_cast<long>(idx)), 0); |
909 | 0 | meta.SetProperty(ns.c_str(), item.c_str(), xmp.toString(static_cast<long>(idx)).c_str()); |
910 | 0 | } |
911 | 0 | continue; |
912 | 0 | } |
913 | 0 | if (xmp.typeId() == xmpText) { |
914 | 0 | const auto xt = dynamic_cast<const XmpTextValue*>(&xmp.value()); |
915 | 0 | if (xmp.count() == 0 || xt->xmpStruct() != XmpValue::xsNone || xt->xmpArrayType() != XmpValue::xaNone) { |
916 | 0 | printNode(ns, xmp.tagName(), "", options); |
917 | 0 | meta.SetProperty(ns.c_str(), xmp.tagName().c_str(), nullptr, options); |
918 | 0 | } else { |
919 | 0 | printNode(ns, xmp.tagName(), xmp.toString(0), options); |
920 | 0 | meta.SetProperty(ns.c_str(), xmp.tagName().c_str(), xmp.toString(0).c_str(), options); |
921 | 0 | } |
922 | 0 | continue; |
923 | 0 | } |
924 | | // Don't let any Xmpdatum go by unnoticed |
925 | 0 | throw Error(ErrorCode::kerUnhandledXmpdatum, xmp.tagName(), xmp.typeName()); |
926 | 0 | } |
927 | 0 | std::string tmpPacket; |
928 | 0 | meta.SerializeToBuffer(&tmpPacket, xmpFormatOptionBits(static_cast<XmpFormatFlags>(formatFlags)), |
929 | 0 | padding); // throws |
930 | 0 | xmpPacket = std::move(tmpPacket); |
931 | |
|
932 | 0 | return 0; |
933 | 0 | } |
934 | 3.82k | #ifndef SUPPRESS_WARNINGS |
935 | 3.82k | catch (const Error& /* e */) { |
936 | 0 | throw; |
937 | 0 | } catch (const XMP_Error& e) { |
938 | 0 | EXV_ERROR << Error(ErrorCode::kerXMPToolkitError, e.GetID(), e.GetErrMsg()) << "\n"; |
939 | 0 | return 3; |
940 | 0 | } |
941 | | #else |
942 | | catch (const XMP_Error&) { |
943 | | return 3; |
944 | | } |
945 | | #endif // SUPPRESS_WARNINGS |
946 | 3.82k | } // XmpParser::encode |
947 | | #else |
948 | | int XmpParser::encode(std::string& /*xmpPacket*/, const XmpData& xmpData, uint16_t /*formatFlags*/, |
949 | | uint32_t /*padding*/) { |
950 | | if (!xmpData.empty()) { |
951 | | #ifndef SUPPRESS_WARNINGS |
952 | | EXV_WARNING << "XMP toolkit support not compiled in.\n"; |
953 | | #endif |
954 | | } |
955 | | return 1; |
956 | | } // XmpParser::encode |
957 | | #endif // !EXV_HAVE_XMP_TOOLKIT |
958 | | |
959 | | } // namespace Exiv2 |
960 | | |
961 | | // ***************************************************************************** |
962 | | // local definitions |
963 | | #ifdef EXV_HAVE_XMP_TOOLKIT |
964 | | namespace { |
965 | 4.07k | Exiv2::XmpValue::XmpStruct xmpStruct(XMP_OptionBits opt) { |
966 | 4.07k | Exiv2::XmpValue::XmpStruct var(Exiv2::XmpValue::xsNone); |
967 | 4.07k | if (XMP_PropIsStruct(opt)) { |
968 | 3.48k | var = Exiv2::XmpValue::xsStruct; |
969 | 3.48k | } |
970 | 4.07k | return var; |
971 | 4.07k | } |
972 | | |
973 | 0 | XMP_OptionBits xmpArrayOptionBits(Exiv2::XmpValue::XmpStruct xs) { |
974 | 0 | XMP_OptionBits var(0); |
975 | 0 | switch (xs) { |
976 | 0 | case Exiv2::XmpValue::xsNone: |
977 | 0 | break; |
978 | 0 | case Exiv2::XmpValue::xsStruct: |
979 | 0 | XMP_SetOption(var, kXMP_PropValueIsStruct); |
980 | 0 | break; |
981 | 0 | } |
982 | 0 | return var; |
983 | 0 | } |
984 | | |
985 | 10.5k | Exiv2::TypeId arrayValueTypeId(XMP_OptionBits opt) { |
986 | 10.5k | Exiv2::TypeId typeId(Exiv2::invalidTypeId); |
987 | 10.5k | if (XMP_PropIsArray(opt)) { |
988 | 7.03k | if (XMP_ArrayIsAlternate(opt)) |
989 | 0 | typeId = Exiv2::xmpAlt; |
990 | 7.03k | else if (XMP_ArrayIsOrdered(opt)) |
991 | 5.91k | typeId = Exiv2::xmpSeq; |
992 | 1.12k | else if (XMP_ArrayIsUnordered(opt)) |
993 | 1.12k | typeId = Exiv2::xmpBag; |
994 | 7.03k | } |
995 | 10.5k | return typeId; |
996 | 10.5k | } |
997 | | |
998 | 4.07k | Exiv2::XmpValue::XmpArrayType xmpArrayType(XMP_OptionBits opt) { |
999 | 4.07k | return Exiv2::XmpValue::xmpArrayType(arrayValueTypeId(opt)); |
1000 | 4.07k | } |
1001 | | |
1002 | 0 | XMP_OptionBits xmpArrayOptionBits(Exiv2::XmpValue::XmpArrayType xat) { |
1003 | 0 | XMP_OptionBits var(0); |
1004 | 0 | switch (xat) { |
1005 | 0 | case Exiv2::XmpValue::xaNone: |
1006 | 0 | break; |
1007 | 0 | case Exiv2::XmpValue::xaAlt: |
1008 | 0 | XMP_SetOption(var, kXMP_PropValueIsArray); |
1009 | 0 | XMP_SetOption(var, kXMP_PropArrayIsAlternate); |
1010 | 0 | break; |
1011 | 0 | case Exiv2::XmpValue::xaSeq: |
1012 | 0 | XMP_SetOption(var, kXMP_PropValueIsArray); |
1013 | 0 | XMP_SetOption(var, kXMP_PropArrayIsOrdered); |
1014 | 0 | break; |
1015 | 0 | case Exiv2::XmpValue::xaBag: |
1016 | 0 | XMP_SetOption(var, kXMP_PropValueIsArray); |
1017 | 0 | break; |
1018 | 0 | } |
1019 | 0 | return var; |
1020 | 0 | } |
1021 | | |
1022 | | #ifdef EXV_ADOBE_XMPSDK |
1023 | | #define kXMP_WriteAliasComments 0x0400UL |
1024 | | #endif |
1025 | | |
1026 | 0 | XMP_OptionBits xmpFormatOptionBits(Exiv2::XmpParser::XmpFormatFlags flags) { |
1027 | 0 | XMP_OptionBits var(0); |
1028 | 0 | if (flags & Exiv2::XmpParser::omitPacketWrapper) |
1029 | 0 | var |= kXMP_OmitPacketWrapper; |
1030 | 0 | if (flags & Exiv2::XmpParser::readOnlyPacket) |
1031 | 0 | var |= kXMP_ReadOnlyPacket; |
1032 | 0 | if (flags & Exiv2::XmpParser::useCompactFormat) |
1033 | 0 | var |= kXMP_UseCompactFormat; |
1034 | 0 | if (flags & Exiv2::XmpParser::includeThumbnailPad) |
1035 | 0 | var |= kXMP_IncludeThumbnailPad; |
1036 | 0 | if (flags & Exiv2::XmpParser::exactPacketLength) |
1037 | 0 | var |= kXMP_ExactPacketLength; |
1038 | 0 | if (flags & Exiv2::XmpParser::writeAliasComments) |
1039 | 0 | var |= kXMP_WriteAliasComments; |
1040 | 0 | if (flags & Exiv2::XmpParser::omitAllFormatting) |
1041 | 0 | var |= kXMP_OmitAllFormatting; |
1042 | 0 | return var; |
1043 | 0 | } |
1044 | | |
1045 | | #ifdef EXIV2_DEBUG_MESSAGES |
1046 | | void printNode(const std::string& schemaNs, const std::string& propPath, const std::string& propValue, |
1047 | | XMP_OptionBits opt) { |
1048 | | static std::once_flag flag; |
1049 | | std::call_once(flag, []() { |
1050 | | std::cout << "ashisabsals\n" |
1051 | | << "lcqqtrgqlai\n"; |
1052 | | }); |
1053 | | enum { |
1054 | | alia = 0, |
1055 | | sche, |
1056 | | hasq, |
1057 | | isqu, |
1058 | | stru, |
1059 | | arra, |
1060 | | abag, |
1061 | | aseq, |
1062 | | aalt, |
1063 | | lang, |
1064 | | simp, |
1065 | | len, |
1066 | | }; |
1067 | | |
1068 | | std::string opts(len, '.'); |
1069 | | if (XMP_PropIsAlias(opt)) |
1070 | | opts[alia] = 'X'; |
1071 | | if (XMP_NodeIsSchema(opt)) |
1072 | | opts[sche] = 'X'; |
1073 | | if (XMP_PropHasQualifiers(opt)) |
1074 | | opts[hasq] = 'X'; |
1075 | | if (XMP_PropIsQualifier(opt)) |
1076 | | opts[isqu] = 'X'; |
1077 | | if (XMP_PropIsStruct(opt)) |
1078 | | opts[stru] = 'X'; |
1079 | | if (XMP_PropIsArray(opt)) |
1080 | | opts[arra] = 'X'; |
1081 | | if (XMP_ArrayIsUnordered(opt)) |
1082 | | opts[abag] = 'X'; |
1083 | | if (XMP_ArrayIsOrdered(opt)) |
1084 | | opts[aseq] = 'X'; |
1085 | | if (XMP_ArrayIsAlternate(opt)) |
1086 | | opts[aalt] = 'X'; |
1087 | | if (XMP_ArrayIsAltText(opt)) |
1088 | | opts[lang] = 'X'; |
1089 | | if (XMP_PropIsSimple(opt)) |
1090 | | opts[simp] = 'X'; |
1091 | | |
1092 | | std::cout << opts << " "; |
1093 | | if (opts[sche] == 'X') { |
1094 | | std::cout << "ns=" << schemaNs; |
1095 | | } else { |
1096 | | std::cout << propPath << " = " << propValue; |
1097 | | } |
1098 | | std::cout << '\n'; |
1099 | | } |
1100 | | #else |
1101 | 98.4k | void printNode(const std::string&, const std::string&, const std::string&, XMP_OptionBits) { |
1102 | 98.4k | } |
1103 | | #endif // EXIV2_DEBUG_MESSAGES |
1104 | | |
1105 | | } // namespace |
1106 | | |
1107 | | Exiv2::XmpKey::UniquePtr Exiv2::XmpParser::makeXmpKey(const std::string& schemaNs, const std::string& propPath, |
1108 | 69.3k | const XmpProperties::XmpLock& lock) { |
1109 | 69.3k | std::string property; |
1110 | 69.3k | std::string::size_type idx = propPath.find(':'); |
1111 | 69.3k | if (idx == std::string::npos) { |
1112 | 0 | throw Exiv2::Error(Exiv2::ErrorCode::kerPropertyNameIdentificationFailed, propPath, schemaNs); |
1113 | 0 | } |
1114 | | // Don't worry about out_of_range, XMP parser takes care of this |
1115 | 69.3k | property = propPath.substr(idx + 1); |
1116 | 69.3k | std::string prefix = Exiv2::XmpProperties::prefixUnlocked(schemaNs, lock); |
1117 | 69.3k | if (prefix.empty()) { |
1118 | | // The namespace was intentionally not registered globally (e.g. a packet |
1119 | | // that rebinds an already-bound prefix to a different URI -- see the |
1120 | | // hardening in decode()). Fall back to the prefix the toolkit parsed so |
1121 | | // the property still gets a key; its URI is preserved per-instance. |
1122 | 51.9k | std::string toolkitPrefix; |
1123 | 51.9k | if (SXMPMeta::GetNamespacePrefix(schemaNs.c_str(), &toolkitPrefix)) { |
1124 | 51.9k | toolkitPrefix.pop_back(); |
1125 | 51.9k | prefix = std::move(toolkitPrefix); |
1126 | 51.9k | } |
1127 | 51.9k | } |
1128 | 69.3k | if (prefix.empty()) { |
1129 | 0 | throw Exiv2::Error(Exiv2::ErrorCode::kerNoPrefixForNamespace, propPath, schemaNs); |
1130 | 0 | } |
1131 | 69.3k | return Exiv2::XmpKey::UniquePtr(new Exiv2::XmpKey(prefix, property, lock)); |
1132 | 69.3k | } // makeXmpKey |
1133 | | |
1134 | | std::string Exiv2::XmpParser::resolveNamespace(const XmpData& xmpData, const std::string& prefix, |
1135 | 0 | const XmpProperties::XmpLock& lock) { |
1136 | 0 | const auto& nsBindings = xmpData.nsBindings(); |
1137 | 0 | const auto binding = nsBindings.find(prefix); |
1138 | 0 | return binding != nsBindings.end() ? binding->second : XmpProperties::nsUnlocked(prefix, lock); |
1139 | 0 | } // resolveNamespace |
1140 | | #endif // EXV_HAVE_XMP_TOOLKIT |