diff --git a/include/exiv2/basicio.hpp b/include/exiv2/basicio.hpp index a96a166528..65d1d29e9e 100644 --- a/include/exiv2/basicio.hpp +++ b/include/exiv2/basicio.hpp @@ -941,12 +941,19 @@ class EXIV2API CurlIo : public RemoteIo { @throw Error In case of failure. */ EXIV2API DataBuf readFile(const std::string& path); +#ifdef _WIN32 +EXIV2API DataBuf readFile(const std::wstring& path); +#endif /*! @brief Write DataBuf \em buf to file \em path. @return Return the number of bytes written. @throw Error In case of failure. */ +#ifdef _WIN32 EXIV2API size_t writeFile(const DataBuf& buf, const std::string& path); +#else +EXIV2API size_t writeFile(const DataBuf& buf, const std::string& path); +#endif #ifdef EXV_USE_CURL /*! @brief The callback function is called by libcurl to write the data diff --git a/include/exiv2/exif.hpp b/include/exiv2/exif.hpp index 480c3f2185..8ce7c3cea2 100644 --- a/include/exiv2/exif.hpp +++ b/include/exiv2/exif.hpp @@ -303,6 +303,9 @@ class EXIV2API ExifThumb : public ExifThumbC { application that comes with OS X for one.) - David Harvey. */ void setJpegThumbnail(const std::string& path, URational xres, URational yres, uint16_t unit); +#ifdef _WIN32 + void setJpegThumbnail(const std::wstring& path, URational xres, URational yres, uint16_t unit); +#endif #endif /*! @brief Set the Exif thumbnail to the JPEG image pointed to by \em buf, diff --git a/include/exiv2/image.hpp b/include/exiv2/image.hpp index 00bfe40c6c..5fef008833 100644 --- a/include/exiv2/image.hpp +++ b/include/exiv2/image.hpp @@ -538,7 +538,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 @@ -555,7 +555,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 @@ -597,6 +597,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. @@ -630,6 +633,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 e44c0372b8..b82ec282bd 100644 --- a/src/basicio.cpp +++ b/src/basicio.cpp @@ -68,19 +68,13 @@ 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 + fs::path path_; //!< (Standard) path std::string openMode_; //!< File open mode FILE* fp_{}; //!< File stream pointer OpMode opMode_{opSeek}; //!< File open mode @@ -114,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 -} -#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); +FileIo::Impl::Impl(fs::path path) : path_(std::move(path)) { } -#endif int FileIo::Impl::switchMode(OpMode opMode) { if (opMode_ == opMode) @@ -178,7 +159,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 @@ -190,11 +171,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(); @@ -321,21 +298,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 @@ -447,7 +415,7 @@ void FileIo::transfer(BasicIo& src) { if (wasOpen) { if (open(lastMode) != 0) { - throw Error(ErrorCode::kerFileOpenFailed, path(), lastMode, strError()); + throw Error(ErrorCode::kerFileOpenFailed, path(), strError()); } } else close(); @@ -522,15 +490,28 @@ int FileIo::open(const std::string& mode) { 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; + auto wMode = [&] { + if (mode == "ab") + return L"ab"; + if (mode == "rb") + return L"rb"; + if (mode == "wb") + return L"wb"; + if (mode == "a+b") + return L"a+b"; + if (mode == "r+b") + return L"r+b"; + if (mode == "w+b") + return L"w+b"; + return L""; + }(); + + if (_wfopen_s(&p_->fp_, p_->path_.c_str(), wMode)) #else - p_->fp_ = ::fopen(path().c_str(), mode.c_str()); + p_->fp_ = std::fopen(path().c_str(), mode.c_str()); if (!p_->fp_) - return 1; #endif + return 1; return 0; } @@ -584,7 +565,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() { @@ -871,7 +854,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; } @@ -1678,6 +1661,28 @@ size_t writeFile(const DataBuf& buf, const std::string& path) { } return file.write(buf.c_data(), buf.size()); } + +#ifdef _WIN32 +DataBuf readFile(const std::wstring& path) { + FileIo file(path); + if (file.open("rb") != 0) { + throw Error(ErrorCode::kerFileOpenFailed, "rb", strError()); + } + DataBuf buf(static_cast(fs::file_size(path))); + if (file.read(buf.data(), buf.size()) != buf.size()) { + throw Error(ErrorCode::kerCallFailed, strError(), "FileIo::read"); + } + return buf; +} + +size_t writeFile(const DataBuf& buf, const std::wstring& path) { + FileIo file(path); + if (file.open("wb") != 0) { + throw Error(ErrorCode::kerFileOpenFailed, "wb", strError()); + } + return file.write(buf.c_data(), buf.size()); +} +#endif #endif #ifdef EXV_USE_CURL diff --git a/src/exif.cpp b/src/exif.cpp index ad489238a2..96b2dd63fb 100644 --- a/src/exif.cpp +++ b/src/exif.cpp @@ -411,6 +411,13 @@ void ExifThumb::setJpegThumbnail(const std::string& path, URational xres, URatio DataBuf thumb = readFile(path); // may throw setJpegThumbnail(thumb.c_data(), thumb.size(), xres, yres, unit); } + +#ifdef _WIN32 +void ExifThumb::setJpegThumbnail(const std::wstring& path, URational xres, URational yres, uint16_t unit) { + DataBuf thumb = readFile(path); // may throw + setJpegThumbnail(thumb.c_data(), thumb.size(), xres, yres, unit); +} +#endif #endif void ExifThumb::setJpegThumbnail(const byte* buf, size_t size, URational xres, URational yres, uint16_t unit) { diff --git a/src/image.cpp b/src/image.cpp index eaa78c8fd4..038d61ff0d 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -782,6 +782,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); @@ -825,7 +836,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 @@ -842,8 +853,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); @@ -887,6 +898,22 @@ Image::UniquePtr ImageFactory::create(ImageType type, const std::string& path) { throw Error(ErrorCode::kerUnsupportedImageType, static_cast(type)); return image; } + +#ifdef _WIN32 +Image::UniquePtr ImageFactory::create(ImageType type, const std::wstring& path) { + auto fileIo = std::make_unique(path); + // Create or overwrite the file, then close it + if (fileIo->open("w+b") != 0) + throw Error(ErrorCode::kerFileOpenFailed, "w+b", strError()); + fileIo->close(); + + BasicIo::UniquePtr io(std::move(fileIo)); + auto image = create(type, std::move(io)); + if (!image) + throw Error(ErrorCode::kerUnsupportedImageType, static_cast(type)); + return image; +} +#endif #endif Image::UniquePtr ImageFactory::create(ImageType type) { 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 d35a66c85b..fc49920845 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/meson.build b/unitTests/meson.build index b1b5b0d49d..f5534c3a2e 100644 --- a/unitTests/meson.build +++ b/unitTests/meson.build @@ -48,7 +48,7 @@ if host_machine.system() == 'windows' and get_option('default_library') != 'stat test_sources += int_lib endif -t_args = ['-UEXIV2API', '-DEXIV2API=', '-DTESTDATA_PATH="@0@"'.format(meson.current_source_dir() / '..' / 'test' / 'data')] +t_args = ['-UEXIV2API', '-DEXIV2API=', '-DTESTDATA_PATH="@0@/"'.format(meson.current_source_dir() / '..' / 'test' / 'data')] unit_tests = executable( 'unit_tests', 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)); }