Coverage Report

Created: 2025-10-13 07:11

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