Coverage Report

Created: 2026-01-09 06:34

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
108M
Value::Value(TypeId typeId) : type_(typeId) {
20
108M
}
21
22
40.4M
Value::UniquePtr Value::create(TypeId typeId) {
23
40.4M
  switch (typeId) {
24
0
    case invalidTypeId:
25
8.92k
    case signedByte:
26
3.30M
    case unsignedByte:
27
3.30M
      return std::make_unique<DataValue>(typeId);
28
121k
    case asciiString:
29
121k
      return std::make_unique<AsciiValue>();
30
29.7M
    case unsignedShort:
31
29.7M
      return std::make_unique<ValueType<uint16_t>>();
32
238k
    case unsignedLong:
33
266k
    case tiffIfd:
34
266k
      return std::make_unique<ValueType<uint32_t>>(typeId);
35
57.7k
    case unsignedRational:
36
57.7k
      return std::make_unique<ValueType<URational>>();
37
131k
    case undefined:
38
131k
      return std::make_unique<DataValue>();
39
3.61M
    case signedShort:
40
3.61M
      return std::make_unique<ValueType<int16_t>>();
41
80.8k
    case signedLong:
42
80.8k
      return std::make_unique<ValueType<int32_t>>();
43
42.4k
    case signedRational:
44
42.4k
      return std::make_unique<ValueType<Rational>>();
45
10.4k
    case tiffFloat:
46
10.4k
      return std::make_unique<ValueType<float>>();
47
10.3k
    case tiffDouble:
48
10.3k
      return std::make_unique<ValueType<double>>();
49
62.8k
    case string:
50
62.8k
      return std::make_unique<StringValue>();
51
1.68k
    case date:
52
1.68k
      return std::make_unique<DateValue>();
53
2.07k
    case time:
54
2.07k
      return std::make_unique<TimeValue>();
55
3.38k
    case comment:
56
3.38k
      return std::make_unique<CommentValue>();
57
28.4k
    case xmpText:
58
28.4k
      return std::make_unique<XmpTextValue>();
59
0
    case xmpBag:
60
3.54k
    case xmpSeq:
61
3.54k
    case xmpAlt:
62
3.54k
      return std::make_unique<XmpArrayValue>(typeId);
63
0
    case langAlt:
64
0
      return std::make_unique<LangAltValue>();
65
3.02M
    default:
66
3.02M
      return std::make_unique<DataValue>(typeId);
67
40.4M
  }
68
40.4M
}  // Value::create
69
70
1.47k
int Value::setDataArea(const byte* /*buf*/, size_t /*len*/) {
71
1.47k
  return -1;
72
1.47k
}
73
74
142k
std::string Value::toString() const {
75
142k
  std::ostringstream os;
76
142k
  write(os);
77
142k
  ok_ = !os.fail();
78
142k
  return os.str();
79
142k
}
80
81
18.7k
std::string Value::toString(size_t /*n*/) const {
82
18.7k
  return toString();
83
18.7k
}
84
85
61.2k
size_t Value::sizeDataArea() const {
86
61.2k
  return 0;
87
61.2k
}
88
89
5.32k
DataBuf Value::dataArea() const {
90
5.32k
  return {nullptr, 0};
91
5.32k
}
92
93
6.46M
DataValue::DataValue(TypeId typeId) : Value(typeId) {
94
6.46M
}
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
11.9M
size_t DataValue::count() const {
101
11.9M
  return size();
102
11.9M
}
103
104
6.46M
int DataValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) {
105
  // byteOrder not needed
106
6.46M
  value_.assign(buf, buf + len);
107
6.46M
  return 0;
108
6.46M
}
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
3.12M
size_t DataValue::copy(byte* buf, ByteOrder /*byteOrder*/) const {
123
  // byteOrder not needed
124
3.12M
  return std::copy(value_.begin(), value_.end(), buf) - buf;
125
3.12M
}
126
127
22.9M
size_t DataValue::size() const {
128
22.9M
  return value_.size();
129
22.9M
}
130
131
19.2M
DataValue* DataValue::clone_() const {
132
19.2M
  return new DataValue(*this);
133
19.2M
}
134
135
135k
std::ostream& DataValue::write(std::ostream& os) const {
136
135k
  if (!value_.empty()) {
137
135k
    std::copy(value_.begin(), value_.end() - 1, std::ostream_iterator<int>(os, " "));
138
135k
    os << static_cast<int>(value_.back());
139
135k
  }
140
135k
  return os;
141
135k
}
142
143
4.13k
std::string DataValue::toString(size_t n) const {
144
4.13k
  ok_ = true;
145
4.13k
  return std::to_string(value_.at(n));
146
4.13k
}
147
148
224k
int64_t DataValue::toInt64(size_t n) const {
149
224k
  ok_ = true;
150
224k
  return value_.at(n);
151
224k
}
152
153
59.9k
uint32_t DataValue::toUint32(size_t n) const {
154
59.9k
  ok_ = true;
155
59.9k
  return value_.at(n);
156
59.9k
}
157
158
2.02k
float DataValue::toFloat(size_t n) const {
159
2.02k
  ok_ = true;
160
2.02k
  return value_.at(n);
161
2.02k
}
162
163
2.77k
Rational DataValue::toRational(size_t n) const {
164
2.77k
  ok_ = true;
165
2.77k
  return {value_.at(n), 1};
166
2.77k
}
167
168
0
StringValueBase::StringValueBase(TypeId typeId, const std::string& buf) : Value(typeId) {
169
0
  read(buf);
170
0
}
171
172
175
int StringValueBase::read(const std::string& buf) {
173
175
  value_ = buf;
174
175
  return 0;
175
175
}
176
177
170k
int StringValueBase::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) {
178
  // byteOrder not needed
179
170k
  if (buf)
180
170k
    value_ = std::string(reinterpret_cast<const char*>(buf), len);
181
170k
  return 0;
182
170k
}
183
184
91.5k
size_t StringValueBase::copy(byte* buf, ByteOrder /*byteOrder*/) const {
185
91.5k
  if (value_.empty())
186
35.7k
    return 0;
187
  // byteOrder not needed
188
55.7k
  return value_.copy(reinterpret_cast<char*>(buf), value_.size());
189
91.5k
}
190
191
259k
size_t StringValueBase::count() const {
192
259k
  return size();
193
259k
}
194
195
495k
size_t StringValueBase::size() const {
196
495k
  return value_.size();
197
495k
}
198
199
1.28k
std::ostream& StringValueBase::write(std::ostream& os) const {
200
1.28k
  return os << value_;
201
1.28k
}
202
203
27.1k
int64_t StringValueBase::toInt64(size_t n) const {
204
27.1k
  ok_ = true;
205
27.1k
  return value_.at(n);
206
27.1k
}
207
208
3.81k
uint32_t StringValueBase::toUint32(size_t n) const {
209
3.81k
  ok_ = true;
210
3.81k
  return value_.at(n);
211
3.81k
}
212
213
472
float StringValueBase::toFloat(size_t n) const {
214
472
  ok_ = true;
215
472
  return value_.at(n);
216
472
}
217
218
528
Rational StringValueBase::toRational(size_t n) const {
219
528
  ok_ = true;
220
528
  return {value_.at(n), 1};
221
528
}
222
223
62.8k
StringValue::StringValue() : StringValueBase(string) {
224
62.8k
}
225
226
0
StringValue::StringValue(const std::string& buf) : StringValueBase(string, buf) {
227
0
}
228
229
812k
StringValue* StringValue::clone_() const {
230
812k
  return new StringValue(*this);
231
812k
}
232
233
121k
AsciiValue::AsciiValue() : StringValueBase(asciiString) {
234
121k
}
235
236
0
AsciiValue::AsciiValue(const std::string& buf) : StringValueBase(asciiString, buf) {
237
0
}
238
239
35.5k
int AsciiValue::read(const std::string& buf) {
240
35.5k
  value_ = buf;
241
  // ensure count>0 and nul terminated # https://github.com/Exiv2/exiv2/issues/1484
242
35.5k
  if (value_.empty() || value_.back() != '\0') {
243
35.5k
    value_ += '\0';
244
35.5k
  }
245
35.5k
  return 0;
246
35.5k
}
247
248
267k
AsciiValue* AsciiValue::clone_() const {
249
267k
  return new AsciiValue(*this);
250
267k
}
251
252
174k
std::ostream& AsciiValue::write(std::ostream& os) const {
253
  // Write only up to the first '\0' (if any)
254
174k
  std::string::size_type pos = value_.find_first_of('\0');
255
174k
  if (pos == std::string::npos)
256
6.94k
    pos = value_.size();
257
174k
  return os << value_.substr(0, pos);
258
174k
}
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.24k
const char* CommentValue::CharsetInfo::name(CharsetId charsetId) {
271
1.24k
  return charsetTable_[charsetId < lastCharsetId ? charsetId : undefined].name_;
272
1.24k
}
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
6.19k
CommentValue::CharsetId CommentValue::CharsetInfo::charsetIdByCode(const std::string& code) {
286
6.19k
  int i = 0;
287
24.6k
  for (; charsetTable_[i].charsetId_ != lastCharsetId && std::string(charsetTable_[i].code_, 8) != code; ++i) {
288
18.4k
  }
289
6.19k
  return charsetTable_[i].charsetId_ == lastCharsetId ? invalidCharsetId : charsetTable_[i].charsetId_;
290
6.19k
}
291
292
3.38k
CommentValue::CommentValue() : StringValueBase(Exiv2::undefined) {
293
3.38k
}
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.38k
int CommentValue::read(const byte* buf, size_t len, ByteOrder byteOrder) {
330
3.38k
  byteOrder_ = byteOrder;
331
3.38k
  return StringValueBase::read(buf, len, byteOrder);
332
3.38k
}
333
334
533
size_t CommentValue::copy(byte* buf, ByteOrder byteOrder) const {
335
533
  std::string c = value_;
336
533
  if (charsetId() == unicode) {
337
57
    c = value_.substr(8);
338
57
    [[maybe_unused]] const size_t sz = c.size();
339
57
    if (byteOrder_ == littleEndian && byteOrder == bigEndian) {
340
0
      convertStringCharset(c, "UCS-2LE", "UCS-2BE");
341
57
    } else if (byteOrder_ == bigEndian && byteOrder == littleEndian) {
342
0
      convertStringCharset(c, "UCS-2BE", "UCS-2LE");
343
0
    }
344
57
    c = value_.substr(0, 8) + c;
345
57
  }
346
533
  if (c.empty())
347
336
    return 0;
348
197
  return c.copy(reinterpret_cast<char*>(buf), c.size());
349
533
}
350
351
1.67k
std::ostream& CommentValue::write(std::ostream& os) const {
352
1.67k
  CharsetId csId = charsetId();
353
1.67k
  std::string text = comment();
354
1.67k
  if (csId != undefined) {
355
1.24k
    os << "charset=" << CharsetInfo::name(csId) << " ";
356
1.24k
  }
357
1.67k
  return os << text;
358
1.67k
}
359
360
1.67k
std::string CommentValue::comment(const char* encoding) const {
361
1.67k
  std::string c;
362
1.67k
  if (value_.length() < 8) {
363
82
    return c;
364
82
  }
365
1.59k
  c = value_.substr(8);
366
1.59k
  if (charsetId() == unicode) {
367
787
    const char* from = !encoding || *encoding == '\0' ? detectCharset(c) : encoding;
368
787
    if (!convertStringCharset(c, from, "UTF-8"))
369
31
      throw Error(ErrorCode::kerInvalidIconvEncoding, from, "UTF-8");
370
787
  }
371
372
  // # 1266 Remove trailing nulls
373
1.56k
  if (charsetId() == undefined || charsetId() == ascii) {
374
360
    auto n = c.find('\0');
375
360
    if (n != std::string::npos)
376
264
      c.resize(n);
377
360
  }
378
1.56k
  return c;
379
1.59k
}
380
381
6.61k
CommentValue::CharsetId CommentValue::charsetId() const {
382
6.61k
  CharsetId charsetId = undefined;
383
6.61k
  if (value_.length() >= 8) {
384
6.19k
    const std::string code = value_.substr(0, 8);
385
6.19k
    charsetId = CharsetInfo::charsetIdByCode(code);
386
6.19k
  }
387
6.61k
  return charsetId;
388
6.61k
}
389
390
787
const char* CommentValue::detectCharset(std::string& c) const {
391
  // Interpret a BOM if there is one
392
787
  if (c.front() == '\xef' && c[1] == '\xbb' && c[2] == '\xbf') {
393
54
    c = c.substr(3);
394
54
    return "UTF-8";
395
54
  }
396
733
  if (c.front() == '\xff' && c[1] == '\xfe') {
397
110
    c = c.substr(2);
398
110
    return "UCS-2LE";
399
110
  }
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
7.54k
CommentValue* CommentValue::clone_() const {
411
7.54k
  return new CommentValue(*this);
412
7.54k
}
413
414
5.83k
void XmpValue::setXmpArrayType(XmpArrayType xmpArrayType) {
415
5.83k
  xmpArrayType_ = xmpArrayType;
416
5.83k
}
417
418
930
void XmpValue::setXmpStruct(XmpStruct xmpStruct) {
419
930
  xmpStruct_ = xmpStruct;
420
930
}
421
422
49.0k
XmpValue::XmpArrayType XmpValue::xmpArrayType() const {
423
49.0k
  return xmpArrayType_;
424
49.0k
}
425
426
5.72k
XmpValue::XmpArrayType XmpValue::xmpArrayType(TypeId typeId) {
427
5.72k
  XmpArrayType xa = xaNone;
428
5.72k
  switch (typeId) {
429
50
    case xmpAlt:
430
50
      xa = xaAlt;
431
50
      break;
432
223
    case xmpBag:
433
223
      xa = xaBag;
434
223
      break;
435
4.85k
    case xmpSeq:
436
4.85k
      xa = xaSeq;
437
4.85k
      break;
438
600
    default:
439
600
      break;
440
5.72k
  }
441
5.72k
  return xa;
442
5.72k
}
443
444
49.1k
XmpValue::XmpStruct XmpValue::xmpStruct() const {
445
49.1k
  return xmpStruct_;
446
49.1k
}
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
48.0k
XmpTextValue::XmpTextValue() : XmpValue(xmpText) {
469
48.0k
}
470
471
0
XmpTextValue::XmpTextValue(const std::string& buf) : XmpValue(xmpText) {
472
0
  read(buf);
473
0
}
474
475
806k
int XmpTextValue::read(const std::string& buf) {
476
  // support a type=Alt,Bag,Seq,Struct indicator
477
806k
  std::string b = buf;
478
806k
  std::string type;
479
806k
  if (buf.starts_with("type=")) {
480
441
    std::string::size_type pos = buf.find_first_of(' ');
481
441
    type = buf.substr(5, pos - 5);
482
    // Strip quotes (so you can also specify the type without quotes)
483
441
    if (!type.empty() && type.front() == '"')
484
206
      type = type.substr(1);
485
441
    if (!type.empty() && type.back() == '"')
486
121
      type.pop_back();
487
441
    b.clear();
488
441
    if (pos != std::string::npos)
489
171
      b = buf.substr(pos + 1);
490
441
  }
491
806k
  if (!type.empty()) {
492
289
    if (type == "Alt") {
493
56
      setXmpArrayType(XmpValue::xaAlt);
494
233
    } else if (type == "Bag") {
495
19
      setXmpArrayType(XmpValue::xaBag);
496
214
    } else if (type == "Seq") {
497
32
      setXmpArrayType(XmpValue::xaSeq);
498
182
    } else if (type == "Struct") {
499
36
      setXmpStruct();
500
146
    } else {
501
146
      throw Error(ErrorCode::kerInvalidXmpText, type);
502
146
    }
503
289
  }
504
806k
  value_ = std::move(b);
505
806k
  return 0;
506
806k
}
507
508
0
XmpTextValue::UniquePtr XmpTextValue::clone() const {
509
0
  return UniquePtr(clone_());
510
0
}
511
512
35.2k
size_t XmpTextValue::size() const {
513
35.2k
  return value_.size();
514
35.2k
}
515
516
35.2k
size_t XmpTextValue::count() const {
517
35.2k
  return size();
518
35.2k
}
519
520
41.6k
std::ostream& XmpTextValue::write(std::ostream& os) const {
521
41.6k
  bool del = false;
522
41.6k
  if (xmpArrayType() != XmpValue::xaNone) {
523
240
    switch (xmpArrayType()) {
524
22
      case XmpValue::xaAlt:
525
22
        os << "type=\"Alt\"";
526
22
        break;
527
2
      case XmpValue::xaBag:
528
2
        os << "type=\"Bag\"";
529
2
        break;
530
216
      case XmpValue::xaSeq:
531
216
        os << "type=\"Seq\"";
532
216
        break;
533
0
      case XmpValue::xaNone:
534
0
        break;  // just to suppress the warning
535
240
    }
536
240
    del = true;
537
41.4k
  } else if (xmpStruct() != XmpValue::xsNone) {
538
616
    switch (xmpStruct()) {
539
616
      case XmpValue::xsStruct:
540
616
        os << "type=\"Struct\"";
541
616
        break;
542
0
      case XmpValue::xsNone:
543
0
        break;  // just to suppress the warning
544
616
    }
545
616
    del = true;
546
616
  }
547
41.6k
  if (del && !value_.empty())
548
2
    os << " ";
549
41.6k
  return os << value_;
550
41.6k
}
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
90.1k
XmpTextValue* XmpTextValue::clone_() const {
569
90.1k
  return new XmpTextValue(*this);
570
90.1k
}
571
572
4.83k
XmpArrayValue::XmpArrayValue(TypeId typeId) : XmpValue(typeId) {
573
4.83k
  setXmpArrayType(xmpArrayType(typeId));
574
4.83k
}
575
576
22.4k
int XmpArrayValue::read(const std::string& buf) {
577
22.4k
  if (!buf.empty())
578
15.1k
    value_.push_back(buf);
579
22.4k
  return 0;
580
22.4k
}
581
582
0
XmpArrayValue::UniquePtr XmpArrayValue::clone() const {
583
0
  return UniquePtr(clone_());
584
0
}
585
586
4.12k
size_t XmpArrayValue::count() const {
587
4.12k
  return value_.size();
588
4.12k
}
589
590
2.09k
std::ostream& XmpArrayValue::write(std::ostream& os) const {
591
2.09k
  if (!value_.empty()) {
592
1.70k
    std::copy(value_.begin(), value_.end() - 1, std::ostream_iterator<std::string>(os, ", "));
593
1.70k
    os << value_.back();
594
1.70k
  }
595
2.09k
  return os;
596
2.09k
}
597
598
2.89k
std::string XmpArrayValue::toString(size_t n) const {
599
2.89k
  ok_ = true;
600
2.89k
  return value_.at(n);
601
2.89k
}
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
13.2k
XmpArrayValue* XmpArrayValue::clone_() const {
620
13.2k
  return new XmpArrayValue(*this);
621
13.2k
}
622
623
290
LangAltValue::LangAltValue() : XmpValue(langAlt) {
624
290
}
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
308
size_t LangAltValue::count() const {
678
308
  return value_.size();
679
308
}
680
681
308
std::ostream& LangAltValue::write(std::ostream& os) const {
682
308
  bool first = true;
683
684
  // Write the default entry first
685
308
  if (auto i = value_.find("x-default"); i != value_.end()) {
686
92
    os << "lang=\"" << i->first << "\" " << i->second;
687
92
    first = false;
688
92
  }
689
690
  // Write the others
691
308
  for (const auto& [lang, s] : value_) {
692
284
    if (lang != "x-default") {
693
192
      if (!first)
694
0
        os << ", ";
695
192
      os << "lang=\"" << lang << "\" " << s;
696
192
      first = false;
697
192
    }
698
284
  }
699
308
  return os;
700
308
}
701
702
0
std::string LangAltValue::toString(size_t /*n*/) const {
703
0
  return toString("x-default");
704
0
}
705
706
0
std::string LangAltValue::toString(const std::string& qualifier) const {
707
0
  if (auto i = value_.find(qualifier); i != value_.end()) {
708
0
    ok_ = true;
709
0
    return i->second;
710
0
  }
711
0
  ok_ = false;
712
0
  return "";
713
0
}
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
1.22k
LangAltValue* LangAltValue::clone_() const {
736
1.22k
  return new LangAltValue(*this);
737
1.22k
}
738
739
1.68k
DateValue::DateValue() : Value(date) {
740
1.68k
  date_ = {};
741
1.68k
}
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
1.51k
int DateValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) {
748
1.51k
  const std::string str(reinterpret_cast<const char*>(buf), len);
749
1.51k
  return read(str);
750
1.51k
}
751
752
1.68k
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
1.68k
  size_t monthPos = 0;
