diff --git a/include/exiv2/basicio.hpp b/include/exiv2/basicio.hpp index b069c911cb..1697ed1567 100644 --- a/include/exiv2/basicio.hpp +++ b/include/exiv2/basicio.hpp @@ -310,6 +310,9 @@ class EXIV2API FileIo : public BasicIo { Nonzero if failure. */ int open(const std::string& mode); +#ifdef _WIN32 + int open(const std::wstring& mode); +#endif /*! @brief Open the file using the default access mode of "rb". This method can also be used to "reopen" a file which will flush diff --git a/include/exiv2/image.hpp b/include/exiv2/image.hpp index 273a42d153..f80c5d73bb 100644 --- a/include/exiv2/image.hpp +++ b/include/exiv2/image.hpp @@ -523,7 +523,7 @@ class EXIV2API ImageFactory { */ static BasicIo::UniquePtr createIo(const std::string& path, bool useCurl = true); #ifdef _WIN32 - static BasicIo::UniquePtr createIo(const std::wstring& path); + static BasicIo::UniquePtr createIo(const std::wstring& path, bool useCurl = true); #endif /*! @brief Create an Image subclass of the appropriate type by reading @@ -540,7 +540,7 @@ class EXIV2API ImageFactory { */ static Image::UniquePtr open(const std::string& path, bool useCurl = true); #ifdef _WIN32 - static Image::UniquePtr open(const std::wstring& path); + static Image::UniquePtr open(const std::wstring& path, bool useCurl = true); #endif /*! @brief Create an Image subclass of the appropriate type by reading @@ -582,6 +582,9 @@ class EXIV2API ImageFactory { @throw Error If the image type is not supported. */ static Image::UniquePtr create(ImageType type, const std::string& path); +#ifdef _WIN32 + static Image::UniquePtr create(ImageType type, const std::wstring& path); +#endif /*! @brief Create an Image subclass of the requested type by creating a new image in memory. @@ -615,6 +618,9 @@ class EXIV2API ImageFactory { @return %Image type or Image::none if the type is not recognized. */ static ImageType getType(const std::string& path); +#ifdef _WIN32 + static ImageType getType(const std::wstring& path); +#endif /*! @brief Returns the image type of the provided data buffer. @param data Pointer to a data buffer containing an image. The contents diff --git a/src/basicio.cpp b/src/basicio.cpp index 225c033efc..78ef049990 100644 --- a/src/basicio.cpp +++ b/src/basicio.cpp @@ -67,22 +67,17 @@ void BasicIo::seekOrThrow(int64_t offset, Position pos, ErrorCode err) { class FileIo::Impl { public: //! Constructor - explicit Impl(std::string path); -#ifdef _WIN32 - explicit Impl(std::wstring path); -#endif + explicit Impl(fs::path path); ~Impl() = default; // Enumerations //! Mode of operation enum OpMode { opRead, opWrite, opSeek }; // DATA - std::string path_; //!< (Standard) path -#ifdef _WIN32 - std::wstring wpath_; //!< UCS2 path -#endif - std::string openMode_; //!< File open mode - FILE* fp_{}; //!< File stream pointer - OpMode opMode_{opSeek}; //!< File open mode + fs::path path_; //!< (Standard) path + std::string openMode_; //!< File open mode + std::wstring wOpenMode_; //!< File open mode (wide) + FILE* fp_{}; //!< File stream pointer + OpMode opMode_{opSeek}; //!< File open mode #if defined _WIN32 HANDLE hFile_{}; //!< Duplicated fd @@ -113,21 +108,8 @@ class FileIo::Impl { Impl& operator=(const Impl&) = delete; //!< Assignment }; -FileIo::Impl::Impl(std::string path) : path_(std::move(path)) { -#ifdef _WIN32 - wchar_t t[512]; - const auto nw = MultiByteToWideChar(CP_UTF8, 0, path_.data(), static_cast(path_.size()), t, 512); - wpath_.assign(t, nw); -#endif +FileIo::Impl::Impl(fs::path path) : path_(std::move(path)) { } -#ifdef _WIN32 -FileIo::Impl::Impl(std::wstring path) : wpath_(std::move(path)) { - char t[1024]; - const auto nc = - WideCharToMultiByte(CP_UTF8, 0, wpath_.data(), static_cast(wpath_.size()), t, 1024, nullptr, nullptr); - path_.assign(t, nc); -} -#endif int FileIo::Impl::switchMode(OpMode opMode) { if (opMode_ == opMode) @@ -137,6 +119,19 @@ int FileIo::Impl::switchMode(OpMode opMode) { bool reopen = true; switch (opMode) { +#ifdef _WIN32 + case opRead: + // Flush if current mode allows reading, else reopen (in mode "r+b" + // as in this case we know that we can write to the file) + if (wOpenMode_.front() == L'r' || wOpenMode_.at(1) == L'+') + reopen = false; + break; + case opWrite: + // Flush if current mode allows writing, else reopen + if (wOpenMode_.front() != L'r' || wOpenMode_.at(1) == L'+') + reopen = false; + break; +#else case opRead: // Flush if current mode allows reading, else reopen (in mode "r+b" // as in this case we know that we can write to the file) @@ -148,6 +143,7 @@ int FileIo::Impl::switchMode(OpMode opMode) { if (openMode_.front() != 'r' || openMode_.at(1) == '+') reopen = false; break; +#endif case opSeek: reopen = false; break; @@ -177,7 +173,7 @@ int FileIo::Impl::switchMode(OpMode opMode) { openMode_ = "r+b"; opMode_ = opSeek; #ifdef _WIN32 - if (_wfopen_s(&fp_, wpath_.c_str(), L"r+b")) + if (_wfopen_s(&fp_, path_.c_str(), L"r+b")) return 1; return _fseeki64(fp_, offset, SEEK_SET); #else @@ -189,11 +185,7 @@ int FileIo::Impl::switchMode(OpMode opMode) { } // FileIo::Impl::switchMode int FileIo::Impl::stat(StructStat& buf) const { -#ifdef _WIN32 - const auto& file = wpath_; -#else const auto& file = path_; -#endif try { buf.st_size = fs::file_size(file); buf.st_mode = fs::status(file).permissions(); @@ -320,21 +312,12 @@ byte* FileIo::mmap(bool isWriteable) { void FileIo::setPath(const std::string& path) { close(); p_->path_ = path; -#ifdef _WIN32 - wchar_t t[512]; - const auto nw = MultiByteToWideChar(CP_UTF8, 0, p_->path_.data(), static_cast(p_->path_.size()), t, 512); - p_->wpath_.assign(t, nw); -#endif } #ifdef _WIN32 void FileIo::setPath(const std::wstring& path) { close(); - p_->wpath_ = path; - char t[1024]; - const auto nc = WideCharToMultiByte(CP_UTF8, 0, p_->wpath_.data(), static_cast(p_->wpath_.size()), t, 1024, - nullptr, nullptr); - p_->path_.assign(t, nc); + p_->path_ = path; } #endif @@ -497,7 +480,11 @@ size_t FileIo::tell() const { size_t FileIo::size() const { // Flush and commit only if the file is open for writing +#ifdef _WIN32 + if (p_->fp_ && (p_->wOpenMode_.front() != L'r' || p_->wOpenMode_.at(1) == L'+')) { +#else if (p_->fp_ && (p_->openMode_.front() != 'r' || p_->openMode_.at(1) == '+')) { +#endif std::fflush(p_->fp_); #ifdef _MSC_VER // This is required on msvcrt before stat after writing to a file @@ -513,26 +500,34 @@ size_t FileIo::size() const { int FileIo::open() { // Default open is in read-only binary mode +#ifdef _WIN32 + return open(L"rb"); +#else return open("rb"); +#endif } int FileIo::open(const std::string& mode) { close(); p_->openMode_ = mode; p_->opMode_ = Impl::opSeek; -#ifdef _WIN32 - wchar_t wmode[10]; - MultiByteToWideChar(CP_UTF8, 0, mode.c_str(), -1, wmode, 10); - if (_wfopen_s(&p_->fp_, p_->wpath_.c_str(), wmode)) - return 1; -#else p_->fp_ = ::fopen(path().c_str(), mode.c_str()); if (!p_->fp_) return 1; -#endif return 0; } +#ifdef _WIN32 +int FileIo::open(const std::wstring& mode) { + close(); + p_->wOpenMode_ = mode; + p_->opMode_ = Impl::opSeek; + if (_wfopen_s(&p_->fp_, p_->path_.c_str(), mode.c_str())) + return 1; + return 0; +} +#endif + bool FileIo::isopen() const { return p_->fp_ != nullptr; } @@ -583,7 +578,9 @@ bool FileIo::eof() const { } const std::string& FileIo::path() const noexcept { - return p_->path_; + static thread_local std::string p; + p = p_->path_.string(); + return p; } void FileIo::populateFakeData() { @@ -870,7 +867,7 @@ bool MemIo::eof() const { } const std::string& MemIo::path() const noexcept { - static std::string _path{"MemIo"}; + static const std::string _path{"MemIo"}; return _path; } diff --git a/src/image.cpp b/src/image.cpp index 45cf586ba3..1cf75e2700 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -763,6 +763,17 @@ ImageType ImageFactory::getType([[maybe_unused]] const std::string& path) { #endif } +#ifdef _WIN32 +ImageType ImageFactory::getType([[maybe_unused]] const std::wstring& path) { +#ifdef EXV_ENABLE_FILESYSTEM + FileIo fileIo(path); + return getType(fileIo); +#else + return ImageType::none; +#endif +} +#endif + ImageType ImageFactory::getType(const byte* data, size_t size) { MemIo memIo(data, size); return getType(memIo); @@ -806,7 +817,7 @@ BasicIo::UniquePtr ImageFactory::createIo(const std::string& path, [[maybe_unuse } // ImageFactory::createIo #ifdef _WIN32 -BasicIo::UniquePtr ImageFactory::createIo(const std::wstring& path) { +BasicIo::UniquePtr ImageFactory::createIo(const std::wstring& path, bool) { #ifdef EXV_ENABLE_FILESYSTEM return std::make_unique(path); #else @@ -823,8 +834,8 @@ Image::UniquePtr ImageFactory::open(const std::string& path, bool useCurl) { } #ifdef _WIN32 -Image::UniquePtr ImageFactory::open(const std::wstring& path) { - auto image = open(ImageFactory::createIo(path)); // may throw +Image::UniquePtr ImageFactory::open(const std::wstring& path, bool useCurl) { + auto image = open(ImageFactory::createIo(path, useCurl)); // may throw if (!image) { char t[1024]; WideCharToMultiByte(CP_UTF8, 0, path.c_str(), -1, t, 1024, nullptr, nullptr); diff --git "a/test/data/\320\240\320\265\320\263\320\260\320\275.jp2" "b/test/data/\320\240\320\265\320\263\320\260\320\275.jp2" new file mode 100644 index 0000000000..fb011b5217 Binary files /dev/null and "b/test/data/\320\240\320\265\320\263\320\260\320\275.jp2" differ diff --git a/tests/regression_tests/test_regression_allfiles.py b/tests/regression_tests/test_regression_allfiles.py index 1ea1b6e709..6bd2c87bc6 100644 --- a/tests/regression_tests/test_regression_allfiles.py +++ b/tests/regression_tests/test_regression_allfiles.py @@ -58,6 +58,7 @@ def get_valid_files(data_dir): "imagemagick.pgf", "iptc-psAPP13s-wIPTC-psAPP13s-noIPTC.jpg", "Reagan.jp2", + "Реган.jp2", "issue_ghsa_8949_hhfh_j7rj_poc.exv", "exiv2-bug495.jpg", "issue_1920_poc.tiff", diff --git a/unitTests/test_ImageFactory.cpp b/unitTests/test_ImageFactory.cpp index b77d33e68b..1c43c0b451 100644 --- a/unitTests/test_ImageFactory.cpp +++ b/unitTests/test_ImageFactory.cpp @@ -100,45 +100,49 @@ TEST(TheImageFactory, cannotCreateInstancesForSomeTypesInFiles) { TEST(TheImageFactory, loadInstancesDifferentImageTypes) { fs::path testData(TESTDATA_PATH); - std::string imagePath = (testData / "DSC_3079.jpg").string(); + fs::path imagePath = testData / "DSC_3079.jpg"; EXPECT_EQ(ImageType::jpeg, ImageFactory::getType(imagePath)); EXPECT_NO_THROW(ImageFactory::open(imagePath, false)); - imagePath = (testData / "exiv2-bug1108.exv").string(); + imagePath = testData / "exiv2-bug1108.exv"; EXPECT_EQ(ImageType::exv, ImageFactory::getType(imagePath)); EXPECT_NO_THROW(ImageFactory::open(imagePath, false)); - imagePath = (testData / "exiv2-canon-powershot-s40.crw").string(); + imagePath = testData / "exiv2-canon-powershot-s40.crw"; EXPECT_EQ(ImageType::crw, ImageFactory::getType(imagePath)); EXPECT_NO_THROW(ImageFactory::open(imagePath, false)); - imagePath = (testData / "exiv2-bug1044.tif").string(); + imagePath = testData / "exiv2-bug1044.tif"; EXPECT_EQ(ImageType::tiff, ImageFactory::getType(imagePath)); EXPECT_NO_THROW(ImageFactory::open(imagePath, false)); #ifdef EXV_HAVE_LIBZ - imagePath = (testData / "exiv2-bug1074.png").string(); + imagePath = testData / "exiv2-bug1074.png"; EXPECT_EQ(ImageType::png, ImageFactory::getType(imagePath)); EXPECT_NO_THROW(ImageFactory::open(imagePath, false)); #endif - imagePath = (testData / "BlueSquare.xmp").string(); + imagePath = testData / "BlueSquare.xmp"; EXPECT_EQ(ImageType::xmp, ImageFactory::getType(imagePath)); EXPECT_NO_THROW(ImageFactory::open(imagePath, false)); - imagePath = (testData / "exiv2-photoshop.psd").string(); + imagePath = testData / "exiv2-photoshop.psd"; EXPECT_EQ(ImageType::psd, ImageFactory::getType(imagePath)); EXPECT_NO_THROW(ImageFactory::open(imagePath, false)); - imagePath = (testData / "cve_2017_1000126_stack-oob-read.webp").string(); + imagePath = testData / "cve_2017_1000126_stack-oob-read.webp"; EXPECT_EQ(ImageType::webp, ImageFactory::getType(imagePath)); EXPECT_NO_THROW(ImageFactory::open(imagePath, false)); - imagePath = (testData / "imagemagick.pgf").string(); + imagePath = testData / "imagemagick.pgf"; EXPECT_EQ(ImageType::pgf, ImageFactory::getType(imagePath)); EXPECT_NO_THROW(ImageFactory::open(imagePath, false)); - imagePath = (testData / "Reagan.jp2").string(); + imagePath = testData / "Reagan.jp2"; + EXPECT_EQ(ImageType::jp2, ImageFactory::getType(imagePath)); + EXPECT_NO_THROW(ImageFactory::open(imagePath, false)); + + imagePath = testData / "Реган.jp2"; EXPECT_EQ(ImageType::jp2, ImageFactory::getType(imagePath)); EXPECT_NO_THROW(ImageFactory::open(imagePath, false)); }