Coverage Report

Created: 2026-08-13 07:05

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
54.5M
Value::Value(TypeId typeId) : type_(typeId) {
20
54.5M
}
21
22
21.6M
Value::UniquePtr Value::create(TypeId typeId) {
23
21.6M
  switch (typeId) {
24
0
    case invalidTypeId:
25
8.08k
    case signedByte:
26
2.66M
    case unsignedByte:
27
2.66M
      return std::make_unique<DataValue>(typeId);
28
101k
    case asciiString:
29
101k
      return std::make_unique<AsciiValue>();
30
14.5M
    case unsignedShort:
31
14.5M
      return std::make_unique<ValueType<uint16_t>>();
32
364k
    case unsignedLong:
33
385k
    case tiffIfd:
34
385k
      return std::make_unique<ValueType<uint32_t>>(typeId);
35
48.8k
    case unsignedRational:
36
48.8k
      return std::make_unique<ValueType<URational>>();
37
103k
    case undefined:
38
103k
      return std::make_unique<DataValue>();
39
1.10M
    case signedShort:
40
1.10M
      return std::make_unique<ValueType<int16_t>>();
41
166k
    case signedLong:
42
166k
      return std::make_unique<ValueType<int32_t>>();
43
35.9k
    case signedRational:
44
35.9k
      return std::make_unique<ValueType<Rational>>();
45
10.2k
    case tiffFloat:
46
10.2k
      return std::make_unique<ValueType<float>>();
47
8.67k
    case tiffDouble:
48
8.67k
      return std::make_unique<ValueType<double>>();
49
60.7k
    case string:
50
60.7k
      return std::make_unique<StringValue>();
51
3.15k
    case date:
52
3.15k
      return std::make_unique<DateValue>();
53
3.26k
    case time:
54
3.26k
      return std::make_unique<TimeValue>();
55
3.02k
    case comment:
56
3.02k
      return std::make_unique<CommentValue>();
57
23.2k
    case xmpText:
58
23.2k
      return std::make_unique<XmpTextValue>();
59
0
    case xmpBag:
60
3.46k
    case xmpSeq:
61
3.46k
    case xmpAlt:
62
3.46k
      return std::make_unique<XmpArrayValue>(typeId);
63
0
    case langAlt:
64
0
      return std::make_unique<LangAltValue>();
65
2.40M
    default:
66
2.40M
      return std::make_unique<DataValue>(typeId);
67
21.6M
  }
68
21.6M
}  // Value::create
69
70
1.17k
int Value::setDataArea(const byte* /*buf*/, size_t /*len*/) {
71
1.17k
  return -1;
72
1.17k
}
73
74
115k
std::string Value::toString() const {
75
115k
  std::ostringstream os;
76
115k
  write(os);
77
115k
  ok_ = !os.fail();
78
115k
  return os.str();
79
115k
}
80
81
11.9k
std::string Value::toString(size_t /*n*/) const {
82
11.9k
  return toString();
83
11.9k
}
84
85
33.4k
size_t Value::sizeDataArea() const {
86
33.4k
  return 0;
87
33.4k
}
88
89
2.24k
DataBuf Value::dataArea() const {
90
2.24k
  return {nullptr, 0};
91
2.24k
}
92
93
5.17M
DataValue::DataValue(TypeId typeId) : Value(typeId) {
94
5.17M
}
95
96
0
DataValue::DataValue(const byte* buf, size_t len, ByteOrder byteOrder, TypeId typeId) : Value(typeId) {
97
0
  read(buf, len, byteOrder);
98
0
}
99
100
9.29M
size_t DataValue::count() const {
101
9.29M
  return size();
102
9.29M
}
103
104
5.17M
int DataValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) {
105
  // byteOrder not needed
106
5.17M
  value_.assign(buf, buf + len);
107
5.17M
  return 0;
108
5.17M
}
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
2.33M
size_t DataValue::copy(byte* buf, ByteOrder /*byteOrder*/) const {
123
  // byteOrder not needed
124
2.33M
  return std::copy(value_.begin(), value_.end(), buf) - buf;
125
2.33M
}
126
127
17.6M
size_t DataValue::size() const {
128
17.6M
  return value_.size();
129
17.6M
}
130
131
15.0M
DataValue* DataValue::clone_() const {
132
15.0M
  return new DataValue(*this);
133
15.0M
}
134
135
112k
std::ostream& DataValue::write(std::ostream& os) const {
136
112k
  if (!value_.empty()) {
137
112k
    std::copy(value_.begin(), value_.end() - 1, std::ostream_iterator<int>(os, " "));
138
112k
    os << static_cast<int>(value_.back());
139
112k
  }
140
112k
  return os;
141
112k
}
142
143
1.80k
std::string DataValue::toString(size_t n) const {
144
1.80k
  ok_ = true;
145
1.80k
  return std::to_string(value_.at(n));
146
1.80k
}
147
148
265k
int64_t DataValue::toInt64(size_t n) const {
149
265k
  ok_ = true;
150
265k
  return value_.at(n);
151
265k
}
152
153
47.5k
uint32_t DataValue::toUint32(size_t n) const {
154
47.5k
  ok_ = true;
155
47.5k
  return value_.at(n);
156
47.5k
}
157
158
1.62k
float DataValue::toFloat(size_t n) const {
159
1.62k
  ok_ = true;
160
1.62k
  return value_.at(n);
161
1.62k
}
162
163
2.11k
Rational DataValue::toRational(size_t n) const {
164
2.11k
  ok_ = true;
165
2.11k
  return {value_.at(n), 1};
166
2.11k
}
167
168
0
StringValueBase::StringValueBase(TypeId typeId, const std::string& buf) : Value(typeId) {
169
0
  read(buf);
170
0
}
171
172
150
int StringValueBase::read(const std::string& buf) {
173
150
  value_ = buf;
174
150
  return 0;
175
150
}
176
177
150k
int StringValueBase::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) {
178
  // byteOrder not needed
179
150k
  if (buf)
180
150k
    value_ = std::string(reinterpret_cast<const char*>(buf), len);
181
150k
  return 0;
182
150k
}
183
184
83.9k
size_t StringValueBase::copy(byte* buf, ByteOrder /*byteOrder*/) const {
185
83.9k
  if (value_.empty())
186
32.3k
    return 0;
187
  // byteOrder not needed
188
51.5k
  return value_.copy(reinterpret_cast<char*>(buf), value_.size());
189
83.9k
}
190
191
215k
size_t StringValueBase::count() const {
192
215k
  return size();
193
215k
}
194
195
425k
size_t StringValueBase::size() const {
196
425k
  return value_.size();
197
425k
}
198
199
3.64k
std::ostream& StringValueBase::write(std::ostream& os) const {
200
3.64k
  return os << value_;
201
3.64k
}
202
203
23.8k
int64_t StringValueBase::toInt64(size_t n) const {
204
23.8k
  ok_ = true;
205
23.8k
  return value_.at(n);
206
23.8k
}
207
208
2.69k
uint32_t StringValueBase::toUint32(size_t n) const {
209
2.69k
  ok_ = true;
210
2.69k
  return value_.at(n);
211
2.69k
}
212
213
605
float StringValueBase::toFloat(size_t n) const {
214
605
  ok_ = true;
215
605
  return value_.at(n);
216
605
}
217
218
740
Rational StringValueBase::toRational(size_t n) const {
219
740
  ok_ = true;
220
740
  return {value_.at(n), 1};
221
740
}
222
223
60.7k
StringValue::StringValue() : StringValueBase(string) {
224
60.7k
}
225
226
0
StringValue::StringValue(const std::string& buf) : StringValueBase(string, buf) {
227
0
}
228
229
839k
StringValue* StringValue::clone_() const {
230
839k
  return new StringValue(*this);
231
839k
}
232
233
101k
AsciiValue::AsciiValue() : StringValueBase(asciiString) {
234
101k
}
235
236
0
AsciiValue::AsciiValue(const std::string& buf) : StringValueBase(asciiString, buf) {
237
0
}
238
239
30.1k
int AsciiValue::read(const std::string& buf) {
240
30.1k
  value_ = buf;
241
  // ensure count>0 and nul terminated # https://github.com/Exiv2/exiv2/issues/1484
242
30.1k
  if (value_.empty() || value_.back() != '\0') {
243
30.1k
    value_ += '\0';
244
30.1k
  }
245
30.1k
  return 0;
246
30.1k
}
247
248
220k
AsciiValue* AsciiValue::clone_() const {
249
220k
  return new AsciiValue(*this);
250
220k
}
251
252
146k
std::ostream& AsciiValue::write(std::ostream& os) const {
253
  // Write only up to the first '\0' (if any)
254
146k
  std::string::size_type pos = value_.find_first_of('\0');
255
146k
  if (pos == std::string::npos)
256
5.36k
    pos = value_.size();
257
146k
  return os << value_.substr(0, pos);
258
146k
}
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
982
const char* CommentValue::CharsetInfo::name(CharsetId charsetId) {
271
982
  return charsetTable_[charsetId < lastCharsetId ? charsetId : undefined].name_;
272
982
}
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
5.42k
CommentValue::CharsetId CommentValue::CharsetInfo::charsetIdByCode(const std::string& code) {
286
5.42k
  int i = 0;
287
23.3k
  for (; charsetTable_[i].charsetId_ != lastCharsetId && std::string(charsetTable_[i].code_, 8) != code; ++i) {
288
17.9k
  }
289
5.42k
  return charsetTable_[i].charsetId_ == lastCharsetId ? invalidCharsetId : charsetTable_[i].charsetId_;
290
5.42k
}
291
292
3.02k
CommentValue::CommentValue() : StringValueBase(Exiv2::undefined) {
293
3.02k
}
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
3.02k
int CommentValue::read(const byte* buf, size_t len, ByteOrder byteOrder) {
330
3.02k
  byteOrder_ = byteOrder;
331
3.02k
  return StringValueBase::read(buf, len, byteOrder);
332
3.02k
}
333
334
362
size_t CommentValue::copy(byte* buf, ByteOrder byteOrder) const {
335
362
  std::string c = value_;
336
362
  if (charsetId() == unicode) {
337
61
    c = value_.substr(8);
338
61
    [[maybe_unused]] const size_t sz = c.size();
339
61
    if (byteOrder_ == littleEndian && byteOrder == bigEndian) {
340
0
      convertStringCharset(c, "UCS-2LE", "UCS-2BE");
341
61
    } else if (byteOrder_ == bigEndian && byteOrder == littleEndian) {
342
0
      convertStringCharset(c, "UCS-2BE", "UCS-2LE");
343
0
    }
344
61
    c = value_.substr(0, 8) + c;
345
61
  }
346
362
  if (c.empty())
347
114
    return 0;
348
248
  return c.copy(reinterpret_cast<char*>(buf), c.size());
349
362
}
350
351
1.51k
std::ostream& CommentValue::write(std::ostream& os) const {
352
1.51k
  CharsetId csId = charsetId();
353
1.51k
  std::string text = comment();
354
1.51k
  if (csId != undefined) {
355
982
    os << "charset=" << CharsetInfo::name(csId) << " ";
356
982
  }
357
1.51k
  return os << text;
358
1.51k
}
359
360
1.51k
std::string CommentValue::comment(const char* encoding) const {
361
1.51k
  std::string c;
362
1.51k
  if (value_.length() < 8) {
363
112
    return c;
364
112
  }
365
1.40k
  c = value_.substr(8);
366
1.40k
  if (charsetId() == unicode) {
367
394
    const char* from = !encoding || *encoding == '\0' ? detectCharset(c) : encoding;
368
394
    if (!convertStringCharset(c, from, "UTF-8"))
369
16
      throw Error(ErrorCode::kerInvalidIconvEncoding, from, "UTF-8");
370
394
  }
371
372
  // # 1266 Remove trailing nulls
373
1.38k
  if (charsetId() == undefined || charsetId() == ascii) {
374
488
    auto n = c.find('\0');
375
488
    if (n != std::string::npos)
376
336
      c.resize(n);
377
488
  }
378
1.38k
  return c;
379
1.40k
}
380
381
5.64k
CommentValue::CharsetId CommentValue::charsetId() const {
382
5.64k
  CharsetId charsetId = undefined;
383
5.64k
  if (value_.length() >= 8) {
384
5.42k
    const std::string code = value_.substr(0, 8);
385
5.42k
    charsetId = CharsetInfo::charsetIdByCode(code);
386
5.42k
  }
387
5.64k
  return charsetId;
388
5.64k
}
389
390
394
const char* CommentValue::detectCharset(std::string& c) const {
391
  // Interpret a BOM if there is one
392
394
  if (c.compare(0, 3, "\xef\xbb\xbf") == 0) {
393
62
    c = c.substr(3);
394
62
    return "UTF-8";
395
62
  }
396
332
  if (c.compare(0, 2, "\xff\xfe") == 0) {
397
65
    c = c.substr(2);
398
65
    return "UCS-2LE";
399
65
  }
400
267
  if (c.compare(0, 2, "\xfe\xff") == 0) {
401
127
    c = c.substr(2);
402
127
    return "UCS-2BE";
403
127
  }
404
405
  // Todo: Add logic to guess if the comment is encoded in UTF-8
406
407
140
  return byteOrder_ == littleEndian ? "UCS-2LE" : "UCS-2BE";
408
267
}
409
410
6.47k
CommentValue* CommentValue::clone_() const {
411
6.47k
  return new CommentValue(*this);
412
6.47k
}
413
414
4.92k
void XmpValue::setXmpArrayType(XmpArrayType xmpArrayType) {
415
4.92k
  xmpArrayType_ = xmpArrayType;
416
4.92k
}
417
418
556
void XmpValue::setXmpStruct(XmpStruct xmpStruct) {
419
556
  xmpStruct_ = xmpStruct;
420
556
}
421
422
55.4k
XmpValue::XmpArrayType XmpValue::xmpArrayType() const {
423
55.4k
  return xmpArrayType_;
424
55.4k
}
425
426
4.83k
XmpValue::XmpArrayType XmpValue::xmpArrayType(TypeId typeId) {
427
4.83k
  XmpArrayType xa = xaNone;
428
4.83k
  switch (typeId) {
429
78
    case xmpAlt:
430
78
      xa = xaAlt;
431
78
      break;
432
160
    case xmpBag:
433
160
      xa = xaBag;
434
160
      break;
435
4.21k
    case xmpSeq:
436
4.21k
      xa = xaSeq;
437
4.21k
      break;
438
378
    default:
439
378
      break;
440
4.83k
  }
441
4.83k
  return xa;
442
4.83k
}
443
444
55.7k
XmpValue::XmpStruct XmpValue::xmpStruct() const {
445
55.7k
  return xmpStruct_;
446
55.7k
}
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
34.0k
XmpTextValue::XmpTextValue() : XmpValue(xmpText) {
469
34.0k
}
470
471
0
XmpTextValue::XmpTextValue(const std::string& buf) : XmpValue(xmpText) {
472
0
  read(buf);
473
0
}
474
475
810k
int XmpTextValue::read(const std::string& buf) {
476
  // support a type=Alt,Bag,Seq,Struct indicator
477
810k
  std::string b = buf;
478
810k
  std::string type;
479
810k
  if (buf.starts_with("type=")) {
480
417
    std::string::size_type pos = buf.find_first_of(' ');
481
417
    type = buf.substr(5, pos - 5);
482
    // Strip quotes (so you can also specify the type without quotes)
483
417
    if (!type.empty() && type.front() == '"')
484
197
      type = type.substr(1);
485
417
    if (!type.empty() && type.back() == '"')
486
111
      type.pop_back();
487
417
    b.clear();
488
417
    if (pos != std::string::npos)
489
157
      b = buf.substr(pos + 1);
490
417
  }
491
810k
  if (!type.empty()) {
492
264
    if (type == "Alt") {
493
54
      setXmpArrayType(XmpValue::xaAlt);
494
210
    } else if (type == "Bag") {
495
17
      setXmpArrayType(XmpValue::xaBag);
496
193
    } else if (type == "Seq") {
497
26
      setXmpArrayType(XmpValue::xaSeq);
498
167
    } else if (type == "Struct") {
499
10
      setXmpStruct();
500
157
    } else {
501
157
      throw Error(ErrorCode::kerInvalidXmpText, type);
502
157
    }
503
264
  }
504
810k
  value_ = std::move(b);
505
810k
  return 0;
506
810k
}
507
508
0
XmpTextValue::UniquePtr XmpTextValue::clone() const {
509
0
  return UniquePtr(clone_());
510
0
}
511
512
21.9k
size_t XmpTextValue::size() const {
513
21.9k
  std::ostringstream os;
514
21.9k
  write(os);
515
21.9k
  return os.str().size();
516
21.9k
}
517
518
21.9k
size_t XmpTextValue::count() const {
519
21.9k
  return size();
520
21.9k
}
521
522
47.7k
std::ostream& XmpTextValue::write(std::ostream& os) const {
523
47.7k
  bool del = false;
524
47.7k
  if (xmpArrayType() != XmpValue::xaNone) {
525
419
    switch (xmpArrayType()) {
526
30
      case XmpValue::xaAlt:
527
30
        os << "type=\"Alt\"";
528
30
        break;
529
12
      case XmpValue::xaBag:
530
12
        os << "type=\"Bag\"";
531
12
        break;
532
377
      case XmpValue::xaSeq:
533
377
        os << "type=\"Seq\"";
534
377
        break;
535
0
      case XmpValue::xaNone:
536
0
        break;  // just to suppress the warning
537
419
    }
538
419
    del = true;
539
47.3k
  } else if (xmpStruct() != XmpValue::xsNone) {
540
965
    switch (xmpStruct()) {
541
965
      case XmpValue::xsStruct:
542
965
        os << "type=\"Struct\"";
543
965
        break;
544
0
      case XmpValue::xsNone:
545
0
        break;  // just to suppress the warning
546
965
    }
547
965
    del = true;
548
965
  }
549
47.7k
  if (del && !value_.empty())
550
8
    os << " ";
551
47.7k
  return os << value_;
552
47.7k
}
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
49.8k
XmpTextValue* XmpTextValue::clone_() const {
571
49.8k
  return new XmpTextValue(*this);
572
49.8k
}
573
574
4.28k
XmpArrayValue::XmpArrayValue(TypeId typeId) : XmpValue(typeId) {
575
4.28k
  setXmpArrayType(xmpArrayType(typeId));
576
4.28k
}
577
578
25.9k
int XmpArrayValue::read(const std::string& buf) {
579
25.9k
  if (!buf.empty())
580
20.1k
    value_.push_back(buf);
581
25.9k
  return 0;
582
25.9k
}
583
584
0
XmpArrayValue::UniquePtr XmpArrayValue::clone() const {
585
0
  return UniquePtr(clone_());
586
0
}
587
588
2.97k
size_t XmpArrayValue::count() const {
589
2.97k
  return value_.size();
590
2.97k
}
591
592
1.83k
std::ostream& XmpArrayValue::write(std::ostream& os) const {
593
1.83k
  if (!value_.empty()) {
594
1.52k
    std::copy(value_.begin(), value_.end() - 1, std::ostream_iterator<std::string>(os, ", "));
595
1.52k
    os << value_.back();
596
1.52k
  }
597
1.83k
  return os;
598
1.83k
}
599
600
1.61k
std::string XmpArrayValue::toString(size_t n) const {
601
1.61k
  ok_ = true;
602
1.61k
  return value_.at(n);
603
1.61k
}
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
7.54k
XmpArrayValue* XmpArrayValue::clone_() const {
622
7.54k
  return new XmpArrayValue(*this);
623
7.54k
}
624
625
172
LangAltValue::LangAltValue() : XmpValue(langAlt) {
626
172
}
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
188
size_t LangAltValue::count() const {
680
188
  return value_.size();
681
188
}
682
683
188
std::ostream& LangAltValue::write(std::ostream& os) const {
684
188
  bool first = true;
685
686
  // Write the default entry first
687
188
  if (auto i = value_.find("x-default"); i != value_.end()) {
688
26
    os << "lang=\"" << i->first << "\" " << i->second;
689
26
    first = false;
690
26
  }
691
692
  // Write the others
693
188
  for (const auto& [lang, s] : value_) {
694
186
    if (lang != "x-default") {
695
160
      if (!first)
696
14
        os << ", ";
697
160
      os << "lang=\"" << lang << "\" " << s;
698
160
      first = false;
699
160
    }
700
186
  }
701
188
  return os;
702
188
}
703
704
0
std::string LangAltValue::toString(size_t /*n*/) const {
705
0
  return toString("x-default");
706
0
}
707
708
0
std::string LangAltValue::toString(const std::string& qualifier) const {
709
0
  if (auto i = value_.find(qualifier); i != value_.end()) {
710
0
    ok_ = true;
711
0
    return i->second;
712
0
  }
713
0
  ok_ = false;
714
0
  return "";
715
0
}
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
385
LangAltValue* LangAltValue::clone_() const {
738
385
  return new LangAltValue(*this);
739
385
}
740
741
3.15k
DateValue::DateValue() : Value(date) {
742
3.15k
  date_ = {};
743
3.15k
}
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
3.00k
int DateValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) {
750
3.00k
  const std::string str(reinterpret_cast<const char*>(buf), len);
751
3.00k
  return read(str);
752
3.00k
}
753
754
3.15k
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
3.15k
  size_t monthPos = 0;