756
1.68k
  size_t dayPos = 0;
757
758
2.23k
  auto printWarning = [] {
759
2.23k
#ifndef SUPPRESS_WARNINGS
760
2.23k
    EXV_WARNING << Error(ErrorCode::kerUnsupportedDateFormat) << "\n";
761
2.23k
#endif
762
2.23k
  };
763
764
1.68k
  if (buf.size() < 8) {
765
293
    printWarning();
766
293
    return 1;
767
293
  }
768
769
1.39k
  if ((buf.size() >= 10 && buf[4] == '-' && buf[7] == '-') || (buf.size() == 8)) {
770
953
    if (buf.size() >= 10) {
771
75
      monthPos = 5;
772
75
      dayPos = 8;
773
878
    } else {
774
878
      monthPos = 4;
775
878
      dayPos = 6;
776
878
    }
777
778
1.77k
    auto checkDigits = [&buf, &printWarning](size_t start, size_t count, int32_t& dest) {
779
4.80k
      for (size_t i = start; i < start + count; ++i) {
780
3.75k
        if (!std::isdigit(buf[i])) {
781
722
          printWarning();
782
722
          return 1;
783
722
        }
784
3.75k
      }
785
1.05k
      dest = std::stoul(buf.substr(start, count));
786
1.05k
      return 0;
787
1.77k
    };
788
789
953
    if (checkDigits(0, 4, date_.year) || checkDigits(monthPos, 2, date_.month) || checkDigits(dayPos, 2, date_.day)) {
790
722
      printWarning();
791
722
      return 1;
792
722
    }
793
794
231
    if (date_.month > 12 || date_.day > 31) {
795
61
      date_.month = 0;
796
61
      date_.day = 0;
797
61
      printWarning();
798
61
      return 1;
799
61
    }
800
170
    return 0;
801
231
  }
