Coverage Report

Created: 2026-01-10 06:22

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