758
3.15k
  size_t dayPos = 0;
759
760
4.89k
  auto printWarning = [] {
761
4.89k
#ifndef SUPPRESS_WARNINGS
762
4.89k
    EXV_WARNING << Error(ErrorCode::kerUnsupportedDateFormat) << "\n";
763
4.89k
#endif
764
4.89k
  };
765
766
3.15k
  if (buf.size() < 8) {
767
349
    printWarning();
768
349
    return 1;
769
349
  }
770
771
2.80k
  if ((buf.size() >= 10 && buf[4] == '-' && buf[7] == '-') || (buf.size() == 8)) {
772
2.27k
    if (buf.size() >= 10) {
773
164
      monthPos = 5;
774
164
      dayPos = 8;
775
2.10k
    } else {
776
2.10k
      monthPos = 4;
777
2.10k
      dayPos = 6;
778
2.10k
    }
779
780
3.50k
    auto checkDigits = [&buf, &printWarning](size_t start, size_t count, int32_t& dest) {
781
8.46k
      for (size_t i = start; i < start + count; ++i) {
782
6.93k
        if (!std::isdigit(buf[i])) {
783
1.97k
          printWarning();
784
1.97k
          return 1;
785
1.97k
        }
786
6.93k
      }
787
1.52k
      dest = std::stoul(buf.substr(start, count));
788
1.52k
      return 0;
789
3.50k
    };
790
791
2.27k
    if (checkDigits(0, 4, date_.year) || checkDigits(monthPos, 2, date_.month) || checkDigits(dayPos, 2, date_.day)) {
792
1.97k
      printWarning();
793
1.97k
      return 1;
794
1.97k
    }
795
796
294
    if (date_.month > 12 || date_.day > 31) {
797
57
      date_.month = 0;
798
57
      date_.day = 0;
799
57
      printWarning();
800
57
      return 1;
801
57
    }
802
237
    return 0;
803
294
  }
