diff --git a/include/miniocpp/args.h b/include/miniocpp/args.h index dae815b9..c6de1e84 100644 --- a/include/miniocpp/args.h +++ b/include/miniocpp/args.h @@ -18,6 +18,7 @@ #ifndef MINIO_CPP_ARGS_H_INCLUDED #define MINIO_CPP_ARGS_H_INCLUDED +#include #include #include #include @@ -208,7 +209,7 @@ using StatObjectArgs = ObjectConditionalReadArgs; using RemoveObjectArgs = ObjectVersionArgs; struct DownloadObjectArgs : public ObjectReadArgs { - std::string filename; + std::filesystem::path filename; bool overwrite; http::ProgressFunction progressfunc = nullptr; void* progress_userdata = nullptr; @@ -396,7 +397,7 @@ struct ComposeObjectArgs : public ObjectWriteArgs { }; // struct ComposeObjectArgs struct UploadObjectArgs : public PutObjectBaseArgs { - std::string filename; + std::filesystem::path filename; http::ProgressFunction progressfunc = nullptr; void* progress_userdata = nullptr; diff --git a/include/miniocpp/utils.h b/include/miniocpp/utils.h index 221c31e2..9738c44c 100644 --- a/include/miniocpp/utils.h +++ b/include/miniocpp/utils.h @@ -25,6 +25,7 @@ #endif #include +#include #include #include #include @@ -38,6 +39,17 @@ namespace minio::utils { +// path::u8string() returns std::string in C++17 but std::u8string in C++20; +// normalize to std::string so callers work under both standards. +inline std::string PathToUtf8(const std::filesystem::path& p) { +#ifdef __cpp_lib_char8_t + const std::u8string u8 = p.u8string(); + return std::string(reinterpret_cast(u8.data()), u8.size()); +#else + return p.u8string(); +#endif +} + inline constexpr unsigned int kMaxMultipartCount = 10000; // 10000 parts inline constexpr unsigned int kOptPartSize = 64 * 1024 * 1024; // 64MiB inline constexpr unsigned int kMinPartSize = 5 * 1024 * 1024; // 5MiB diff --git a/src/args.cc b/src/args.cc index 7ced7df5..752f7851 100644 --- a/src/args.cc +++ b/src/args.cc @@ -211,12 +211,21 @@ error::Error DownloadObjectArgs::Validate() const { if (error::Error err = ObjectReadArgs::Validate()) { return err; } - if (!utils::CheckNonEmptyString(filename)) { + if (!utils::CheckNonEmptyString(utils::PathToUtf8(filename))) { return error::Error("filename cannot be empty"); } - if (!overwrite && std::filesystem::exists(filename)) { - return error::Error("file " + filename + " already exists"); + if (!overwrite) { + std::error_code ec; + const bool exists = std::filesystem::exists(filename, ec); + if (ec) { + return error::Error("unable to check " + utils::PathToUtf8(filename) + + ": " + ec.message()); + } + if (exists) { + return error::Error("file " + utils::PathToUtf8(filename) + + " already exists"); + } } return error::SUCCESS; @@ -433,16 +442,21 @@ error::Error UploadObjectArgs::Validate() { if (error::Error err = ObjectArgs::Validate()) { return err; } - if (!utils::CheckNonEmptyString(filename)) { + if (!utils::CheckNonEmptyString(utils::PathToUtf8(filename))) { return error::Error("filename cannot be empty"); } - if (!std::filesystem::exists(filename)) { - return error::Error("file " + filename + " does not exist"); + std::error_code ec; + const std::uintmax_t obj_size = std::filesystem::file_size(filename, ec); + if (ec) { + if (ec == std::errc::no_such_file_or_directory) { + return error::Error("file " + utils::PathToUtf8(filename) + + " does not exist"); + } + return error::Error("unable to stat " + utils::PathToUtf8(filename) + ": " + + ec.message()); } - - std::filesystem::path file_path = filename; - object_size = static_cast(std::filesystem::file_size(file_path)); + object_size = static_cast(obj_size); return utils::CalcPartInfo(object_size, part_size, part_count); } diff --git a/src/client.cc b/src/client.cc index cb5f9851..25f46f4d 100644 --- a/src/client.cc +++ b/src/client.cc @@ -1160,13 +1160,15 @@ Result Client::DownloadObject(DownloadObjectArgs args) { etag = resp->etag; } - std::string temp_filename = - args.filename + "." + utils::UriEncode(etag) + ".part.minio"; + // Keep the temporary name a path so non-ASCII names survive on Windows + // (ofstream's path overload uses the wide API there). + std::filesystem::path temp_filename = args.filename; + temp_filename += "." + utils::UriEncode(etag) + ".part.minio"; std::ofstream fout(temp_filename, std::ios::trunc | std::ios::out | std::ios::binary); if (!fout.is_open()) { - return error::make("unable to open file " + - temp_filename); + return error::make( + "unable to open file " + utils::PathToUtf8(temp_filename)); } std::string region; @@ -1671,8 +1673,9 @@ Result Client::UploadObject(UploadObjectArgs args) { try { file.open(args.filename); } catch (std::system_error& err) { - return error::make( - "unable to open file " + args.filename + "; " + err.code().message()); + return error::make("unable to open file " + + utils::PathToUtf8(args.filename) + + "; " + err.code().message()); } PutObjectArgs po_args(file, args.object_size, 0);