Coverage Report

Created: 2026-10-28 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/exiv2/src/xmp.cpp
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