804
532
  printWarning();
805
532
  return 1;
806
2.80k
}
807
808
0
void DateValue::setDate(const Date& src) {
809
0
  date_ = src;
810
0
}
811
812
149
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
149
  auto out = reinterpret_cast<char*>(buf);
817
149
  auto it = stringFormatTo(out, "{:04}{:02}{:02}", date_.year, date_.month, date_.day);
818
819
149
  return it - out;
820
149
}
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
298
size_t DateValue::size() const {
831
298
  return 8;
832
298
}
833
834
1.62k
DateValue* DateValue::clone_() const {
835
1.62k
  return new DateValue(*this);
836
1.62k
}
837
838
608
std::ostream& DateValue::write(std::ostream& os) const {
839
  // Write DateValue in ISO 8601 Extended format: YYYY-MM-DD
840
608
  return os << stringFormat("{:04}-{:02}-{:02}", date_.year, date_.month, date_.day);
841
608
}
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
3.26k
TimeValue::TimeValue() : Value(time) {
878
3.26k
  time_ = {};
879
3.26k
}
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
3.26k
int TimeValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) {
886
3.26k
  const std::string str(reinterpret_cast<const char*>(buf), len);
887
3.26k
  return read(str);
888
3.26k
}
889
890
3.26k
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
3.26k
  auto printWarning = [] {
896
2.37k
#ifndef SUPPRESS_WARNINGS
897
2.37k
    EXV_WARNING << Error(ErrorCode::kerUnsupportedTimeFormat) << "\n";
898
2.37k
#endif
899
2.37k
    return 1;
900
2.37k
  };
