Coverage Report

Created: 2026-06-10 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/exiv2/src/iptc.cpp
Line
Count
Source
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
3
// included header files
4
#include "iptc.hpp"
5
#include "config.h"
6
#include "datasets.hpp"
7
#include "enforce.hpp"
8
#include "error.hpp"
9
#include "image_int.hpp"
10
#include "types.hpp"
11
#include "value.hpp"
12
13
// + standard includes
14
#include <algorithm>
15
#include <iostream>
16
17
// *****************************************************************************
18
namespace {
19
/*!
20
  @brief Read a single dataset payload and create a new metadata entry.
21
22
  @param iptcData IPTC metadata container to add the dataset to
23
  @param dataSet  DataSet number
24
  @param record   Record Id
25
  @param data     Pointer to the first byte of dataset payload
26
  @param sizeData Length in bytes of dataset payload
27
  @return 0 if successful.
28
 */
29
int readData(Exiv2::IptcData& iptcData, uint16_t dataSet, uint16_t record, const Exiv2::byte* data, uint32_t sizeData);
30
31
//! Unary predicate that matches an Iptcdatum with given record and dataset
32
class FindIptcdatum {
33
 public:
34
  //! Constructor, initializes the object with the record and dataset id
35
70.3k
  FindIptcdatum(uint16_t dataset, uint16_t record) : dataset_(dataset), record_(record) {
36
70.3k
  }
37
  /*!
38
    @brief Returns true if the record and dataset id of the argument
39
          Iptcdatum is equal to that of the object.
40
  */
41
1.27M
  bool operator()(const Exiv2::Iptcdatum& iptcdatum) const {
42
1.27M
    return dataset_ == iptcdatum.tag() && record_ == iptcdatum.record();
43
1.27M
  }
44
45
 private:
46
  // DATA
47
  uint16_t dataset_;
48
  uint16_t record_;
49
50
};  // class FindIptcdatum
51
}  // namespace
52
53
// *****************************************************************************
54
// class member definitions
55
namespace Exiv2 {
56
86.6k
Iptcdatum::Iptcdatum(const IptcKey& key, const Value* pValue) : key_(key.clone()) {
57
86.6k
  if (pValue)
58
29.2k
    value_ = pValue->clone();
59
86.6k
}
60
61
326k
Iptcdatum::Iptcdatum(const Iptcdatum& rhs) {
62
326k
  if (rhs.key_)
63
326k
    key_ = rhs.key_->clone();  // deep copy
64
326k
  if (rhs.value_)
65
326k
    value_ = rhs.value_->clone();  // deep copy
66
326k
}
67
68
413k
Iptcdatum::~Iptcdatum() = default;
69
70
0
size_t Iptcdatum::copy(byte* buf, ByteOrder byteOrder) const {
71
0
  return value_ ? value_->copy(buf, byteOrder) : 0;
72
0
}
73
74
0
std::ostream& Iptcdatum::write(std::ostream& os, const ExifData*) const {
75
0
  return os << value();
76
0
}
77
78
107k
std::string Iptcdatum::key() const {
79
107k
  return key_ ? key_->key() : "";
80
107k
}
81
82
0
std::string Iptcdatum::recordName() const {
83
0
  return key_ ? key_->recordName() : "";
84
0
}
85
86
556k
uint16_t Iptcdatum::record() const {
87
556k
  return key_ ? key_->record() : 0;
88
556k
}
89
90
0
const char* Iptcdatum::familyName() const {
91
0
  return key_ ? key_->familyName() : "";
92
0
}
93
94
0
std::string Iptcdatum::groupName() const {
95
0
  return key_ ? key_->groupName() : "";
96
0
}
97
98
0
std::string Iptcdatum::tagName() const {
99
0
  return key_ ? key_->tagName() : "";
100
0
}
101
102
0
std::string Iptcdatum::tagLabel() const {
103
0
  return key_ ? key_->tagLabel() : "";
104
0
}
105
106
0
std::string Iptcdatum::tagDesc() const {
107
0
  return key_ ? key_->tagDesc() : "";
108
0
}
109
110
1.47M
uint16_t Iptcdatum::tag() const {
111
1.47M
  return key_ ? key_->tag() : 0;
112
1.47M
}
113
114
0
TypeId Iptcdatum::typeId() const {
115
0
  return value_ ? value_->typeId() : invalidTypeId;
116
0
}
117
118
0
const char* Iptcdatum::typeName() const {
119
0
  return TypeInfo::typeName(typeId());
120
0
}
121
122
0
size_t Iptcdatum::typeSize() const {
123
0
  return TypeInfo::typeSize(typeId());
124
0
}
125
126
0
size_t Iptcdatum::count() const {
127
0
  return value_ ? value_->count() : 0;
128
0
}
129
130
75.3k
size_t Iptcdatum::size() const {
131
75.3k
  return value_ ? value_->size() : 0;
132
75.3k
}
133
134
55.5k
std::string Iptcdatum::toString() const {
135
55.5k
  return value_ ? value_->toString() : "";
136
55.5k
}
137
138
0
std::string Iptcdatum::toString(size_t n) const {
139
0
  return value_ ? value_->toString(n) : "";
140
0
}
141
142
0
int64_t Iptcdatum::toInt64(size_t n) const {
143
0
  return value_ ? value_->toInt64(n) : -1;
144
0
}
145
146
0
float Iptcdatum::toFloat(size_t n) const {
147
0
  return value_ ? value_->toFloat(n) : -1;
148
0
}
149
150
0
Rational Iptcdatum::toRational(size_t n) const {
151
0
  return value_ ? value_->toRational(n) : Rational(-1, 1);
152
0
}
153
154
0
Value::UniquePtr Iptcdatum::getValue() const {
155
0
  return value_ ? value_->clone() : nullptr;
156
0
}
157
158
93.2k
const Value& Iptcdatum::value() const {
159
93.2k
  if (!value_)
160
0
    throw Error(ErrorCode::kerValueNotSet, key());
161
93.2k
  return *value_;
162
93.2k
}
163
164
114k
Iptcdatum& Iptcdatum::operator=(const Iptcdatum& rhs) {
165
114k
  if (this == &rhs)
166
0
    return *this;
167
168
114k
  key_.reset();
169
114k
  if (rhs.key_)
170
114k
    key_ = rhs.key_->clone();  // deep copy
171
172
114k
  value_.reset();
173
114k
  if (rhs.value_)
174
114k
    value_ = rhs.value_->clone();  // deep copy
175
176
114k
  return *this;
177
114k
}  // Iptcdatum::operator=
178
179
0
Iptcdatum& Iptcdatum::operator=(const uint16_t& value) {
180
0
  auto v = std::make_unique<UShortValue>();
181
0
  v->value_.push_back(value);
182
0
  value_ = std::move(v);
183
0
  return *this;
184
0
}
185
186
1.64k
Iptcdatum& Iptcdatum::operator=(const std::string& value) {
187
1.64k
  setValue(value);
188
1.64k
  return *this;
189
1.64k
}
190
191
0
Iptcdatum& Iptcdatum::operator=(const Value& value) {
192
0
  setValue(&value);
193
0
  return *this;
194
0
}
195
196
0
void Iptcdatum::setValue(const Value* pValue) {
197
0
  value_.reset();
198
0
  if (pValue)
199
0
    value_ = pValue->clone();
200
0
}
201
202
57.5k
int Iptcdatum::setValue(const std::string& value) {
203
57.5k
  if (!value_) {
204
57.3k
    TypeId type = IptcDataSets::dataSetType(tag(), record());
205
57.3k
    value_ = Value::create(type);
206
57.3k
  }
207
57.5k
  return value_->read(value);
208
57.5k
}
209
210
1.64k
Iptcdatum& IptcData::operator[](const std::string& key) {
211
1.64k
  IptcKey iptcKey(key);
212
1.64k
  auto pos = findKey(iptcKey);
213
1.64k
  if (pos == end()) {
214
1.38k
    return iptcMetadata_.emplace_back(iptcKey);
215
1.38k
  }
216
253
  return *pos;
217
1.64k
}
218
219
1.36k
size_t IptcData::size() const {
220
1.36k
  size_t newSize = 0;
221
37.6k
  for (auto&& iptc : iptcMetadata_) {
222
    // marker, record Id, dataset num, first 2 bytes of size
223
37.6k
    newSize += 5;
224
37.6k
    size_t dataSize = iptc.size();
225
37.6k
    newSize += dataSize;
226
37.6k
    if (dataSize > 32767) {
227
      // extended dataset (we always use 4 bytes)
228
0
      newSize += 4;
229
0
    }
230
37.6k
  }
231
1.36k
  return newSize;
232
1.36k
}
233
234
29.2k
int IptcData::add(const IptcKey& key, const Value* value) {
235
29.2k
  return add(Iptcdatum(key, value));
236
29.2k
}
237
238
85.2k
int IptcData::add(const Iptcdatum& iptcDatum) {
239
85.2k
  if (!IptcDataSets::dataSetRepeatable(iptcDatum.tag(), iptcDatum.record()) &&
240
16.8k
      findId(iptcDatum.tag(), iptcDatum.record()) != end()) {
241
13.1k
    return 6;
242
13.1k
  }
243
  // allow duplicates
244
72.0k
  iptcMetadata_.push_back(iptcDatum);
245
72.0k
  return 0;
246
85.2k
}
247
248
2.42k
IptcData::const_iterator IptcData::findKey(const IptcKey& key) const {
249
2.42k
  return std::find_if(iptcMetadata_.begin(), iptcMetadata_.end(), FindIptcdatum(key.tag(), key.record()));
250
2.42k
}
251
252
51.0k
IptcData::iterator IptcData::findKey(const IptcKey& key) {
253
51.0k
  return std::find_if(iptcMetadata_.begin(), iptcMetadata_.end(), FindIptcdatum(key.tag(), key.record()));
254
51.0k
}
255
256
0
IptcData::const_iterator IptcData::findId(uint16_t dataset, uint16_t record) const {
257
0
  return std::find_if(iptcMetadata_.begin(), iptcMetadata_.end(), FindIptcdatum(dataset, record));
258
0
}
259
260
16.8k
IptcData::iterator IptcData::findId(uint16_t dataset, uint16_t record) {
261
16.8k
  return std::find_if(iptcMetadata_.begin(), iptcMetadata_.end(), FindIptcdatum(dataset, record));
262
16.8k
}
263
264
/// \todo not used internally. At least we should test it
265
0
void IptcData::sortByKey() {
266
0
  std::sort(iptcMetadata_.begin(), iptcMetadata_.end(), cmpMetadataByKey);
267
0
}
268
269
/// \todo not used internally. At least we should test it
270
0
void IptcData::sortByTag() {
271
0
  std::sort(iptcMetadata_.begin(), iptcMetadata_.end(), cmpMetadataByTag);
272
0
}
273
274
0
IptcData::iterator IptcData::erase(IptcData::iterator pos) {
275
0
  return iptcMetadata_.erase(pos);
276
0
}
277
278
0
void IptcData::printStructure(std::ostream& out, const Slice<byte*>& bytes, size_t depth) {
279
0
  if (bytes.size() < 3) {
280
0
    return;
281
0
  }
282
0
  size_t i = 0;
283
0
  while (i < bytes.size() - 3 && bytes.at(i) != 0x1c)
284
0
    i++;
285
0
  out << Internal::indent(++depth) << "Record | DataSet | Name                     | Length | Data" << '\n';
286
0
  while (i < bytes.size() - 3) {
287
0
    if (bytes.at(i) != 0x1c) {
288
0
      break;
289
0
    }
290
0
    uint16_t record = bytes.at(i + 1);
291
0
    uint16_t dataset = bytes.at(i + 2);
292
0
    Internal::enforce(bytes.size() - i >= 5, ErrorCode::kerCorruptedMetadata);
293
0
    uint16_t len = getUShort(bytes.subSlice(i + 3, bytes.size()), bigEndian);
294
295
0
    Internal::enforce(bytes.size() - i >= 5 + static_cast<size_t>(len), ErrorCode::kerCorruptedMetadata);
296
0
    out << stringFormat("  {:6} | {:7} | {:<24} | {:6} | ", record, dataset,
297
0
                        Exiv2::IptcDataSets::dataSetName(dataset, record), len);
298
0
    out << Internal::binaryToString(makeSlice(bytes, i + 5, i + 5 + std::min<uint16_t>(40, len)))
299
0
        << (len > 40 ? "...\n" : "\n");
300
0
    i += 5 + len;
301
0
  }
302
0
}
303
304
2.42k
const char* IptcData::detectCharset() const {
305
2.42k
  auto pos = findKey(IptcKey("Iptc.Envelope.CharacterSet"));
306
2.42k
  if (pos != end()) {
307
676
    const std::string value = pos->toString();
308
676
    if (pos->value().ok() && value == "\033%G")
309
676
      return "UTF-8";
310
676
  }
311
312
1.75k
  bool ascii = true;
313
1.75k
  bool utf8 = true;
314
315
1.75k
  for (const auto& key : *this) {
316
0
    std::string value = key.toString();
317
0
    if (key.value().ok()) {
318
0
      int seqCount = 0;
319
0
      for (auto c : value) {
320
0
        if (seqCount) {
321
0
          if ((c & 0xc0) != 0x80) {
322
0
            utf8 = false;
323
0
            break;
324
0
          }
325
0
          --seqCount;
326
0
        } else {
327
0
          if (c & 0x80)
328
0
            ascii = false;
329
0
          else
330
0
            continue;  // ascii character
331
332
0
          if ((c & 0xe0) == 0xc0)
333
0
            seqCount = 1;
334
0
          else if ((c & 0xf0) == 0xe0)
335
0
            seqCount = 2;
336
0
          else if ((c & 0xf8) == 0xf0)
337
0
            seqCount = 3;
338
0
          else if ((c & 0xfc) == 0xf8)
339
0
            seqCount = 4;
340
0
          else if ((c & 0xfe) == 0xfc)
341
0
            seqCount = 5;
342
0
          else {
343
0
            utf8 = false;
344
0
            break;
345
0
          }
346
0
        }
347
0
      }
348
0
      if (seqCount)
349
0
        utf8 = false;  // unterminated seq
350
0
      if (!utf8)
351
0
        break;
352
0
    }
353
0
  }
354
355
1.75k
  if (ascii)
356
1.75k
    return "ASCII";
357
0
  if (utf8)
358
0
    return "UTF-8";
359
0
  return nullptr;
360
0
}
361
362
2.53k
int IptcParser::decode(IptcData& iptcData, const byte* pData, size_t size) {
363
#ifdef EXIV2_DEBUG_MESSAGES
364
  std::cerr << "IptcParser::decode, size = " << size << "\n";
365
#endif
366
2.53k
  auto pRead = pData;
367
2.53k
  const auto pEnd = pData + size;
368
2.53k
  iptcData.clear();
369
370
2.53k
  uint16_t record = 0;
371
2.53k
  uint16_t dataSet = 0;
372
2.53k
  uint32_t sizeData = 0;
373
2.53k
  byte extTest = 0;
374
375
987k
  while (6 <= static_cast<size_t>(pEnd - pRead)) {
376
    // First byte should be a marker. If it isn't, scan forward and skip
377
    // the chunk bytes present in some images. This deviates from the
378
    // standard, which advises to treat such cases as errors.
379
986k
    if (*pRead++ != marker_)
380
956k
      continue;
381
30.0k
    record = *pRead++;
382
30.0k
    dataSet = *pRead++;
383
384
30.0k
    extTest = *pRead;
385
30.0k
    if (extTest & 0x80) {
386
      // extended dataset
387
894
      uint16_t sizeOfSize = (getUShort(pRead, bigEndian) & 0x7FFF);
388
894
      if (sizeOfSize > 4)
389
182
        return 5;
390
712
      pRead += 2;
391
712
      if (sizeOfSize > pEnd - pRead)
392
6
        return 6;
393
706
      sizeData = 0;
394
1.29k
      for (; sizeOfSize > 0; --sizeOfSize) {
395
593
        sizeData |= *pRead++ << (8 * (sizeOfSize - 1));
396
593
      }
397
29.1k
    } else {
398
      // standard dataset
399
29.1k
      sizeData = getUShort(pRead, bigEndian);
400
29.1k
      pRead += 2;
401
29.1k
    }
402
29.9k
    if (sizeData <= static_cast<size_t>(pEnd - pRead)) {
403
29.3k
      int rc = readData(iptcData, dataSet, record, pRead, sizeData);
404
29.3k
      if (rc != 0) {
405
0
#ifndef SUPPRESS_WARNINGS
406
0
        EXV_WARNING << "Failed to read IPTC dataset " << IptcKey(dataSet, record) << " (rc = " << rc << "); skipped.\n";
407
0
#endif
408
0
      }
409
29.3k
    } else {
410
573
#ifndef SUPPRESS_WARNINGS
411
573
      EXV_WARNING << "IPTC dataset " << IptcKey(dataSet, record) << " has invalid size " << sizeData << "; skipped.\n";
412
573
#endif
413
573
      return 7;
414
573
    }
415
29.3k
    pRead += sizeData;
416
29.3k
  }
417
418
1.77k
  return 0;
419
2.53k
}  // IptcParser::decode
420
421
22.6k
DataBuf IptcParser::encode(const IptcData& iptcData) {
422
22.6k
  DataBuf buf;
423
22.6k
  if (iptcData.empty())
424
21.2k
    return buf;
425
426
1.36k
  buf = DataBuf(iptcData.size());
427
1.36k
  byte* pWrite = buf.data();
428
429
  // Copy the iptc data sets and sort them by record but preserve the order of datasets
430
1.36k
  IptcMetadata sortedIptcData(iptcData.begin(), iptcData.end());
431
1.36k
  std::stable_sort(sortedIptcData.begin(), sortedIptcData.end(),
432
170k
                   [](const auto& l, const auto& r) { return l.record() < r.record(); });
433
434
37.6k
  for (const auto& iter : sortedIptcData) {
435
    // marker, record Id, dataset num
436
37.6k
    *pWrite++ = marker_;
437
37.6k
    *pWrite++ = static_cast<byte>(iter.record());
438
37.6k
    *pWrite++ = static_cast<byte>(iter.tag());
439
440
    // extended or standard dataset?
441
37.6k
    if (size_t dataSize = iter.size(); dataSize > 32767) {
442
      // always use 4 bytes for extended length
443
0
      uint16_t sizeOfSize = 4 | 0x8000;
444
0
      us2Data(pWrite, sizeOfSize, bigEndian);
445
0
      pWrite += 2;
446
0
      ul2Data(pWrite, static_cast<uint32_t>(dataSize), bigEndian);
447
0
      pWrite += 4;
448
37.6k
    } else {
449
37.6k
      us2Data(pWrite, static_cast<uint16_t>(dataSize), bigEndian);
450
37.6k
      pWrite += 2;
451
37.6k
    }
452
37.6k
    pWrite += iter.value().copy(pWrite, bigEndian);
453
37.6k
  }
454
455
1.36k
  return buf;
456
22.6k
}  // IptcParser::encode
457
458
}  // namespace Exiv2
459
460
// *****************************************************************************
461
// local definitions
462
namespace {
463
29.3k
int readData(Exiv2::IptcData& iptcData, uint16_t dataSet, uint16_t record, const Exiv2::byte* data, uint32_t sizeData) {
464
29.3k
  Exiv2::TypeId type = Exiv2::IptcDataSets::dataSetType(dataSet, record);
465
29.3k
  auto value = Exiv2::Value::create(type);
466
29.3k
  int rc = value->read(data, sizeData, Exiv2::bigEndian);
467
29.3k
  if (0 == rc) {
468
18.7k
    Exiv2::IptcKey key(dataSet, record);
469
18.7k
    iptcData.add(key, value.get());
470
18.7k
  } else if (1 == rc) {
471
    // If the first attempt failed, try with a string value
472
10.5k
    value = Exiv2::Value::create(Exiv2::string);
473
10.5k
    rc = value->read(data, sizeData, Exiv2::bigEndian);
474
10.5k
    if (0 == rc) {
475
10.5k
      Exiv2::IptcKey key(dataSet, record);
476
10.5k
      iptcData.add(key, value.get());
477
10.5k
    }
478
10.5k
  }
479
29.3k
  return rc;
480
29.3k
}
481
482
}  // namespace