Coverage Report

Created: 2026-03-21 06:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/exiv2/src/value.cpp
Line
Count
Source
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
3
// included header files
4
#include "value.hpp"
5
#include "config.h"
6
#include "convert.hpp"
7
#include "enforce.hpp"
8
#include "error.hpp"
9
#include "image_int.hpp"
10
#include "types.hpp"
11
12
// + standard includes
13
#include <iterator>
14
#include <sstream>
15
16
// *****************************************************************************
17
// class member definitions
18
namespace Exiv2 {
19
70.8M
Value::Value(TypeId typeId) : type_(typeId) {
20
70.8M
}
21
22
28.8M
Value::UniquePtr Value::create(TypeId typeId) {
23
28.8M
  switch (typeId) {
24
0
    case invalidTypeId:
25
11.1k
    case signedByte:
26
3.82M
    case unsignedByte:
27
3.82M
      return std::make_unique<DataValue>(typeId);
28
143k
    case asciiString:
29
143k
      return std::make_unique<AsciiValue>();
30
17.0M
    case unsignedShort:
31
17.0M
      return std::make_unique<ValueType<uint16_t>>();
32
262k
    case unsignedLong:
33
297k
    case tiffIfd:
34
297k
      return std::make_unique<ValueType<uint32_t>>(typeId);
35
63.1k
    case unsignedRational:
36
63.1k
      return std::make_unique<ValueType<URational>>();
37
152k
    case undefined:
38
152k
      return std::make_unique<DataValue>();
39
2.98M
    case signedShort:
40
2.98M
      return std::make_unique<ValueType<int16_t>>();
41
269k
    case signedLong:
42
269k
      return std::make_unique<ValueType<int32_t>>();
43
45.7k
    case signedRational:
44
45.7k
      return std::make_unique<ValueType<Rational>>();
45
17.5k
    case tiffFloat:
46
17.5k
      return std::make_unique<ValueType<float>>();
47
21.1k
    case tiffDouble:
48
21.1k
      return std::make_unique<ValueType<double>>();
49
128k
    case string:
50
128k
      return std::make_unique<StringValue>();
51
8.44k
    case date:
52
8.44k
      return std::make_unique<DateValue>();
53
15.4k
    case time:
54
15.4k
      return std::make_unique<TimeValue>();
55
7.11k
    case comment:
56
7.11k
      return std::make_unique<CommentValue>();
57
98.8k
    case xmpText:
58
98.8k
      return std::make_unique<XmpTextValue>();
59
0
    case xmpBag:
60
19.8k
    case xmpSeq:
61
19.8k
    case xmpAlt:
62
19.8k
      return std::make_unique<XmpArrayValue>(typeId);
63
0
    case langAlt:
64
0
      return std::make_unique<LangAltValue>();
65
3.71M
    default:
66
3.71M
      return std::make_unique<DataValue>(typeId);
67
28.8M
  }
68
28.8M
}  // Value::create
69
70
5.97k
int Value::setDataArea(const byte* /*buf*/, size_t /*len*/) {
71
5.97k
  return -1;
72
5.97k
}
73
74
373k
std::string Value::toString() const {
75
373k
  std::ostringstream os;
76
373k
  write(os);
77
373k
  ok_ = !os.fail();
78
373k
  return os.str();
79
373k
}
80
81
167k
std::string Value::toString(size_t /*n*/) const {
82
167k
  return toString();
83
167k
}
84
85
103k
size_t Value::sizeDataArea() const {
86
103k
  return 0;
87
103k
}
88
89
4.37k
DataBuf Value::dataArea() const {
90
4.37k
  return {nullptr, 0};
91
4.37k
}
92
93
7.68M
DataValue::DataValue(TypeId typeId) : Value(typeId) {
94
7.68M
}
95
96
17
DataValue::DataValue(const byte* buf, size_t len, ByteOrder byteOrder, TypeId typeId) : Value(typeId) {
97
17
  read(buf, len, byteOrder);
98
17
}
99
100
16.3M
size_t DataValue::count() const {
101
16.3M
  return size();
102
16.3M
}
103
104
7.68M
int DataValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) {
105
  // byteOrder not needed
106
7.68M
  value_.assign(buf, buf + len);
107
7.68M
  return 0;
108
7.68M
}
109
110
0
int DataValue::read(const std::string& buf) {
111
0
  std::istringstream is(buf);
112
0
  int tmp = 0;
113
0
  ValueType val;
114
0
  while (is >> tmp)
115
0
    val.push_back(tmp);
116
0
  if (!is.eof())
117
0
    return 1;
118
0
  value_ = std::move(val);
119
0
  return 0;
120
0
}
121
122
5.72M
size_t DataValue::copy(byte* buf, ByteOrder /*byteOrder*/) const {
123
  // byteOrder not needed
124
5.72M
  return std::copy(value_.begin(), value_.end(), buf) - buf;
125
5.72M
}
126
127
36.2M
size_t DataValue::size() const {
128
36.2M
  return value_.size();
129
36.2M
}
130
131
25.4M
DataValue* DataValue::clone_() const {
132
25.4M
  return new DataValue(*this);
133
25.4M
}
134
135
115k
std::ostream& DataValue::write(std::ostream& os) const {
136
115k
  if (!value_.empty()) {
137
113k
    std::copy(value_.begin(), value_.end() - 1, std::ostream_iterator<int>(os, " "));
138
113k
    os << static_cast<int>(value_.back());
139
113k
  }
140
115k
  return os;
141
115k
}
142
143
1.91k
std::string DataValue::toString(size_t n) const {
144
1.91k
  ok_ = true;
145
1.91k
  return std::to_string(value_.at(n));
146
1.91k
}
147
148
396k
int64_t DataValue::toInt64(size_t n) const {
149
396k
  ok_ = true;
150
396k
  return value_.at(n);
151
396k
}
152
153
227k
uint32_t DataValue::toUint32(size_t n) const {
154
227k
  ok_ = true;
155
227k
  return value_.at(n);
156
227k
}
157
158
1.77k
float DataValue::toFloat(size_t n) const {
159
1.77k
  ok_ = true;
160
1.77k
  return value_.at(n);
161
1.77k
}
162
163
2.24k
Rational DataValue::toRational(size_t n) const {
164
2.24k
  ok_ = true;
165
2.24k
  return {value_.at(n), 1};
166
2.24k
}
167
168
0
StringValueBase::StringValueBase(TypeId typeId, const std::string& buf) : Value(typeId) {
169
0
  read(buf);
170
0
}
171
172
52.7k
int StringValueBase::read(const std::string& buf) {
173
52.7k
  value_ = buf;
174
52.7k
  return 0;
175
52.7k
}
176
177
210k
int StringValueBase::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) {
178
  // byteOrder not needed
179
210k
  if (buf)
180
210k
    value_ = std::string(reinterpret_cast<const char*>(buf), len);
181
210k
  return 0;
182
210k
}
183
184
152k
size_t StringValueBase::copy(byte* buf, ByteOrder /*byteOrder*/) const {
185
152k
  if (value_.empty())
186
76.2k
    return 0;
187
  // byteOrder not needed
188
76.1k
  return value_.copy(reinterpret_cast<char*>(buf), value_.size());
189
152k
}
190
191
430k
size_t StringValueBase::count() const {
192
430k
  return size();
193
430k
}
194
195
837k
size_t StringValueBase::size() const {
196
837k
  return value_.size();
197
837k
}
198
199
52.9k
std::ostream& StringValueBase::write(std::ostream& os) const {
200
52.9k
  return os << value_;
201
52.9k
}
202
203
114k
int64_t StringValueBase::toInt64(size_t n) const {
204
114k
  ok_ = true;
205
114k
  return value_.at(n);
206
114k
}
207
208
70.4k
uint32_t StringValueBase::toUint32(size_t n) const {
209
70.4k
  ok_ = true;
210
70.4k
  return value_.at(n);
211
70.4k
}
212
213
301
float StringValueBase::toFloat(size_t n) const {
214
301
  ok_ = true;
215
301
  return value_.at(n);
216
301
}
217
218
346
Rational StringValueBase::toRational(size_t n) const {
219
346
  ok_ = true;
220
346
  return {value_.at(n), 1};
221
346
}
222
223
128k
StringValue::StringValue() : StringValueBase(string) {
224
128k
}
225
226
0
StringValue::StringValue(const std::string& buf) : StringValueBase(string, buf) {
227
0
}
228
229
947k
StringValue* StringValue::clone_() const {
230
947k
  return new StringValue(*this);
231
947k
}
232
233
143k
AsciiValue::AsciiValue() : StringValueBase(asciiString) {
234
143k
}
235
236
0
AsciiValue::AsciiValue(const std::string& buf) : StringValueBase(asciiString, buf) {
237
0
}
238
239
32.8k
int AsciiValue::read(const std::string& buf) {
240
32.8k
  value_ = buf;
241
  // ensure count>0 and nul terminated # https://github.com/Exiv2/exiv2/issues/1484
242
32.8k
  if (value_.empty() || value_.back() != '\0') {
243
32.8k
    value_ += '\0';
244
32.8k
  }
245
32.8k
  return 0;
246
32.8k
}
247
248
385k
AsciiValue* AsciiValue::clone_() const {
249
385k
  return new AsciiValue(*this);
250
385k
}
251
252
171k
std::ostream& AsciiValue::write(std::ostream& os) const {
253
  // Write only up to the first '\0' (if any)
254
171k
  std::string::size_type pos = value_.find_first_of('\0');
255
171k
  if (pos == std::string::npos)
256
15.0k
    pos = value_.size();
257
171k
  return os << value_.substr(0, pos);
258
171k
}
259
260
//! Lookup list of supported IFD type information
261
constexpr CommentValue::CharsetTable CommentValue::CharsetInfo::charsetTable_[] = {
262
    {ascii, "Ascii", "ASCII\0\0\0"},
263
    {jis, "Jis", "JIS\0\0\0\0\0"},
264
    {unicode, "Unicode", "UNICODE\0"},
265
    {undefined, "Undefined", "\0\0\0\0\0\0\0\0"},
266
    {invalidCharsetId, "InvalidCharsetId", "\0\0\0\0\0\0\0\0"},
267
    {lastCharsetId, "InvalidCharsetId", "\0\0\0\0\0\0\0\0"},
268
};
269
270
826
const char* CommentValue::CharsetInfo::name(CharsetId charsetId) {
271
826
  return charsetTable_[charsetId < lastCharsetId ? charsetId : undefined].name_;
272
826
}
273
274
0
const char* CommentValue::CharsetInfo::code(CharsetId charsetId) {
275
0
  return charsetTable_[charsetId < lastCharsetId ? charsetId : undefined].code_;
276
0
}
277
278
0
CommentValue::CharsetId CommentValue::CharsetInfo::charsetIdByName(const std::string& name) {
279
0
  int i = 0;
280
0
  for (; charsetTable_[i].charsetId_ != lastCharsetId && charsetTable_[i].name_ != name; ++i) {
281
0
  }
282
0
  return charsetTable_[i].charsetId_ == lastCharsetId ? invalidCharsetId : charsetTable_[i].charsetId_;
283
0
}
284
285
7.43k
CommentValue::CharsetId CommentValue::CharsetInfo::charsetIdByCode(const std::string& code) {
286
7.43k
  int i = 0;
287
30.5k
  for (; charsetTable_[i].charsetId_ != lastCharsetId && std::string(charsetTable_[i].code_, 8) != code; ++i) {
288
23.1k
  }
289
7.43k
  return charsetTable_[i].charsetId_ == lastCharsetId ? invalidCharsetId : charsetTable_[i].charsetId_;
290
7.43k
}
291
292
7.11k
CommentValue::CommentValue() : StringValueBase(Exiv2::undefined) {
293
7.11k
}
294
295
0
CommentValue::CommentValue(const std::string& comment) : StringValueBase(Exiv2::undefined) {
296
0
  read(comment);
297
0
}
298
299
0
int CommentValue::read(const std::string& comment) {
300
0
  std::string c = comment;
301
0
  CharsetId charsetId = undefined;
302
0
  if (comment.starts_with("charset=")) {
303
0
    const std::string::size_type pos = comment.find_first_of(' ');
304
0
    std::string name = comment.substr(8, pos - 8);
305
    // Strip quotes (so you can also specify the charset without quotes)
306
0
    if (!name.empty() && name.front() == '"')
307
0
      name = name.substr(1);
308
0
    if (!name.empty() && name.back() == '"')
309
0
      name.pop_back();
310
0
    charsetId = CharsetInfo::charsetIdByName(name);
311
0
    if (charsetId == invalidCharsetId) {
312
0
#ifndef SUPPRESS_WARNINGS
313
0
      EXV_WARNING << Error(ErrorCode::kerInvalidCharset, name) << "\n";
314
0
#endif
315
0
      return 1;
316
0
    }
317
0
    c.clear();
318
0
    if (pos != std::string::npos)
319
0
      c = comment.substr(pos + 1);
320
0
  }
321
0
  if (charsetId == unicode) {
322
0
    const char* to = byteOrder_ == littleEndian ? "UCS-2LE" : "UCS-2BE";
323
0
    convertStringCharset(c, "UTF-8", to);
324
0
  }
325
0
  const std::string code(CharsetInfo::code(charsetId), 8);
326
0
  return StringValueBase::read(code + c);
327
0
}
328
329
7.11k
int CommentValue::read(const byte* buf, size_t len, ByteOrder byteOrder) {
330
7.11k
  byteOrder_ = byteOrder;
331
7.11k
  return StringValueBase::read(buf, len, byteOrder);
332
7.11k
}
333
334
5.01k
size_t CommentValue::copy(byte* buf, ByteOrder byteOrder) const {
335
5.01k
  std::string c = value_;
336
5.01k
  if (charsetId() == unicode) {
337
1.17k
    c = value_.substr(8);
338
1.17k
    [[maybe_unused]] const size_t sz = c.size();
339
1.17k
    if (byteOrder_ == littleEndian && byteOrder == bigEndian) {
340
0
      convertStringCharset(c, "UCS-2LE", "UCS-2BE");
341
1.17k
    } else if (byteOrder_ == bigEndian && byteOrder == littleEndian) {
342
338
      convertStringCharset(c, "UCS-2BE", "UCS-2LE");
343
338
    }
344
1.17k
    c = value_.substr(0, 8) + c;
345
1.17k
  }
346
5.01k
  if (c.empty())
347
1.36k
    return 0;
348
3.64k
  return c.copy(reinterpret_cast<char*>(buf), c.size());
349
5.01k
}
350
351
1.16k
std::ostream& CommentValue::write(std::ostream& os) const {
352
1.16k
  CharsetId csId = charsetId();
353
1.16k
  std::string text = comment();
354
1.16k
  if (csId != undefined) {
355
826
    os << "charset=" << CharsetInfo::name(csId) << " ";
356
826
  }
357
1.16k
  return os << text;
358
1.16k
}
359
360
1.16k
std::string CommentValue::comment(const char* encoding) const {
361
1.16k
  std::string c;
362
1.16k
  if (value_.length() < 8) {
363
78
    return c;
364
78
  }
365
1.08k
  c = value_.substr(8);
366
1.08k
  if (charsetId() == unicode) {
367
311
    const char* from = !encoding || *encoding == '\0' ? detectCharset(c) : encoding;
368
311
    if (!convertStringCharset(c, from, "UTF-8"))
369
19
      throw Error(ErrorCode::kerInvalidIconvEncoding, from, "UTF-8");
370
311
  }
371
372
  // # 1266 Remove trailing nulls
373
1.07k
  if (charsetId() == undefined || charsetId() == ascii) {
374
284
    auto n = c.find('\0');
375
284
    if (n != std::string::npos)
376
186
      c.resize(n);
377
284
  }
378
1.07k
  return c;
379
1.08k
}
380
381
9.16k
CommentValue::CharsetId CommentValue::charsetId() const {
382
9.16k
  CharsetId charsetId = undefined;
383
9.16k
  if (value_.length() >= 8) {
384
7.43k
    const std::string code = value_.substr(0, 8);
385
7.43k
    charsetId = CharsetInfo::charsetIdByCode(code);
386
7.43k
  }
387
9.16k
  return charsetId;
388
9.16k
}
389
390
311
const char* CommentValue::detectCharset(std::string& c) const {
391
  // Interpret a BOM if there is one
392
311
  if (c.compare(0, 3, "\xef\xbb\xbf") == 0) {
393
48
    c = c.substr(3);
394
48
    return "UTF-8";
395
48
  }
396
263
  if (c.compare(0, 2, "\xff\xfe") == 0) {
397
30
    c = c.substr(2);
398
30
    return "UCS-2LE";
399
30
  }
400
233
  if (c.compare(0, 2, "\xfe\xff") == 0) {
401
46
    c = c.substr(2);
402
46
    return "UCS-2BE";
403
46
  }
404
405
  // Todo: Add logic to guess if the comment is encoded in UTF-8
406
407
187
  return byteOrder_ == littleEndian ? "UCS-2LE" : "UCS-2BE";
408
233
}
409
410
18.2k
CommentValue* CommentValue::clone_() const {
411
18.2k
  return new CommentValue(*this);
412
18.2k
}
413
414
31.5k
void XmpValue::setXmpArrayType(XmpArrayType xmpArrayType) {
415
31.5k
  xmpArrayType_ = xmpArrayType;
416
31.5k
}
417
418
8.69k
void XmpValue::setXmpStruct(XmpStruct xmpStruct) {
419
8.69k
  xmpStruct_ = xmpStruct;
420
8.69k
}
421
422
473k
XmpValue::XmpArrayType XmpValue::xmpArrayType() const {
423
473k
  return xmpArrayType_;
424
473k
}
425
426
31.1k
XmpValue::XmpArrayType XmpValue::xmpArrayType(TypeId typeId) {
427
31.1k
  XmpArrayType xa = xaNone;
428
31.1k
  switch (typeId) {
429
574
    case xmpAlt:
430
574
      xa = xaAlt;
431
574
      break;
432
387
    case xmpBag:
433
387
      xa = xaBag;
434
387
      break;
435
22.3k
    case xmpSeq:
436
22.3k
      xa = xaSeq;
437
22.3k
      break;
438
7.85k
    default:
439
7.85k
      break;
440
31.1k
  }
441
31.1k
  return xa;
442
31.1k
}
443
444
486k
XmpValue::XmpStruct XmpValue::xmpStruct() const {
445
486k
  return xmpStruct_;
446
486k
}
447
448
0
size_t XmpValue::copy(byte* buf, ByteOrder /*byteOrder*/) const {
449
0
  std::ostringstream os;
450
0
  write(os);
451
0
  std::string s = os.str();
452
0
  if (!s.empty())
453
0
    std::copy(s.begin(), s.end(), buf);
454
0
  return s.size();
455
0
}
456
457
0
int XmpValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) {
458
0
  std::string s(reinterpret_cast<const char*>(buf), len);
459
0
  return read(s);
460
0
}
461
462
0
size_t XmpValue::size() const {
463
0
  std::ostringstream os;
464
0
  write(os);
465
0
  return os.str().size();
466
0
}
467
468
206k
XmpTextValue::XmpTextValue() : XmpValue(xmpText) {
469
206k
}
470
471
0
XmpTextValue::XmpTextValue(const std::string& buf) : XmpValue(xmpText) {
472
0
  read(buf);
473
0
}
474
475
2.38M
int XmpTextValue::read(const std::string& buf) {
476
  // support a type=Alt,Bag,Seq,Struct indicator
477
2.38M
  std::string b = buf;
478
2.38M
  std::string type;
479
2.38M
  if (buf.starts_with("type=")) {
480
2.71k
    std::string::size_type pos = buf.find_first_of(' ');
481
2.71k
    type = buf.substr(5, pos - 5);
482
    // Strip quotes (so you can also specify the type without quotes)
483
2.71k
    if (!type.empty() && type.front() == '"')
484
1.03k
      type = type.substr(1);
485
2.71k
    if (!type.empty() && type.back() == '"')
486
640
      type.pop_back();
487
2.71k
    b.clear();
488
2.71k
    if (pos != std::string::npos)
489
1.49k
      b = buf.substr(pos + 1);
490
2.71k
  }
491
2.38M
  if (!type.empty()) {
492
1.34k
    if (type == "Alt") {
493
176
      setXmpArrayType(XmpValue::xaAlt);
494
1.17k
    } else if (type == "Bag") {
495
144
      setXmpArrayType(XmpValue::xaBag);
496
1.02k
    } else if (type == "Seq") {
497
130
      setXmpArrayType(XmpValue::xaSeq);
498
896
    } else if (type == "Struct") {
499
349
      setXmpStruct();
500
547
    } else {
501
547
      throw Error(ErrorCode::kerInvalidXmpText, type);
502
547
    }
503
1.34k
  }
504
2.38M
  value_ = std::move(b);
505
2.38M
  return 0;
506
2.38M
}
507
508
0
XmpTextValue::UniquePtr XmpTextValue::clone() const {
509
0
  return UniquePtr(clone_());
510
0
}
511
512
109k
size_t XmpTextValue::size() const {
513
109k
  std::ostringstream os;
514
109k
  write(os);
515
109k
  return os.str().size();
516
109k
}
517
518
109k
size_t XmpTextValue::count() const {
519
109k
  return size();
520
109k
}
521
522
296k
std::ostream& XmpTextValue::write(std::ostream& os) const {
523
296k
  bool del = false;
524
296k
  if (xmpArrayType() != XmpValue::xaNone) {
525
619
    switch (xmpArrayType()) {
526
114
      case XmpValue::xaAlt:
527
114
        os << "type=\"Alt\"";
528
114
        break;
529
8
      case XmpValue::xaBag:
530
8
        os << "type=\"Bag\"";
531
8
        break;
532
497
      case XmpValue::xaSeq:
533
497
        os << "type=\"Seq\"";
534
497
        break;
535
0
      case XmpValue::xaNone:
536
0
        break;  // just to suppress the warning
537
619
    }
538
619
    del = true;
539
296k
  } else if (xmpStruct() != XmpValue::xsNone) {
540
7.59k
    switch (xmpStruct()) {
541
7.59k
      case XmpValue::xsStruct:
542
7.59k
        os << "type=\"Struct\"";
543
7.59k
        break;
544
0
      case XmpValue::xsNone:
545
0
        break;  // just to suppress the warning
546
7.59k
    }
547
7.59k
    del = true;
548
7.59k
  }
549
296k
  if (del && !value_.empty())
550
44
    os << " ";
551
296k
  return os << value_;
552
296k
}
553
554
0
int64_t XmpTextValue::toInt64(size_t /*n*/) const {
555
0
  return parseInt64(value_, ok_);
556
0
}
557
558
0
uint32_t XmpTextValue::toUint32(size_t /*n*/) const {
559
0
  return parseUint32(value_, ok_);
560
0
}
561
562
0
float XmpTextValue::toFloat(size_t /*n*/) const {
563
0
  return parseFloat(value_, ok_);
564
0
}
565
566
0
Rational XmpTextValue::toRational(size_t /*n*/) const {
567
0
  return parseRational(value_, ok_);
568
0
}
569
570
392k
XmpTextValue* XmpTextValue::clone_() const {
571
392k
  return new XmpTextValue(*this);
572
392k
}
573
574
22.8k
XmpArrayValue::XmpArrayValue(TypeId typeId) : XmpValue(typeId) {
575
22.8k
  setXmpArrayType(xmpArrayType(typeId));
576
22.8k
}
577
578
602k
int XmpArrayValue::read(const std::string& buf) {
579
602k
  if (!buf.empty())
580
562k
    value_.push_back(buf);
581
602k
  return 0;
582
602k
}
583
584
0
XmpArrayValue::UniquePtr XmpArrayValue::clone() const {
585
0
  return UniquePtr(clone_());
586
0
}
587
588
62.6k
size_t XmpArrayValue::count() const {
589
62.6k
  return value_.size();
590
62.6k
}
591
592
1.54k
std::ostream& XmpArrayValue::write(std::ostream& os) const {
593
1.54k
  if (!value_.empty()) {
594
1.22k
    std::copy(value_.begin(), value_.end() - 1, std::ostream_iterator<std::string>(os, ", "));
595
1.22k
    os << value_.back();
596
1.22k
  }
597
1.54k
  return os;
598
1.54k
}
599
600
169k
std::string XmpArrayValue::toString(size_t n) const {
601
169k
  ok_ = true;
602
169k
  return value_.at(n);
603
169k
}
604
605
0
int64_t XmpArrayValue::toInt64(size_t n) const {
606
0
  return parseInt64(value_.at(n), ok_);
607
0
}
608
609
0
uint32_t XmpArrayValue::toUint32(size_t n) const {
610
0
  return parseUint32(value_.at(n), ok_);
611
0
}
612
613
0
float XmpArrayValue::toFloat(size_t n) const {
614
0
  return parseFloat(value_.at(n), ok_);
615
0
}
616
617
0
Rational XmpArrayValue::toRational(size_t n) const {
618
0
  return parseRational(value_.at(n), ok_);
619
0
}
620
621
49.2k
XmpArrayValue* XmpArrayValue::clone_() const {
622
49.2k
  return new XmpArrayValue(*this);
623
49.2k
}
624
625
1.14k
LangAltValue::LangAltValue() : XmpValue(langAlt) {
626
1.14k
}
627
628
0
LangAltValue::LangAltValue(const std::string& buf) : XmpValue(langAlt) {
629
0
  read(buf);
630
0
}
631
632
0
int LangAltValue::read(const std::string& buf) {
633
0
  std::string b = buf;
634
0
  std::string lang = "x-default";
635
0
  if (buf.starts_with("lang=")) {
636
0
    static constexpr auto ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
637
638
0
    const std::string::size_type pos = buf.find_first_of(' ');
639
0
    if (pos == std::string::npos) {
640
0
      lang = buf.substr(5);
641
0
    } else {
642
0
      lang = buf.substr(5, pos - 5);
643
0
    }
644
0
    if (lang.empty())
645
0
      throw Error(ErrorCode::kerInvalidLangAltValue, buf);
646
    // Strip quotes (so you can also specify the language without quotes)
647
0
    if (lang.front() == '"') {
648
0
      lang = lang.substr(1);
649
650
0
      if (lang.empty() || lang.back() != '"')
651
0
        throw Error(ErrorCode::kerInvalidLangAltValue, buf);
652
653
0
      lang.pop_back();
654
0
    }
655
656
0
    if (lang.empty())
657
0
      throw Error(ErrorCode::kerInvalidLangAltValue, buf);
658
659
    // Check language is in the correct format (see https://www.ietf.org/rfc/rfc3066.txt)
660
0
    if (auto charPos = lang.find_first_not_of(ALPHA); charPos != std::string::npos) {
661
0
      static constexpr auto ALPHA_NUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
662
0
      if (lang.at(charPos) != '-' || lang.find_first_not_of(ALPHA_NUM, charPos + 1) != std::string::npos)
663
0
        throw Error(ErrorCode::kerInvalidLangAltValue, buf);
664
0
    }
665
666
0
    b.clear();
667
0
    if (pos != std::string::npos)
668
0
      b = buf.substr(pos + 1);
669
0
  }
670
671
0
  value_[lang] = std::move(b);
672
0
  return 0;
673
0
}
674
675
0
LangAltValue::UniquePtr LangAltValue::clone() const {
676
0
  return UniquePtr(clone_());
677
0
}
678
679
571
size_t LangAltValue::count() const {
680
571
  return value_.size();
681
571
}
682
683
321
std::ostream& LangAltValue::write(std::ostream& os) const {
684
321
  bool first = true;
685
686
  // Write the default entry first
687
321
  if (auto i = value_.find("x-default"); i != value_.end()) {
688
60
    os << "lang=\"" << i->first << "\" " << i->second;
689
60
    first = false;
690
60
  }
691
692
  // Write the others
693
321
  for (const auto& [lang, s] : value_) {
694
317
    if (lang != "x-default") {
695
257
      if (!first)
696
4
        os << ", ";
697
257
      os << "lang=\"" << lang << "\" " << s;
698
257
      first = false;
699
257
    }
700
317
  }
701
321
  return os;
702
321
}
703
704
440
std::string LangAltValue::toString(size_t /*n*/) const {
705
440
  return toString("x-default");
706
440
}
707
708
440
std::string LangAltValue::toString(const std::string& qualifier) const {
709
440
  if (auto i = value_.find(qualifier); i != value_.end()) {
710
47
    ok_ = true;
711
47
    return i->second;
712
47
  }
713
393
  ok_ = false;
714
393
  return "";
715
440
}
716
717
0
int64_t LangAltValue::toInt64(size_t /*n*/) const {
718
0
  ok_ = false;
719
0
  return 0;
720
0
}
721
722
0
uint32_t LangAltValue::toUint32(size_t /*n*/) const {
723
0
  ok_ = false;
724
0
  return 0;
725
0
}
726
727
0
float LangAltValue::toFloat(size_t /*n*/) const {
728
0
  ok_ = false;
729
0
  return 0.0F;
730
0
}
731
732
0
Rational LangAltValue::toRational(size_t /*n*/) const {
733
0
  ok_ = false;
734
0
  return {0, 0};
735
0
}
736
737
2.33k
LangAltValue* LangAltValue::clone_() const {
738
2.33k
  return new LangAltValue(*this);
739
2.33k
}
740
741
8.44k
DateValue::DateValue() : Value(date) {
742
8.44k
  date_ = {};
743
8.44k
}
744
745
0
DateValue::DateValue(int32_t year, int32_t month, int32_t day) : Value(date) {
746
0
  date_ = {year, month, day};
747
0
}
748
749
7.99k
int DateValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) {
750
7.99k
  const std::string str(reinterpret_cast<const char*>(buf), len);
751
7.99k
  return read(str);
752
7.99k
}
753
754
8.44k
int DateValue::read(const std::string& buf) {
755
  // ISO 8601 date formats:
756
  // https://web.archive.org/web/20171020084445/https://www.loc.gov/standards/datetime/ISO_DIS%208601-1.pdf
757
8.44k
  size_t monthPos = 0;
758
8.44k
  size_t dayPos = 0;
759
760
11.4k
  auto printWarning = [] {
761
11.4k
#ifndef SUPPRESS_WARNINGS
762
11.4k
    EXV_WARNING << Error(ErrorCode::kerUnsupportedDateFormat) << "\n";
763
11.4k
#endif
764
11.4k
  };
765
766
8.44k
  if (buf.size() < 8) {
767
1.12k
    printWarning();
768
1.12k
    return 1;
769
1.12k
  }
770
771
7.31k
  if ((buf.size() >= 10 && buf[4] == '-' && buf[7] == '-') || (buf.size() == 8)) {
772
4.64k
    if (buf.size() >= 10) {
773
899
      monthPos = 5;
774
899
      dayPos = 8;
775
3.74k
    } else {
776
3.74k
      monthPos = 4;
777
3.74k
      dayPos = 6;
778
3.74k
    }
779
780
9.13k
    auto checkDigits = [&buf, &printWarning](size_t start, size_t count, int32_t& dest) {
781
27.7k
      for (size_t i = start; i < start + count; ++i) {
782
22.0k
        if (!std::isdigit(buf[i])) {
783
3.43k
          printWarning();
784
3.43k
          return 1;
785
3.43k
        }
786
22.0k
      }
787
5.69k
      dest = std::stoul(buf.substr(start, count));
788
5.69k
      return 0;
789
9.13k
    };
790
791
4.64k
    if (checkDigits(0, 4, date_.year) || checkDigits(monthPos, 2, date_.month) || checkDigits(dayPos, 2, date_.day)) {
792
3.43k
      printWarning();
793
3.43k
      return 1;
794
3.43k
    }
795
796
1.20k
    if (date_.month > 12 || date_.day > 31) {
797
758
      date_.month = 0;
798
758
      date_.day = 0;
799
758
      printWarning();
800
758
      return 1;
801
758
    }
802
448
    return 0;
803
1.20k
  }
804
2.67k
  printWarning();
805
2.67k
  return 1;
806
7.31k
}
807
808
0
void DateValue::setDate(const Date& src) {
809
0
  date_ = src;
810
0
}
811
812
189
size_t DateValue::copy(byte* buf, ByteOrder /*byteOrder*/) const {
813
  // \note Here the date is copied in the Basic format YYYYMMDD, as the IPTC key  Iptc.Application2.DateCreated
814
  // wants it. Check https://exiv2.org/iptc.html
815
816
189
  auto out = reinterpret_cast<char*>(buf);
817
189
  auto it = stringFormatTo(out, "{:04}{:02}{:02}", date_.year, date_.month, date_.day);
818
819
189
  return it - out;
820
189
}
821
822
0
const DateValue::Date& DateValue::getDate() const {
823
0
  return date_;
824
0
}
825
826
0
size_t DateValue::count() const {
827
0
  return size();
828
0
}
829
830
378
size_t DateValue::size() const {
831
378
  return 8;
832
378
}
833
834
2.19k
DateValue* DateValue::clone_() const {
835
2.19k
  return new DateValue(*this);
836
2.19k
}
837
838
382
std::ostream& DateValue::write(std::ostream& os) const {
839
  // Write DateValue in ISO 8601 Extended format: YYYY-MM-DD
840
382
  return os << stringFormat("{:04}-{:02}-{:02}", date_.year, date_.month, date_.day);
841
382
}
842
843
0
int64_t DateValue::toInt64(size_t /*n*/) const {
844
  // Range of tm struct is limited to about 1970 to 2038
845
  // This will return -1 if outside that range
846
0
  std::tm tms = {};
847
0
  tms.tm_mday = date_.day;
848
0
  tms.tm_mon = date_.month - 1;
849
0
  tms.tm_year = date_.year - 1900;
850
0
  auto l = static_cast<int64_t>(std::mktime(&tms));
851
0
  ok_ = (l != -1);
852
0
  return l;
853
0
}
854
855
0
uint32_t DateValue::toUint32(size_t /*n*/) const {
856
0
  const int64_t t = toInt64();
857
0
  if (t < 0 || t > std::numeric_limits<uint32_t>::max()) {
858
0
    ok_ = false;
859
0
    return 0;
860
0
  }
861
0
  return static_cast<uint32_t>(t);
862
0
}
863
864
0
float DateValue::toFloat(size_t n) const {
865
0
  return static_cast<float>(toInt64(n));
866
0
}
867
868
0
Rational DateValue::toRational(size_t n) const {
869
0
  const int64_t t = toInt64(n);
870
0
  if (t < std::numeric_limits<int32_t>::min() || t > std::numeric_limits<int32_t>::max()) {
871
0
    ok_ = false;
872
0
    return {0, 1};
873
0
  }
874
0
  return {static_cast<int32_t>(t), 1};
875
0
}
876
877
15.4k
TimeValue::TimeValue() : Value(time) {
878
15.4k
  time_ = {};
879
15.4k
}
880
881
0
TimeValue::TimeValue(int32_t hour, int32_t minute, int32_t second, int32_t tzHour, int32_t tzMinute) : Value(date) {
882
0
  time_ = {hour, minute, second, tzHour, tzMinute};
883
0
}
884
885
15.4k
int TimeValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) {
886
15.4k
  const std::string str(reinterpret_cast<const char*>(buf), len);
887
15.4k
  return read(str);
888
15.4k
}
889
890
15.4k
int TimeValue::read(const std::string& buf) {
891
  // ISO 8601 time formats:
892
  // https://web.archive.org/web/20171020084445/https://www.loc.gov/standards/datetime/ISO_DIS%208601-1.pdf
893
  // Not supported formats:
894
  // 4.2.2.4 Representations with decimal fraction: 232050,5
895
15.4k
  auto printWarning = [] {
896
11.3k
#ifndef SUPPRESS_WARNINGS
897
11.3k
    EXV_WARNING << Error(ErrorCode::kerUnsupportedTimeFormat) << "\n";
898
11.3k
#endif
899
11.3k
    return 1;
900
11.3k
  };
901
902
15.4k
  if (buf.size() < 2)
903
1.37k
    return printWarning();
904
905
14.0k
  for (auto c : buf)
906
200k
    if (c != ':' && c != '+' && c != '-' && c != 'Z' && !std::isdigit(c))
907
5.05k
      return printWarning();
908
909
9.02k
  size_t mpos;
910
9.02k
  size_t spos;
911
9.02k
  if (buf.find(':') != std::string::npos) {
912
3.02k
    mpos = 3;
913
3.02k
    spos = 6;
914
6.00k
  } else {
915
6.00k
    mpos = 2;
916
6.00k
    spos = 4;
917
6.00k
  }
918
919
9.02k
  auto hi = std::stoi(buf.substr(0, 2));
920
9.02k
  if (hi < 0 || hi > 23)
921
298
    return printWarning();
922
8.72k
  time_.hour = hi;
923
8.72k
  if (buf.size() > 3) {
924
7.36k
    auto mi = std::stoi(buf.substr(mpos, 2));
925
7.36k
    if (mi < 0 || mi > 59)
926
507
      return printWarning();
927
6.86k
    time_.minute = std::stoi(buf.substr(mpos, 2));
928
6.86k
  } else {
929
1.35k
    time_.minute = 0;
930
1.35k
  }
931
8.22k
  if (buf.size() > 5) {
932
5.96k
    auto si = std::stoi(buf.substr(spos, 2));
933
5.96k
    if (si < 0 || si > 60)
934
1.23k
      return printWarning();
935
4.72k
    time_.second = std::stoi(buf.substr(spos, 2));
936
4.72k
  } else {
937
2.26k
    time_.second = 0;
938
2.26k
  }
939
940
6.98k
  auto fpos = buf.find('+');
941
6.98k
  if (fpos == std::string::npos)
942
4.29k
    fpos = buf.find('-');
943
944
6.98k
  if (fpos != std::string::npos) {
945
5.99k
    auto format = buf.substr(fpos, buf.size());
946
5.99k
    auto posColon = format.find(':');
947
5.99k
    if (posColon == std::string::npos) {
948
      // Extended format
949
3.67k
      auto tzhi = std::stoi(format.substr(0, 3));
950
3.67k
      if (tzhi < -23 || tzhi > 23)
951
729
        return printWarning();
952
2.94k
      time_.tzHour = tzhi;
953
2.94k
      if (format.size() > 3) {
954
2.08k
        int minute = std::stoi(format.substr(3));
955
2.08k
        if (minute < 0 || minute > 59)
956
589
          return printWarning();
957
1.49k
        time_.tzMinute = time_.tzHour < 0 ? -minute : minute;
958
1.49k
      }
959
2.94k
    } else {
960
      // Basic format
961
2.32k
      auto tzhi = std::stoi(format.substr(0, posColon));
962
2.32k
      if (tzhi < -23 || tzhi > 23)
963
713
        return printWarning();
964
1.60k
      time_.tzHour = tzhi;
965
1.60k
      int minute = std::stoi(format.substr(posColon + 1));
966
1.60k
      if (minute < 0 || minute > 59)
967
816
        return printWarning();
968
793
      time_.tzMinute = time_.tzHour < 0 ? -minute : minute;
969
793
    }
970
5.99k
  }
971
4.13k
  return 0;
972
6.98k
}
973
974
/// \todo not used internally. At least we should test it
975
0
void TimeValue::setTime(const Time& src) {
976
0
  time_ = src;
977
0
}
978
979
496
size_t TimeValue::copy(byte* buf, ByteOrder /*byteOrder*/) const {
980
  // NOTE: Here the time is copied in the Basic format HHMMSS:HHMM, as the IPTC key
981
  // Iptc.Application2.TimeCreated wants it. Check https://exiv2.org/iptc.html
982
496
  char plusMinus = '+';
983
496
  if (time_.tzHour < 0 || time_.tzMinute < 0)
984
129
    plusMinus = '-';
985
986
496
  auto out = reinterpret_cast<char*>(buf);
987
496
  auto it = stringFormatTo(out, "{:02}{:02}{:02}{}{:02}{:02}", time_.hour, time_.minute, time_.second, plusMinus,
988
496
                           std::abs(time_.tzHour), std::abs(time_.tzMinute));
989
990
496
  auto wrote = static_cast<size_t>(it - out);
991
496
  Internal::enforce(wrote == 11, Exiv2::ErrorCode::kerUnsupportedTimeFormat);
992
496
  return wrote;
993
496
}
994
995
0
const TimeValue::Time& TimeValue::getTime() const {
996
0
  return time_;
997
0
}
998
999
0
size_t TimeValue::count() const {
1000
0
  return size();
1001
0
}
1002
1003
992
size_t TimeValue::size() const {
1004
992
  return 11;
1005
992
}
1006
1007
6.35k
TimeValue* TimeValue::clone_() const {
1008
6.35k
  return new TimeValue(*this);
1009
6.35k
}
1010
1011
126
std::ostream& TimeValue::write(std::ostream& os) const {
1012
  // Write TimeValue in ISO 8601 Extended format: hh:mm:ss±hh:mm
1013
126
  char plusMinus = '+';
1014
126
  if (time_.tzHour < 0 || time_.tzMinute < 0)
1015
32
    plusMinus = '-';
1016
1017
126
  return os << stringFormat("{:02}:{:02}:{:02}{}{:02}:{:02}", time_.hour, time_.minute, time_.second, plusMinus,
1018
126
                            std::abs(time_.tzHour), std::abs(time_.tzMinute));
1019
126
}
1020
1021
0
int64_t TimeValue::toInt64(size_t /*n*/) const {
1022
  // Returns number of seconds in the day in UTC.
1023
0
  auto result = static_cast<int64_t>(time_.hour - time_.tzHour) * 60 * 60;
1024
0
  result += static_cast<int64_t>(time_.minute - time_.tzMinute) * 60;
1025
0
  result += time_.second;
1026
0
  if (result < 0) {
1027
0
    result += 86400;
1028
0
  }
1029
0
  ok_ = true;
1030
0
  return result;
1031
0
}
1032
1033
0
uint32_t TimeValue::toUint32(size_t /*n*/) const {
1034
0
  return static_cast<uint32_t>(std::clamp<int64_t>(toInt64(), 0, std::numeric_limits<uint32_t>::max()));
1035
0
}
1036
1037
0
float TimeValue::toFloat(size_t n) const {
1038
0
  return static_cast<float>(toInt64(n));
1039
0
}
1040
1041
0
Rational TimeValue::toRational(size_t n) const {
1042
0
  return {static_cast<int32_t>(toInt64(n)), 1};
1043
0
}
1044
1045
}  // namespace Exiv2