// SPDX-License-Identifier: GPL-2.0-or-later // included header files #include "types.hpp" #include "enforce.hpp" #include "futils.hpp" #include "i18n.h" // for _exvGettext #include "safe_op.hpp" // + standard includes #include #include #include #include #include #include #include #include // ***************************************************************************** namespace { //! Information pertaining to the defined %Exiv2 value type identifiers. struct TypeInfoTable { Exiv2::TypeId typeId_; //!< Type id const char* name_; //!< Name of the type size_t size_; //!< Bytes per data entry //! Comparison operator for \em typeId bool operator==(Exiv2::TypeId typeId) const { return typeId_ == typeId; } //! Comparison operator for \em name bool operator==(const std::string& name) const { return 0 == strcmp(name_, name.c_str()); } }; // struct TypeInfoTable //! Lookup list with information of Exiv2 types constexpr auto typeInfoTable = std::array{ TypeInfoTable{Exiv2::invalidTypeId, "Invalid", 0}, TypeInfoTable{Exiv2::unsignedByte, "Byte", 1}, TypeInfoTable{Exiv2::asciiString, "Ascii", 1}, TypeInfoTable{Exiv2::unsignedShort, "Short", 2}, TypeInfoTable{Exiv2::unsignedLong, "Long", 4}, TypeInfoTable{Exiv2::unsignedRational, "Rational", 8}, TypeInfoTable{Exiv2::signedByte, "SByte", 1}, TypeInfoTable{Exiv2::undefined, "Undefined", 1}, TypeInfoTable{Exiv2::signedShort, "SShort", 2}, TypeInfoTable{Exiv2::signedLong, "SLong", 4}, TypeInfoTable{Exiv2::signedRational, "SRational", 8}, TypeInfoTable{Exiv2::tiffFloat, "Float", 4}, TypeInfoTable{Exiv2::tiffDouble, "Double", 8}, TypeInfoTable{Exiv2::tiffIfd, "Ifd", 4}, TypeInfoTable{Exiv2::string, "String", 1}, TypeInfoTable{Exiv2::date, "Date", 8}, TypeInfoTable{Exiv2::time, "Time", 11}, TypeInfoTable{Exiv2::comment, "Comment", 1}, TypeInfoTable{Exiv2::directory, "Directory", 1}, TypeInfoTable{Exiv2::xmpText, "XmpText", 1}, TypeInfoTable{Exiv2::xmpAlt, "XmpAlt", 1}, TypeInfoTable{Exiv2::xmpBag, "XmpBag", 1}, TypeInfoTable{Exiv2::xmpSeq, "XmpSeq", 1}, TypeInfoTable{Exiv2::langAlt, "LangAlt", 1}, }; } // namespace // ***************************************************************************** // class member definitions namespace Exiv2 { const char* TypeInfo::typeName(TypeId typeId) { auto tit = std::find(typeInfoTable.begin(), typeInfoTable.end(), typeId); if (tit == typeInfoTable.end()) return nullptr; return tit->name_; } TypeId TypeInfo::typeId(const std::string& typeName) { auto tit = std::find(typeInfoTable.begin(), typeInfoTable.end(), typeName); if (tit == typeInfoTable.end()) return invalidTypeId; return tit->typeId_; } size_t TypeInfo::typeSize(TypeId typeId) { auto tit = std::find(typeInfoTable.begin(), typeInfoTable.end(), typeId); if (tit == typeInfoTable.end()) return 0; return tit->size_; } DataBuf::DataBuf(size_t size) : pData_(size) { } DataBuf::DataBuf(const byte* pData, size_t size) : pData_(size) { std::copy_n(pData, size, pData_.begin()); } void DataBuf::alloc(size_t size) { pData_.resize(size); } void DataBuf::resize(size_t size) { pData_.resize(size); } void DataBuf::reset() { pData_.clear(); } uint8_t Exiv2::DataBuf::read_uint8(size_t offset) const { if (offset >= pData_.size()) { throw std::overflow_error("Overflow in Exiv2::DataBuf::read_uint8"); } return pData_[offset]; } void Exiv2::DataBuf::write_uint8(size_t offset, uint8_t x) { if (offset >= pData_.size()) { throw std::overflow_error("Overflow in Exiv2::DataBuf::write_uint8"); } pData_[offset] = x; } uint16_t Exiv2::DataBuf::read_uint16(size_t offset, ByteOrder byteOrder) const { if (pData_.size() < 2 || offset > (pData_.size() - 2)) { throw std::overflow_error("Overflow in Exiv2::DataBuf::read_uint16"); } return getUShort(&pData_[offset], byteOrder); } void Exiv2::DataBuf::write_uint16(size_t offset, uint16_t x, ByteOrder byteOrder) { if (pData_.size() < 2 || offset > (pData_.size() - 2)) { throw std::overflow_error("Overflow in Exiv2::DataBuf::write_uint16"); } us2Data(&pData_[offset], x, byteOrder); } uint32_t Exiv2::DataBuf::read_uint32(size_t offset, ByteOrder byteOrder) const { if (pData_.size() < 4 || offset > (pData_.size() - 4)) { throw std::overflow_error("Overflow in Exiv2::DataBuf::read_uint32"); } return getULong(&pData_[offset], byteOrder); } void Exiv2::DataBuf::write_uint32(size_t offset, uint32_t x, ByteOrder byteOrder) { if (pData_.size() < 4 || offset > (pData_.size() - 4)) { throw std::overflow_error("Overflow in Exiv2::DataBuf::write_uint32"); } ul2Data(&pData_[offset], x, byteOrder); } uint64_t Exiv2::DataBuf::read_uint64(size_t offset, ByteOrder byteOrder) const { if (pData_.size() < 8 || offset > (pData_.size() - 8)) { throw std::overflow_error("Overflow in Exiv2::DataBuf::read_uint64"); } return getULongLong(&pData_[offset], byteOrder); } void Exiv2::DataBuf::write_uint64(size_t offset, uint64_t x, ByteOrder byteOrder) { if (pData_.size() < 8 || offset > (pData_.size() - 8)) { throw std::overflow_error("Overflow in Exiv2::DataBuf::write_uint64"); } ull2Data(&pData_[offset], x, byteOrder); } int Exiv2::DataBuf::cmpBytes(size_t offset, const void* buf, size_t bufsize) const { if (pData_.size() < bufsize || offset > pData_.size() - bufsize) { throw std::overflow_error("Overflow in Exiv2::DataBuf::cmpBytes"); } return memcmp(&pData_[offset], buf, bufsize); } byte* Exiv2::DataBuf::data(size_t offset) { /// \todo this first check should be for <= offset if (pData_.size() < offset) { throw std::overflow_error("Overflow in Exiv2::DataBuf::c_data"); } if (pData_.empty() || pData_.size() == offset) { return nullptr; } return &pData_[offset]; } const byte* Exiv2::DataBuf::c_data(size_t offset) const { /// \todo this first check should be for <= offset if (pData_.size() < offset) { throw std::overflow_error("Overflow in Exiv2::DataBuf::c_data"); } if (pData_.empty() || pData_.size() == offset) { return nullptr; } return &pData_[offset]; } const char* Exiv2::DataBuf::c_str(size_t offset) const { return reinterpret_cast(c_data(offset)); } // ************************************************************************* // free functions static void checkDataBufBounds(const DataBuf& buf, size_t end) { enforce(end <= static_cast(std::numeric_limits::max()), "end of slice too large to be compared with DataBuf bounds."); enforce(end <= buf.size(), "Invalid slice bounds specified"); } Slice makeSlice(DataBuf& buf, size_t begin, size_t end) { checkDataBufBounds(buf, end); return {buf.data(), begin, end}; } Slice makeSlice(const DataBuf& buf, size_t begin, size_t end) { checkDataBufBounds(buf, end); return {buf.c_data(), begin, end}; } std::ostream& operator<<(std::ostream& os, const Rational& r) { return os << r.first << "/" << r.second; } std::istream& operator>>(std::istream& is, Rational& r) { // http://dev.exiv2.org/boards/3/topics/1912?r=1915 if (std::tolower(is.peek()) == 'f') { char F = 0; float f = 0.F; is >> F >> f; f = 2.0F * std::log(f) / std::log(2.0F); r = Exiv2::floatToRationalCast(f); } else { int32_t nominator = 0; int32_t denominator = 0; char c('\0'); is >> nominator >> c >> denominator; if (c != '/') is.setstate(std::ios::failbit); if (is) r = {nominator, denominator}; } return is; } std::ostream& operator<<(std::ostream& os, const URational& r) { return os << r.first << "/" << r.second; } std::istream& operator>>(std::istream& is, URational& r) { // http://dev.exiv2.org/boards/3/topics/1912?r=1915 /// \todo This implementation seems to be duplicated for the Rational type. Try to remove duplication if (std::tolower(is.peek()) == 'f') { char F = 0; float f = 0.F; is >> F >> f; f = 2.0F * std::log(f) / std::log(2.0F); r = Exiv2::floatToRationalCast(f); } else { uint32_t nominator = 0; uint32_t denominator = 0; char c('\0'); is >> nominator >> c >> denominator; if (c != '/') is.setstate(std::ios::failbit); if (is) r = {nominator, denominator}; } return is; } uint16_t getUShort(const byte* buf, ByteOrder byteOrder) { return getUShort(makeSliceUntil(buf, 2), byteOrder); } uint32_t getULong(const byte* buf, ByteOrder byteOrder) { if (byteOrder == littleEndian) { return buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0]; } return buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]; } uint64_t getULongLong(const byte* buf, ByteOrder byteOrder) { if (byteOrder == littleEndian) { return static_cast(buf[7]) << 56 | static_cast(buf[6]) << 48 | static_cast(buf[5]) << 40 | static_cast(buf[4]) << 32 | static_cast(buf[3]) << 24 | static_cast(buf[2]) << 16 | static_cast(buf[1]) << 8 | static_cast(buf[0]); } return static_cast(buf[0]) << 56 | static_cast(buf[1]) << 48 | static_cast(buf[2]) << 40 | static_cast(buf[3]) << 32 | static_cast(buf[4]) << 24 | static_cast(buf[5]) << 16 | static_cast(buf[6]) << 8 | static_cast(buf[7]); } URational getURational(const byte* buf, ByteOrder byteOrder) { uint32_t nominator = getULong(buf, byteOrder); uint32_t denominator = getULong(buf + 4, byteOrder); return {nominator, denominator}; } int16_t getShort(const byte* buf, ByteOrder byteOrder) { if (byteOrder == littleEndian) { return buf[1] << 8 | buf[0]; } return buf[0] << 8 | buf[1]; } int32_t getLong(const byte* buf, ByteOrder byteOrder) { if (byteOrder == littleEndian) { return buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0]; } return buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]; } Rational getRational(const byte* buf, ByteOrder byteOrder) { int32_t nominator = getLong(buf, byteOrder); int32_t denominator = getLong(buf + 4, byteOrder); return {nominator, denominator}; } float getFloat(const byte* buf, ByteOrder byteOrder) { // This algorithm assumes that the internal representation of the float // type is the 4-byte IEEE 754 binary32 format, which is common but not // required by the C++ standard. union { uint32_t ul_; float f_; } u; u.ul_ = getULong(buf, byteOrder); return u.f_; } double getDouble(const byte* buf, ByteOrder byteOrder) { // This algorithm assumes that the internal representation of the double // type is the 8-byte IEEE 754 binary64 format, which is common but not // required by the C++ standard. union { uint64_t ull_; double d_; } u; u.ull_ = 0; if (byteOrder == littleEndian) { u.ull_ = static_cast(buf[7]) << 56 | static_cast(buf[6]) << 48 | static_cast(buf[5]) << 40 | static_cast(buf[4]) << 32 | static_cast(buf[3]) << 24 | static_cast(buf[2]) << 16 | static_cast(buf[1]) << 8 | static_cast(buf[0]); } else { u.ull_ = static_cast(buf[0]) << 56 | static_cast(buf[1]) << 48 | static_cast(buf[2]) << 40 | static_cast(buf[3]) << 32 | static_cast(buf[4]) << 24 | static_cast(buf[5]) << 16 | static_cast(buf[6]) << 8 | static_cast(buf[7]); } return u.d_; } long us2Data(byte* buf, uint16_t s, ByteOrder byteOrder) { if (byteOrder == littleEndian) { buf[0] = static_cast(s & 0x00ff); buf[1] = static_cast((s & 0xff00) >> 8); } else { buf[0] = static_cast((s & 0xff00) >> 8); buf[1] = static_cast(s & 0x00ff); } return 2; } long ul2Data(byte* buf, uint32_t l, ByteOrder byteOrder) { if (byteOrder == littleEndian) { buf[0] = static_cast(l & 0x000000ff); buf[1] = static_cast((l & 0x0000ff00) >> 8); buf[2] = static_cast((l & 0x00ff0000) >> 16); buf[3] = static_cast((l & 0xff000000) >> 24); } else { buf[0] = static_cast((l & 0xff000000) >> 24); buf[1] = static_cast((l & 0x00ff0000) >> 16); buf[2] = static_cast((l & 0x0000ff00) >> 8); buf[3] = static_cast(l & 0x000000ff); } return 4; } long ull2Data(byte* buf, uint64_t l, ByteOrder byteOrder) { if (byteOrder == littleEndian) { for (size_t i = 0; i < 8; i++) { buf[i] = static_cast(l & 0xff); l >>= 8; } } else { for (size_t i = 0; i < 8; i++) { buf[8 - i - 1] = static_cast(l & 0xff); l >>= 8; } } return 8; } long ur2Data(byte* buf, URational l, ByteOrder byteOrder) { long o = ul2Data(buf, l.first, byteOrder); o += ul2Data(buf + o, l.second, byteOrder); return o; } long s2Data(byte* buf, int16_t s, ByteOrder byteOrder) { if (byteOrder == littleEndian) { buf[0] = static_cast(s & 0x00ff); buf[1] = static_cast((s & 0xff00) >> 8); } else { buf[0] = static_cast((s & 0xff00) >> 8); buf[1] = static_cast(s & 0x00ff); } return 2; } long l2Data(byte* buf, int32_t l, ByteOrder byteOrder) { if (byteOrder == littleEndian) { buf[0] = static_cast(l & 0x000000ff); buf[1] = static_cast((l & 0x0000ff00) >> 8); buf[2] = static_cast((l & 0x00ff0000) >> 16); buf[3] = static_cast((l & 0xff000000) >> 24); } else { buf[0] = static_cast((l & 0xff000000) >> 24); buf[1] = static_cast((l & 0x00ff0000) >> 16); buf[2] = static_cast((l & 0x0000ff00) >> 8); buf[3] = static_cast(l & 0x000000ff); } return 4; } long r2Data(byte* buf, Rational l, ByteOrder byteOrder) { long o = l2Data(buf, l.first, byteOrder); o += l2Data(buf + o, l.second, byteOrder); return o; } long f2Data(byte* buf, float f, ByteOrder byteOrder) { // This algorithm assumes that the internal representation of the float // type is the 4-byte IEEE 754 binary32 format, which is common but not // required by the C++ standard. union { uint32_t ul_; float f_; } u; u.f_ = f; return ul2Data(buf, u.ul_, byteOrder); } long d2Data(byte* buf, double d, ByteOrder byteOrder) { // This algorithm assumes that the internal representation of the double // type is the 8-byte IEEE 754 binary64 format, which is common but not // required by the C++ standard. union { uint64_t ull_; double d_; } u; u.d_ = d; uint64_t m = 0xff; if (byteOrder == littleEndian) { buf[0] = static_cast(u.ull_ & m); buf[1] = static_cast((u.ull_ & (m << 8)) >> 8); buf[2] = static_cast((u.ull_ & (m << 16)) >> 16); buf[3] = static_cast((u.ull_ & (m << 24)) >> 24); buf[4] = static_cast((u.ull_ & (m << 32)) >> 32); buf[5] = static_cast((u.ull_ & (m << 40)) >> 40); buf[6] = static_cast((u.ull_ & (m << 48)) >> 48); buf[7] = static_cast((u.ull_ & (m << 56)) >> 56); } else { buf[0] = static_cast((u.ull_ & (m << 56)) >> 56); buf[1] = static_cast((u.ull_ & (m << 48)) >> 48); buf[2] = static_cast((u.ull_ & (m << 40)) >> 40); buf[3] = static_cast((u.ull_ & (m << 32)) >> 32); buf[4] = static_cast((u.ull_ & (m << 24)) >> 24); buf[5] = static_cast((u.ull_ & (m << 16)) >> 16); buf[6] = static_cast((u.ull_ & (m << 8)) >> 8); buf[7] = static_cast(u.ull_ & m); } return 8; } void hexdump(std::ostream& os, const byte* buf, size_t len, size_t offset) { const std::string::size_type pos = 8 + 16 * 3 + 2; const std::string align(pos, ' '); std::ios::fmtflags f(os.flags()); size_t i = 0; while (i < len) { os << " " << std::setw(4) << std::setfill('0') << std::hex << i + offset << " "; std::ostringstream ss; do { byte c = buf[i]; os << std::setw(2) << std::setfill('0') << std::right << std::hex << static_cast(c) << " "; ss << (static_cast(c) >= 31 && static_cast(c) < 127 ? char(buf[i]) : '.'); } while (++i < len && i % 16 != 0); std::string::size_type width = 9 + ((i - 1) % 16 + 1) * 3; os << (width > pos ? "" : align.substr(width)) << ss.str() << "\n"; } os << std::dec << std::setfill(' '); os.flags(f); } bool isHex(const std::string& str, size_t size, const std::string& prefix) { if (str.size() <= prefix.size() || str.substr(0, prefix.size()) != prefix) return false; if (size > 0 && str.size() != size + prefix.size()) return false; for (size_t i = prefix.size(); i < str.size(); ++i) { if (!isxdigit(str[i])) return false; } return true; } // isHex int exifTime(const char* buf, struct tm* tm) { int rc = 1; int year = 0, mon = 0, mday = 0, hour = 0, min = 0, sec = 0; int scanned = std::sscanf(buf, "%4d:%2d:%2d %2d:%2d:%2d", &year, &mon, &mday, &hour, &min, &sec); if (scanned == 6) { tm->tm_year = year - 1900; tm->tm_mon = mon - 1; tm->tm_mday = mday; tm->tm_hour = hour; tm->tm_min = min; tm->tm_sec = sec; rc = 0; } return rc; } // exifTime const char* exvGettext(const char* str) { #ifdef EXV_ENABLE_NLS return _exvGettext(str); #else return str; #endif } template <> bool stringTo(const std::string& s, bool& ok) { std::string lcs(s); /* lowercase string */ for (size_t i = 0; i < lcs.length(); i++) { lcs[i] = std::tolower(s[i]); } /* handle the same values as xmp sdk */ if (lcs == "false" || lcs == "f" || lcs == "0") { ok = true; return false; } if (lcs == "true" || lcs == "t" || lcs == "1") { ok = true; return true; } ok = false; return false; } int64_t parseInt64(const std::string& s, bool& ok) { auto ret = stringTo(s, ok); if (ok) return ret; auto f = stringTo(s, ok); if (ok) return static_cast(f); auto r = stringTo(s, ok); if (ok) { if (r.second <= 0) { ok = false; return 0; } return static_cast(static_cast(r.first) / r.second); } bool b = stringTo(s, ok); if (ok) return b ? 1 : 0; // everything failed, return from stringTo is probably the best fit return ret; } uint32_t parseUint32(const std::string& s, bool& ok) { const int64_t x = parseInt64(s, ok); if (ok && 0 <= x && x <= std::numeric_limits::max()) { return static_cast(x); } return 0; } float parseFloat(const std::string& s, bool& ok) { auto ret = stringTo(s, ok); if (ok) return ret; auto r = stringTo(s, ok); if (ok) { if (r.second == 0) { ok = false; return 0.0; } return static_cast(r.first) / r.second; } bool b = stringTo(s, ok); if (ok) return b ? 1.0F : 0.0F; // everything failed, return from stringTo is probably the best fit return ret; } Rational parseRational(const std::string& s, bool& ok) { auto ret = stringTo(s, ok); if (ok) return ret; auto l = stringTo(s, ok); if (ok) return {l, 1}; auto f = stringTo(s, ok); if (ok) return floatToRationalCast(f); bool b = stringTo(s, ok); if (ok) return {b ? 1 : 0, 1}; // everything failed, return from stringTo is probably the best fit return ret; } Rational floatToRationalCast(float f) { // Convert f to double because it simplifies the "in_range" check // below. (INT_MAX can be represented accurately as a double, but // gets rounded when it's converted to float.) const double d = f; const bool in_range = INT_MIN <= d && d <= INT_MAX; if (!in_range) { return {d > 0 ? 1 : -1, 0}; } // Beware: primitive conversion algorithm int32_t den = 1000000; const auto d_as_long = static_cast(d); if (Safe::abs(d_as_long) > 2147) { den = 10000; } if (Safe::abs(d_as_long) > 214748) { den = 100; } if (Safe::abs(d_as_long) > 21474836) { den = 1; } const auto nom = static_cast(std::round(d * den)); const int32_t g = gcd(nom, den); return {nom / g, den / g}; } } // namespace Exiv2 #ifdef EXV_ENABLE_NLS // Declaration is in i18n.h const char* _exvGettext(const char* str) { static bool exvGettextInitialized = false; if (!exvGettextInitialized) { // bindtextdomain(EXV_PACKAGE_NAME, EXV_LOCALEDIR); const std::string localeDir = EXV_LOCALEDIR[0] == '/' ? EXV_LOCALEDIR : (Exiv2::getProcessPath() + EXV_SEPARATOR_STR + EXV_LOCALEDIR); bindtextdomain(EXV_PACKAGE_NAME, localeDir.c_str()); #ifdef EXV_HAVE_BIND_TEXTDOMAIN_CODESET bind_textdomain_codeset(EXV_PACKAGE_NAME, "UTF-8"); #endif exvGettextInitialized = true; } return dgettext(EXV_PACKAGE_NAME, str); } #endif // EXV_ENABLE_NLS