Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | |
3 | | // included header files |
4 | | #include "iptc.hpp" |
5 | | #include "config.h" |
6 | | #include "datasets.hpp" |
7 | | #include "enforce.hpp" |
8 | | #include "error.hpp" |
9 | | #include "image_int.hpp" |
10 | | #include "types.hpp" |
11 | | #include "value.hpp" |
12 | | |
13 | | // + standard includes |
14 | | #include <algorithm> |
15 | | #include <iostream> |
16 | | |
17 | | // ***************************************************************************** |
18 | | namespace { |
19 | | /*! |
20 | | @brief Read a single dataset payload and create a new metadata entry. |
21 | | |
22 | | @param iptcData IPTC metadata container to add the dataset to |
23 | | @param dataSet DataSet number |
24 | | @param record Record Id |
25 | | @param data Pointer to the first byte of dataset payload |
26 | | @param sizeData Length in bytes of dataset payload |
27 | | @return 0 if successful. |
28 | | */ |
29 | | int readData(Exiv2::IptcData& iptcData, uint16_t dataSet, uint16_t record, const Exiv2::byte* data, uint32_t sizeData); |
30 | | |
31 | | //! Unary predicate that matches an Iptcdatum with given record and dataset |
32 | | class FindIptcdatum { |
33 | | public: |
34 | | //! Constructor, initializes the object with the record and dataset id |
35 | 70.3k | FindIptcdatum(uint16_t dataset, uint16_t record) : dataset_(dataset), record_(record) { |
36 | 70.3k | } |
37 | | /*! |
38 | | @brief Returns true if the record and dataset id of the argument |
39 | | Iptcdatum is equal to that of the object. |
40 | | */ |
41 | 1.27M | bool operator()(const Exiv2::Iptcdatum& iptcdatum) const { |
42 | 1.27M | return dataset_ == iptcdatum.tag() && record_ == iptcdatum.record(); |
43 | 1.27M | } |
44 | | |
45 | | private: |
46 | | // DATA |
47 | | uint16_t dataset_; |
48 | | uint16_t record_; |
49 | | |
50 | | }; // class FindIptcdatum |
51 | | } // namespace |
52 | | |
53 | | // ***************************************************************************** |
54 | | // class member definitions |
55 | | namespace Exiv2 { |
56 | 86.6k | Iptcdatum::Iptcdatum(const IptcKey& key, const Value* pValue) : key_(key.clone()) { |
57 | 86.6k | if (pValue) |
58 | 29.2k | value_ = pValue->clone(); |
59 | 86.6k | } |
60 | | |
61 | 326k | Iptcdatum::Iptcdatum(const Iptcdatum& rhs) { |
62 | 326k | if (rhs.key_) |
63 | 326k | key_ = rhs.key_->clone(); // deep copy |
64 | 326k | if (rhs.value_) |
65 | 326k | value_ = rhs.value_->clone(); // deep copy |
66 | 326k | } |
67 | | |
68 | 413k | Iptcdatum::~Iptcdatum() = default; |
69 | | |
70 | 0 | size_t Iptcdatum::copy(byte* buf, ByteOrder byteOrder) const { |
71 | 0 | return value_ ? value_->copy(buf, byteOrder) : 0; |
72 | 0 | } |
73 | | |
74 | 0 | std::ostream& Iptcdatum::write(std::ostream& os, const ExifData*) const { |
75 | 0 | return os << value(); |
76 | 0 | } |
77 | | |
78 | 107k | std::string Iptcdatum::key() const { |
79 | 107k | return key_ ? key_->key() : ""; |
80 | 107k | } |
81 | | |
82 | 0 | std::string Iptcdatum::recordName() const { |
83 | 0 | return key_ ? key_->recordName() : ""; |
84 | 0 | } |
85 | | |
86 | 556k | uint16_t Iptcdatum::record() const { |
87 | 556k | return key_ ? key_->record() : 0; |
88 | 556k | } |
89 | | |
90 | 0 | const char* Iptcdatum::familyName() const { |
91 | 0 | return key_ ? key_->familyName() : ""; |
92 | 0 | } |
93 | | |
94 | 0 | std::string Iptcdatum::groupName() const { |
95 | 0 | return key_ ? key_->groupName() : ""; |
96 | 0 | } |
97 | | |
98 | 0 | std::string Iptcdatum::tagName() const { |
99 | 0 | return key_ ? key_->tagName() : ""; |
100 | 0 | } |
101 | | |
102 | 0 | std::string Iptcdatum::tagLabel() const { |
103 | 0 | return key_ ? key_->tagLabel() : ""; |
104 | 0 | } |
105 | | |
106 | 0 | std::string Iptcdatum::tagDesc() const { |
107 | 0 | return key_ ? key_->tagDesc() : ""; |
108 | 0 | } |
109 | | |
110 | 1.47M | uint16_t Iptcdatum::tag() const { |
111 | 1.47M | return key_ ? key_->tag() : 0; |
112 | 1.47M | } |
113 | | |
114 | 0 | TypeId Iptcdatum::typeId() const { |
115 | 0 | return value_ ? value_->typeId() : invalidTypeId; |
116 | 0 | } |
117 | | |
118 | 0 | const char* Iptcdatum::typeName() const { |
119 | 0 | return TypeInfo::typeName(typeId()); |
120 | 0 | } |
121 | | |
122 | 0 | size_t Iptcdatum::typeSize() const { |
123 | 0 | return TypeInfo::typeSize(typeId()); |
124 | 0 | } |
125 | | |
126 | 0 | size_t Iptcdatum::count() const { |
127 | 0 | return value_ ? value_->count() : 0; |
128 | 0 | } |
129 | | |
130 | 75.3k | size_t Iptcdatum::size() const { |
131 | 75.3k | return value_ ? value_->size() : 0; |
132 | 75.3k | } |
133 | | |
134 | 55.5k | std::string Iptcdatum::toString() const { |
135 | 55.5k | return value_ ? value_->toString() : ""; |
136 | 55.5k | } |
137 | | |
138 | 0 | std::string Iptcdatum::toString(size_t n) const { |
139 | 0 | return value_ ? value_->toString(n) : ""; |
140 | 0 | } |
141 | | |
142 | 0 | int64_t Iptcdatum::toInt64(size_t n) const { |
143 | 0 | return value_ ? value_->toInt64(n) : -1; |
144 | 0 | } |
145 | | |
146 | 0 | float Iptcdatum::toFloat(size_t n) const { |
147 | 0 | return value_ ? value_->toFloat(n) : -1; |
148 | 0 | } |
149 | | |
150 | 0 | Rational Iptcdatum::toRational(size_t n) const { |
151 | 0 | return value_ ? value_->toRational(n) : Rational(-1, 1); |
152 | 0 | } |
153 | | |
154 | 0 | Value::UniquePtr Iptcdatum::getValue() const { |
155 | 0 | return value_ ? value_->clone() : nullptr; |
156 | 0 | } |
157 | | |
158 | 93.2k | const Value& Iptcdatum::value() const { |
159 | 93.2k | if (!value_) |
160 | 0 | throw Error(ErrorCode::kerValueNotSet, key()); |
161 | 93.2k | return *value_; |
162 | 93.2k | } |
163 | | |
164 | 114k | Iptcdatum& Iptcdatum::operator=(const Iptcdatum& rhs) { |
165 | 114k | if (this == &rhs) |
166 | 0 | return *this; |
167 | | |
168 | 114k | key_.reset(); |
169 | 114k | if (rhs.key_) |
170 | 114k | key_ = rhs.key_->clone(); // deep copy |
171 | | |
172 | 114k | value_.reset(); |
173 | 114k | if (rhs.value_) |
174 | 114k | value_ = rhs.value_->clone(); // deep copy |
175 | | |
176 | 114k | return *this; |
177 | 114k | } // Iptcdatum::operator= |
178 | | |
179 | 0 | Iptcdatum& Iptcdatum::operator=(const uint16_t& value) { |
180 | 0 | auto v = std::make_unique<UShortValue>(); |
181 | 0 | v->value_.push_back(value); |
182 | 0 | value_ = std::move(v); |
183 | 0 | return *this; |
184 | 0 | } |
185 | | |
186 | 1.64k | Iptcdatum& Iptcdatum::operator=(const std::string& value) { |
187 | 1.64k | setValue(value); |
188 | 1.64k | return *this; |
189 | 1.64k | } |
190 | | |
191 | 0 | Iptcdatum& Iptcdatum::operator=(const Value& value) { |
192 | 0 | setValue(&value); |
193 | 0 | return *this; |
194 | 0 | } |
195 | | |
196 | 0 | void Iptcdatum::setValue(const Value* pValue) { |
197 | 0 | value_.reset(); |
198 | 0 | if (pValue) |
199 | 0 | value_ = pValue->clone(); |
200 | 0 | } |
201 | | |
202 | 57.5k | int Iptcdatum::setValue(const std::string& value) { |
203 | 57.5k | if (!value_) { |
204 | 57.3k | TypeId type = IptcDataSets::dataSetType(tag(), record()); |
205 | 57.3k | value_ = Value::create(type); |
206 | 57.3k | } |
207 | 57.5k | return value_->read(value); |
208 | 57.5k | } |
209 | | |
210 | 1.64k | Iptcdatum& IptcData::operator[](const std::string& key) { |
211 | 1.64k | IptcKey iptcKey(key); |
212 | 1.64k | auto pos = findKey(iptcKey); |
213 | 1.64k | if (pos == end()) { |
214 | 1.38k | return iptcMetadata_.emplace_back(iptcKey); |
215 | 1.38k | } |
216 | 253 | return *pos; |
217 | 1.64k | } |
218 | | |
219 | 1.36k | size_t IptcData::size() const { |
220 | 1.36k | size_t newSize = 0; |
221 | 37.6k | for (auto&& iptc : iptcMetadata_) { |
222 | | // marker, record Id, dataset num, first 2 bytes of size |
223 | 37.6k | newSize += 5; |
224 | 37.6k | size_t dataSize = iptc.size(); |
225 | 37.6k | newSize += dataSize; |
226 | 37.6k | if (dataSize > 32767) { |
227 | | // extended dataset (we always use 4 bytes) |
228 | 0 | newSize += 4; |
229 | 0 | } |
230 | 37.6k | } |
231 | 1.36k | return newSize; |
232 | 1.36k | } |
233 | | |
234 | 29.2k | int IptcData::add(const IptcKey& key, const Value* value) { |
235 | 29.2k | return add(Iptcdatum(key, value)); |
236 | 29.2k | } |
237 | | |
238 | 85.2k | int IptcData::add(const Iptcdatum& iptcDatum) { |
239 | 85.2k | if (!IptcDataSets::dataSetRepeatable(iptcDatum.tag(), iptcDatum.record()) && |
240 | 16.8k | findId(iptcDatum.tag(), iptcDatum.record()) != end()) { |
241 | 13.1k | return 6; |
242 | 13.1k | } |
243 | | // allow duplicates |
244 | 72.0k | iptcMetadata_.push_back(iptcDatum); |
245 | 72.0k | return 0; |
246 | 85.2k | } |
247 | | |
248 | 2.42k | IptcData::const_iterator IptcData::findKey(const IptcKey& key) const { |
249 | 2.42k | return std::find_if(iptcMetadata_.begin(), iptcMetadata_.end(), FindIptcdatum(key.tag(), key.record())); |
250 | 2.42k | } |
251 | | |
252 | 51.0k | IptcData::iterator IptcData::findKey(const IptcKey& key) { |
253 | 51.0k | return std::find_if(iptcMetadata_.begin(), iptcMetadata_.end(), FindIptcdatum(key.tag(), key.record())); |
254 | 51.0k | } |
255 | | |
256 | 0 | IptcData::const_iterator IptcData::findId(uint16_t dataset, uint16_t record) const { |
257 | 0 | return std::find_if(iptcMetadata_.begin(), iptcMetadata_.end(), FindIptcdatum(dataset, record)); |
258 | 0 | } |
259 | | |
260 | 16.8k | IptcData::iterator IptcData::findId(uint16_t dataset, uint16_t record) { |
261 | 16.8k | return std::find_if(iptcMetadata_.begin(), iptcMetadata_.end(), FindIptcdatum(dataset, record)); |
262 | 16.8k | } |
263 | | |
264 | | /// \todo not used internally. At least we should test it |
265 | 0 | void IptcData::sortByKey() { |
266 | 0 | std::sort(iptcMetadata_.begin(), iptcMetadata_.end(), cmpMetadataByKey); |
267 | 0 | } |
268 | | |
269 | | /// \todo not used internally. At least we should test it |
270 | 0 | void IptcData::sortByTag() { |
271 | 0 | std::sort(iptcMetadata_.begin(), iptcMetadata_.end(), cmpMetadataByTag); |
272 | 0 | } |
273 | | |
274 | 0 | IptcData::iterator IptcData::erase(IptcData::iterator pos) { |
275 | 0 | return iptcMetadata_.erase(pos); |
276 | 0 | } |
277 | | |
278 | 0 | void IptcData::printStructure(std::ostream& out, const Slice<byte*>& bytes, size_t depth) { |
279 | 0 | if (bytes.size() < 3) { |
280 | 0 | return; |
281 | 0 | } |
282 | 0 | size_t i = 0; |
283 | 0 | while (i < bytes.size() - 3 && bytes.at(i) != 0x1c) |
284 | 0 | i++; |
285 | 0 | out << Internal::indent(++depth) << "Record | DataSet | Name | Length | Data" << '\n'; |
286 | 0 | while (i < bytes.size() - 3) { |
287 | 0 | if (bytes.at(i) != 0x1c) { |
288 | 0 | break; |
289 | 0 | } |
290 | 0 | uint16_t record = bytes.at(i + 1); |
291 | 0 | uint16_t dataset = bytes.at(i + 2); |
292 | 0 | Internal::enforce(bytes.size() - i >= 5, ErrorCode::kerCorruptedMetadata); |
293 | 0 | uint16_t len = getUShort(bytes.subSlice(i + 3, bytes.size()), bigEndian); |
294 | |
|
295 | 0 | Internal::enforce(bytes.size() - i >= 5 + static_cast<size_t>(len), ErrorCode::kerCorruptedMetadata); |
296 | 0 | out << stringFormat(" {:6} | {:7} | {:<24} | {:6} | ", record, dataset, |
297 | 0 | Exiv2::IptcDataSets::dataSetName(dataset, record), len); |
298 | 0 | out << Internal::binaryToString(makeSlice(bytes, i + 5, i + 5 + std::min<uint16_t>(40, len))) |
299 | 0 | << (len > 40 ? "...\n" : "\n"); |
300 | 0 | i += 5 + len; |
301 | 0 | } |
302 | 0 | } |
303 | | |
304 | 2.42k | const char* IptcData::detectCharset() const { |
305 | 2.42k | auto pos = findKey(IptcKey("Iptc.Envelope.CharacterSet")); |
306 | 2.42k | if (pos != end()) { |
307 | 676 | const std::string value = pos->toString(); |
308 | 676 | if (pos->value().ok() && value == "\033%G") |
309 | 676 | return "UTF-8"; |
310 | 676 | } |
311 | | |
312 | 1.75k | bool ascii = true; |
313 | 1.75k | bool utf8 = true; |
314 | | |
315 | 1.75k | for (const auto& key : *this) { |
316 | 0 | std::string value = key.toString(); |
317 | 0 | if (key.value().ok()) { |
318 | 0 | int seqCount = 0; |
319 | 0 | for (auto c : value) { |
320 | 0 | if (seqCount) { |
321 | 0 | if ((c & 0xc0) != 0x80) { |
322 | 0 | utf8 = false; |
323 | 0 | break; |
324 | 0 | } |
325 | 0 | --seqCount; |
326 | 0 | } else { |
327 | 0 | if (c & 0x80) |
328 | 0 | ascii = false; |
329 | 0 | else |
330 | 0 | continue; // ascii character |
331 | | |
332 | 0 | if ((c & 0xe0) == 0xc0) |
333 | 0 | seqCount = 1; |
334 | 0 | else if ((c & 0xf0) == 0xe0) |
335 | 0 | seqCount = 2; |
336 | 0 | else if ((c & 0xf8) == 0xf0) |
337 | 0 | seqCount = 3; |
338 | 0 | else if ((c & 0xfc) == 0xf8) |
339 | 0 | seqCount = 4; |
340 | 0 | else if ((c & 0xfe) == 0xfc) |
341 | 0 | seqCount = 5; |
342 | 0 | else { |
343 | 0 | utf8 = false; |
344 | 0 | break; |
345 | 0 | } |
346 | 0 | } |
347 | 0 | } |
348 | 0 | if (seqCount) |
349 | 0 | utf8 = false; // unterminated seq |
350 | 0 | if (!utf8) |
351 | 0 | break; |
352 | 0 | } |
353 | 0 | } |
354 | | |
355 | 1.75k | if (ascii) |
356 | 1.75k | return "ASCII"; |
357 | 0 | if (utf8) |
358 | 0 | return "UTF-8"; |
359 | 0 | return nullptr; |
360 | 0 | } |
361 | | |
362 | 2.53k | int IptcParser::decode(IptcData& iptcData, const byte* pData, size_t size) { |
363 | | #ifdef EXIV2_DEBUG_MESSAGES |
364 | | std::cerr << "IptcParser::decode, size = " << size << "\n"; |
365 | | #endif |
366 | 2.53k | auto pRead = pData; |
367 | 2.53k | const auto pEnd = pData + size; |
368 | 2.53k | iptcData.clear(); |
369 | | |
370 | 2.53k | uint16_t record = 0; |
371 | 2.53k | uint16_t dataSet = 0; |
372 | 2.53k | uint32_t sizeData = 0; |
373 | 2.53k | byte extTest = 0; |
374 | | |
375 | 987k | while (6 <= static_cast<size_t>(pEnd - pRead)) { |
376 | | // First byte should be a marker. If it isn't, scan forward and skip |
377 | | // the chunk bytes present in some images. This deviates from the |
378 | | // standard, which advises to treat such cases as errors. |
379 | 986k | if (*pRead++ != marker_) |
380 | 956k | continue; |
381 | 30.0k | record = *pRead++; |
382 | 30.0k | dataSet = *pRead++; |
383 | | |
384 | 30.0k | extTest = *pRead; |
385 | 30.0k | if (extTest & 0x80) { |
386 | | // extended dataset |
387 | 894 | uint16_t sizeOfSize = (getUShort(pRead, bigEndian) & 0x7FFF); |
388 | 894 | if (sizeOfSize > 4) |
389 | 182 | return 5; |
390 | 712 | pRead += 2; |
391 | 712 | if (sizeOfSize > pEnd - pRead) |
392 | 6 | return 6; |
393 | 706 | sizeData = 0; |
394 | 1.29k | for (; sizeOfSize > 0; --sizeOfSize) { |
395 | 593 | sizeData |= *pRead++ << (8 * (sizeOfSize - 1)); |
396 | 593 | } |
397 | 29.1k | } else { |
398 | | // standard dataset |
399 | 29.1k | sizeData = getUShort(pRead, bigEndian); |
400 | 29.1k | pRead += 2; |
401 | 29.1k | } |
402 | 29.9k | if (sizeData <= static_cast<size_t>(pEnd - pRead)) { |
403 | 29.3k | int rc = readData(iptcData, dataSet, record, pRead, sizeData); |
404 | 29.3k | if (rc != 0) { |
405 | 0 | #ifndef SUPPRESS_WARNINGS |
406 | 0 | EXV_WARNING << "Failed to read IPTC dataset " << IptcKey(dataSet, record) << " (rc = " << rc << "); skipped.\n"; |
407 | 0 | #endif |
408 | 0 | } |
409 | 29.3k | } else { |
410 | 573 | #ifndef SUPPRESS_WARNINGS |
411 | 573 | EXV_WARNING << "IPTC dataset " << IptcKey(dataSet, record) << " has invalid size " << sizeData << "; skipped.\n"; |
412 | 573 | #endif |
413 | 573 | return 7; |
414 | 573 | } |
415 | 29.3k | pRead += sizeData; |
416 | 29.3k | } |
417 | | |
418 | 1.77k | return 0; |
419 | 2.53k | } // IptcParser::decode |
420 | | |
421 | 22.6k | DataBuf IptcParser::encode(const IptcData& iptcData) { |
422 | 22.6k | DataBuf buf; |
423 | 22.6k | if (iptcData.empty()) |
424 | 21.2k | return buf; |
425 | | |
426 | 1.36k | buf = DataBuf(iptcData.size()); |
427 | 1.36k | byte* pWrite = buf.data(); |
428 | | |
429 | | // Copy the iptc data sets and sort them by record but preserve the order of datasets |
430 | 1.36k | IptcMetadata sortedIptcData(iptcData.begin(), iptcData.end()); |
431 | 1.36k | std::stable_sort(sortedIptcData.begin(), sortedIptcData.end(), |
432 | 170k | [](const auto& l, const auto& r) { return l.record() < r.record(); }); |
433 | | |
434 | 37.6k | for (const auto& iter : sortedIptcData) { |
435 | | // marker, record Id, dataset num |
436 | 37.6k | *pWrite++ = marker_; |
437 | 37.6k | *pWrite++ = static_cast<byte>(iter.record()); |
438 | 37.6k | *pWrite++ = static_cast<byte>(iter.tag()); |
439 | | |
440 | | // extended or standard dataset? |
441 | 37.6k | if (size_t dataSize = iter.size(); dataSize > 32767) { |
442 | | // always use 4 bytes for extended length |
443 | 0 | uint16_t sizeOfSize = 4 | 0x8000; |
444 | 0 | us2Data(pWrite, sizeOfSize, bigEndian); |
445 | 0 | pWrite += 2; |
446 | 0 | ul2Data(pWrite, static_cast<uint32_t>(dataSize), bigEndian); |
447 | 0 | pWrite += 4; |
448 | 37.6k | } else { |
449 | 37.6k | us2Data(pWrite, static_cast<uint16_t>(dataSize), bigEndian); |
450 | 37.6k | pWrite += 2; |
451 | 37.6k | } |
452 | 37.6k | pWrite += iter.value().copy(pWrite, bigEndian); |
453 | 37.6k | } |
454 | | |
455 | 1.36k | return buf; |
456 | 22.6k | } // IptcParser::encode |
457 | | |
458 | | } // namespace Exiv2 |
459 | | |
460 | | // ***************************************************************************** |
461 | | // local definitions |
462 | | namespace { |
463 | 29.3k | int readData(Exiv2::IptcData& iptcData, uint16_t dataSet, uint16_t record, const Exiv2::byte* data, uint32_t sizeData) { |
464 | 29.3k | Exiv2::TypeId type = Exiv2::IptcDataSets::dataSetType(dataSet, record); |
465 | 29.3k | auto value = Exiv2::Value::create(type); |
466 | 29.3k | int rc = value->read(data, sizeData, Exiv2::bigEndian); |
467 | 29.3k | if (0 == rc) { |
468 | 18.7k | Exiv2::IptcKey key(dataSet, record); |
469 | 18.7k | iptcData.add(key, value.get()); |
470 | 18.7k | } else if (1 == rc) { |
471 | | // If the first attempt failed, try with a string value |
472 | 10.5k | value = Exiv2::Value::create(Exiv2::string); |
473 | 10.5k | rc = value->read(data, sizeData, Exiv2::bigEndian); |
474 | 10.5k | if (0 == rc) { |
475 | 10.5k | Exiv2::IptcKey key(dataSet, record); |
476 | 10.5k | iptcData.add(key, value.get()); |
477 | 10.5k | } |
478 | 10.5k | } |
479 | 29.3k | return rc; |
480 | 29.3k | } |
481 | | |
482 | | } // namespace |