802
440
  printWarning();
803
440
  return 1;
804
1.39k
}
805
806
0
void DateValue::setDate(const Date& src) {
807
0
  date_ = src;
808
0
}
809
810
47
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
47
  auto out = reinterpret_cast<char*>(buf);
815
47
  auto it = stringFormatTo(out, "{:04}{:02}{:02}", date_.year, date_.month, date_.day);
816
817
47
  return it - out;
818
47
}
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
94
size_t DateValue::size() const {
829
94
  return 8;
830
94
}
831
832
663
DateValue* DateValue::clone_() const {
833
663
  return new DateValue(*this);
834
663
}
835
836
474
std::ostream& DateValue::write(std::ostream& os) const {
837
  // Write DateValue in ISO 8601 Extended format: YYYY-MM-DD
838
474
  return os << stringFormat("{:04}-{:02}-{:02}", date_.year, date_.month, date_.day);
839
474
}
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
2.07k
TimeValue::TimeValue() : Value(time) {
876
2.07k
  time_ = {};
877
2.07k
}
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
2.07k
int TimeValue::read(const byte* buf, size_t len, ByteOrder /*byteOrder*/) {
884
2.07k
  const std::string str(reinterpret_cast<const char*>(buf), len);
885
2.07k
  return read(str);
886
2.07k
}
887
888
2.07k
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
2.07k
  auto printWarning = [] {
894
1.65k
#ifndef SUPPRESS_WARNINGS
895
1.65k
    EXV_WARNING << Error(ErrorCode::kerUnsupportedTimeFormat) << "\n";
896
1.65k
#endif
897
1.65k
    return 1;
898
1.65k
  };