901
902
3.26k
  if (buf.size() < 2)
903
112
    return printWarning();
904
905
3.15k
  for (auto c : buf)
906
28.6k
    if (c != ':' && c != '+' && c != '-' && c != 'Z' && !std::isdigit(c))
907
1.04k
      return printWarning();
908
909
2.10k
  size_t mpos;
910
2.10k
  size_t spos;
911
2.10k
  if (buf.find(':') != std::string::npos) {
912
809
    mpos = 3;
913
809
    spos = 6;
914
1.30k
  } else {
915
1.30k
    mpos = 2;
916
1.30k
    spos = 4;
917
1.30k
  }
918
919
2.10k
  try {
920
2.10k
    auto hi = std::stoi(buf.substr(0, 2));
921
2.10k
    if (hi < 0 || hi > 23)
922
317
      return printWarning();
923
1.79k
    time_.hour = hi;
924
1.79k
    if (buf.size() > 3) {
925
1.70k
      auto mi = std::stoi(buf.substr(mpos, 2));
926
1.70k
      if (mi < 0 || mi > 59)
927
127
        return printWarning();
928
1.57k
      time_.minute = std::stoi(buf.substr(mpos, 2));
929
1.57k
    } else {
930
86
      time_.minute = 0;
931
86
    }
932
1.66k
    if (buf.size() > 5) {
933
1.39k
      auto si = std::stoi(buf.substr(spos, 2));
934
1.39k
      if (si < 0 || si > 60)
935
128
        return printWarning();
936
1.26k
      time_.second = std::stoi(buf.substr(spos, 2));
937
1.26k
    } else {
938
275
      time_.second = 0;
939
275
    }
940
941
1.53k
    auto fpos = buf.find('+');
942
1.53k
    if (fpos == std::string::npos)
943
1.22k
      fpos = buf.find('-');
944
945
1.53k
    if (fpos != std::string::npos) {
946
1.16k
      auto format = buf.substr(fpos, buf.size());
947
      // Use the sign of the raw offset string rather than of the parsed
948
      // tzHour: when the hour magnitude is 0 (e.g. "-00:30"), std::stoi
949
      // returns 0, which loses the '-' sign that std::stoi("-00") cannot
950
      // preserve. format always starts with '+' or '-' (see fpos above).
951
1.16k
      const bool negative = format.at(0) == '-';
952
1.16k
      auto posColon = format.find(':');
953
1.16k
      if (posColon == std::string::npos) {
954
        // Extended format
955
781
        auto tzhi = std::stoi(format.substr(0, 3));
956
781
        if (tzhi < -23 || tzhi > 23)
957
102
          return printWarning();
958
679
        time_.tzHour = tzhi;
959
679
        if (format.size() > 3) {
960
464
          int minute = std::stoi(format.substr(3));
961
464
          if (minute < 0 || minute > 59)
962
90
            return printWarning();
963
374
          time_.tzMinute = negative ? -minute : minute;
964
374
        }
965
679
      } else {
966
        // Basic format
967
382
        auto tzhi = std::stoi(format.substr(0, posColon));
968
382
        if (tzhi < -23 || tzhi > 23)
969
50
          return printWarning();
970
332
        time_.tzHour = tzhi;
971
332
        int minute = std::stoi(format.substr(posColon + 1));
972
332
        if (minute < 0 || minute > 59)
973
106
          return printWarning();
974
226
        time_.tzMinute = negative ? -minute : minute;
975
226
      }
976
1.16k
    }
977
1.53k
  } catch (std::exception&) {
978
    // std::stoi might throw an exception if the syntax is invalid.
979
296
    return printWarning();
980
296
  }