899
900
2.07k
  if (buf.size() < 2)
901
155
    return printWarning();
902
903
1.91k
  for (auto c : buf)
904
23.8k
    if (c != ':' && c != '+' && c != '-' && c != 'Z' && !std::isdigit(c))
905
737
      return printWarning();
906
907
1.17k
  size_t mpos;
908
1.17k
  size_t spos;
909
1.17k
  if (buf.find(':') != std::string::npos) {
910
652
    mpos = 3;
911
652
    spos = 6;
912
652
  } else {
913
526
    mpos = 2;
914
526
    spos = 4;
915
526
  }
916
917
1.17k
  auto hi = std::stoi(buf.substr(0, 2));
918
1.17k
  if (hi < 0 || hi > 23)
919
168
    return printWarning();
920
1.01k
  time_.hour = hi;
921
1.01k
  if (buf.size() > 3) {
922
949
    auto mi = std::stoi(buf.substr(mpos, 2));
923
949
    if (mi < 0 || mi > 59)
924
180
      return printWarning();
925
769
    time_.minute = std::stoi(buf.substr(mpos, 2));
926
769
  } else {
927
61
    time_.minute = 0;
928
61
  }
929
830
  if (buf.size() > 5) {
930
725
    auto si = std::stoi(buf.substr(spos, 2));
931
725
    if (si < 0 || si > 60)
932
50
      return printWarning();
933
675
    time_.second = std::stoi(buf.substr(spos, 2));
934
675
  } else {
935
105
    time_.second = 0;
936
105
  }
937
938
780
  auto fpos = buf.find('+');
939
780
  if (fpos == std::string::npos)
940
614
    fpos = buf.find('-');
941
942
780
  if (fpos != std::string::npos) {
943
709
    auto format = buf.substr(fpos, buf.size());
944
709
    auto posColon = format.find(':');
945
709
    if (posColon == std::string::npos) {
946
      // Extended format
947
395
      auto tzhi = std::stoi(format.substr(0, 3));
948
395
      if (tzhi < -23 || tzhi > 23)
949
81
        return printWarning();
950
314
      time_.tzHour = tzhi;
951
314
      if (format.size() > 3) {
952
251
        int minute = std::stoi(format.substr(3));
953
251
        if (minute < 0 || minute > 59)
954
107
          return printWarning();
955
144
        time_.tzMinute = time_.tzHour < 0 ? -minute : minute;
956
144
      }
957
314
    } else {
958
      // Basic format
959
314
      auto tzhi = std::stoi(format.substr(0, posColon));
960
314
      if (tzhi < -23 || tzhi > 23)
961
53
        return printWarning();
962
261
      time_.tzHour = tzhi;
963
261
      int minute = std::stoi(format.substr(posColon + 1));
964
261
      if (minute < 0 || minute > 59)
965
122
        return printWarning();
966
139
      time_.tzMinute = time_.tzHour < 0 ? -minute : minute;
967
139
    }
968
709
  }