981
893
  return 0;
982
2.10k
}
983
984
/// \todo not used internally. At least we should test it
985
0
void TimeValue::setTime(const Time& src) {
986
0
  time_ = src;
987
0
}
988
989
210
size_t TimeValue::copy(byte* buf, ByteOrder /*byteOrder*/) const {
990
  // NOTE: Here the time is copied in the Basic format HHMMSS:HHMM, as the IPTC key
991
  // Iptc.Application2.TimeCreated wants it. Check https://exiv2.org/iptc.html
992
210
  char plusMinus = '+';
993
210
  if (time_.tzHour < 0 || time_.tzMinute < 0)
994
29
    plusMinus = '-';
995
996
210
  auto out = reinterpret_cast<char*>(buf);
997
210
  auto it = stringFormatTo(out, "{:02}{:02}{:02}{}{:02}{:02}", time_.hour, time_.minute, time_.second, plusMinus,
998
210
                           std::abs(time_.tzHour), std::abs(time_.tzMinute));
999
1000
210
  auto wrote = static_cast<size_t>(it - out);
1001
210
  Internal::enforce(wrote == 11, Exiv2::ErrorCode::kerUnsupportedTimeFormat);
1002
210
  return wrote;
1003
210
}
1004
1005
0
const TimeValue::Time& TimeValue::getTime() const {
1006
0
  return time_;
1007
0
}
1008
1009
0
size_t TimeValue::count() const {
1010
0
  return size();
1011
0
}
1012
1013
420
size_t TimeValue::size() const {
1014
420
  return 11;
1015
420
}
1016
1017
1.61k
TimeValue* TimeValue::clone_() const {
1018
1.61k
  return new TimeValue(*this);
1019
1.61k
}
1020
1021
146
std::ostream& TimeValue::write(std::ostream& os) const {
1022
  // Write TimeValue in ISO 8601 Extended format: hh:mm:ss±hh:mm
1023
146
  char plusMinus = '+';
1024
146
  if (time_.tzHour < 0 || time_.tzMinute < 0)
1025
56
    plusMinus = '-';
1026
1027
146
  return os << stringFormat("{:02}:{:02}:{:02}{}{:02}:{:02}", time_.hour, time_.minute, time_.second, plusMinus,
1028
146
                            std::abs(time_.tzHour), std::abs(time_.tzMinute));
1029
146
}
1030
1031
0
int64_t TimeValue::toInt64(size_t /*n*/) const {
1032
  // Returns number of seconds in the day in UTC.
1033
0
  auto result = static_cast<int64_t>(time_.hour - time_.tzHour) * 60 * 60;
1034
0
  result += static_cast<int64_t>(time_.minute - time_.tzMinute) * 60;
1035
0
  result += time_.second;
1036
0
  if (result < 0) {
1037
0
    result += 86400;
1038
0
  }
1039
0
  ok_ = true;
1040
0
  return result;
1041
0
}
1042
1043
0
uint32_t TimeValue::toUint32(size_t /*n*/) const {
1044
0
  return static_cast<uint32_t>(std::clamp<int64_t>(toInt64(), 0, std::numeric_limits<uint32_t>::max()));
1045
0
}
1046
1047
0
float TimeValue::toFloat(size_t n) const {
1048
0
  return static_cast<float>(toInt64(n));
1049
0
}
1050
1051
0
Rational TimeValue::toRational(size_t n) const {
1052
0
  return {static_cast<int32_t>(toInt64(n)), 1};
1053
0
}
1054
1055
}  // namespace Exiv2