969
417
  return 0;
970
780
}
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
194
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
194
  char plusMinus = '+';
981
194
  if (time_.tzHour < 0 || time_.tzMinute < 0)
982
9
    plusMinus = '-';
983
984
194
  auto out = reinterpret_cast<char*>(buf);
985
194
  auto it = stringFormatTo(out, "{:02}{:02}{:02}{}{:02}{:02}", time_.hour, time_.minute, time_.second, plusMinus,
986
194
                           std::abs(time_.tzHour), std::abs(time_.tzMinute));
987
988
194
  auto wrote = static_cast<size_t>(it - out);
989
194
  Internal::enforce(wrote == 11, Exiv2::ErrorCode::kerUnsupportedTimeFormat);
990
194
  return wrote;
991
194
}
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
388
size_t TimeValue::size() const {
1002
388
  return 11;
1003
388
}
1004
1005
1.01k
TimeValue* TimeValue::clone_() const {
1006
1.01k
  return new TimeValue(*this);
1007
1.01k
}
1008
1009
112
std::ostream& TimeValue::write(std::ostream& os) const {
1010
  // Write TimeValue in ISO 8601 Extended format: hh:mm:ss±hh:mm
1011
112
  char plusMinus = '+';
1012
112
  if (time_.tzHour < 0 || time_.tzMinute < 0)
1013
34
    plusMinus = '-';
1014
1015
112
  return os << stringFormat("{:02}:{:02}:{:02}{}{:02}:{:02}", time_.hour, time_.minute, time_.second, plusMinus,
1016
112
                            std::abs(time_.tzHour), std::abs(time_.tzMinute));
1017
112
}
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