diff --git a/.gitignore b/.gitignore index d93fb517..6e1ca8b5 100755 --- a/.gitignore +++ b/.gitignore @@ -46,4 +46,4 @@ latex/ CMakeSettings.json install trace.json -.cache/ \ No newline at end of file +.cache/ diff --git a/CMakeLists.txt b/CMakeLists.txt index e0186081..46af32b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,9 +91,10 @@ include(CheckIncludeFileCXX) check_include_file_cxx(any HAS_ANY) check_include_file_cxx(string_view HAS_STRING_VIEW) check_include_file_cxx(coroutine HAS_COROUTINE) -if (HAS_ANY AND HAS_STRING_VIEW AND HAS_COROUTINE) +check_include_file_cxx(filesystem HAS_FILESYSTEM) +if (HAS_ANY AND HAS_STRING_VIEW AND HAS_COROUTINE AND HAS_FILESYSTEM) set(DROGON_CXX_STANDARD 20) -elseif (HAS_ANY AND HAS_STRING_VIEW) +elseif (HAS_ANY AND HAS_STRING_VIEW AND HAS_FILESYSTEM) set(DROGON_CXX_STANDARD 17) else () set(DROGON_CXX_STANDARD 14) @@ -128,14 +129,33 @@ else (NOT WIN32) endif (NOT WIN32) if (DROGON_CXX_STANDARD EQUAL 14) - # With C++14, use boost to support any and string_view + # With C++14, use Boost to support any and string_view message(STATUS "use c++14") find_package(Boost 1.61.0 REQUIRED) - message(STATUS "boost include dir:" ${Boost_INCLUDE_DIR}) + find_package(Boost 1.61.0 REQUIRED COMPONENTS filesystem) + message(STATUS "Using Boost filesytem, string_view and any") + message(STATUS "Boost include dir: " ${Boost_INCLUDE_DIR}) target_link_libraries(${PROJECT_NAME} PUBLIC Boost::boost) + target_link_libraries(${PROJECT_NAME} PUBLIC Boost::filesystem) list(APPEND INCLUDE_DIRS_FOR_DYNAMIC_VIEW ${Boost_INCLUDE_DIR}) elseif (DROGON_CXX_STANDARD EQUAL 17) + # With C++17, use Boost if std::filesystem::path is missing message(STATUS "use c++17") + # Check for partial implementation of c++17 (Windows/OSX only?) + try_compile(check_filesystem_path ${CMAKE_BINARY_DIR}/cmaketest + ${PROJECT_SOURCE_DIR}/cmake/tests/check_has_std_filesystem_path.cc + CXX_STANDARD 17) + if (check_filesystem_path) + message(STATUS "Using c++17 filesytem::path") + else() + find_package(Boost 1.49.0 COMPONENTS filesystem system REQUIRED) + message(STATUS "Using Boost filesytem::path") + message(STATUS "Boost include dir: " ${Boost_INCLUDE_DIR}) + include_directories(${BOOST_INCLUDE_DIRS}) + message(STATUS "Boost libraries: " ${Boost_LIBRARIES}) + target_link_libraries(${PROJECT_NAME} PUBLIC Boost::filesystem Boost::system) + list(APPEND INCLUDE_DIRS_FOR_DYNAMIC_VIEW ${Boost_INCLUDE_DIR}) + endif() else () message(STATUS "use c++20") endif () diff --git a/cmake/tests/check_has_std_filesystem_path.cc b/cmake/tests/check_has_std_filesystem_path.cc new file mode 100755 index 00000000..1b368c33 --- /dev/null +++ b/cmake/tests/check_has_std_filesystem_path.cc @@ -0,0 +1,7 @@ +#include + +int main() +{ + std::filesystem::path aPath("../"); + return 0; +} diff --git a/drogon_ctl/templates/cmake.csp b/drogon_ctl/templates/cmake.csp index f244b72d..c10ace65 100644 --- a/drogon_ctl/templates/cmake.csp +++ b/drogon_ctl/templates/cmake.csp @@ -32,10 +32,12 @@ target_link_libraries(${PROJECT_NAME} PRIVATE Drogon::Drogon) # ############################################################################## if (CMAKE_CXX_STANDARD LESS 17) - # With C++14, use boost to support any and string_view + # With C++14, use boost to support any, string_view and filesystem message(STATUS "use c++14") find_package(Boost 1.61.0 REQUIRED) - target_include_directories(${PROJECT_NAME} PRIVATE ${Boost_INCLUDE_DIRS}) + find_package(Boost 1.61.0 REQUIRED COMPONENTS filesystem) + target_link_libraries(${PROJECT_NAME} PUBLIC Boost::boost) + target_link_libraries(${PROJECT_NAME} PUBLIC Boost::filesystem) elseif (CMAKE_CXX_STANDARD LESS 20) message(STATUS "use c++17") else () diff --git a/lib/inc/drogon/utils/Utilities.h b/lib/inc/drogon/utils/Utilities.h index 94c0ffce..90c67b1f 100644 --- a/lib/inc/drogon/utils/Utilities.h +++ b/lib/inc/drogon/utils/Utilities.h @@ -154,6 +154,39 @@ DROGON_EXPORT std::string formattedString(const char *format, ...); */ DROGON_EXPORT int createPath(const std::string &path); +#ifdef _WIN32 +/** + * @brief Convert a UTF-8 path with arbitrary directory separator to a standard + * Windows UCS2 path. + * @note Although windows accept both slash and backslash as directory + * separator, it is better to stick to its standard. + * + * @param strUtf8Path Ascii path considered as being UTF-8 + * + * @return std::wstring path, with windows standard backslash directory + * separator. + */ +DROGON_EXPORT std::wstring toNativePath(const std::string &strPath); +/** + * @brief Convert a UCS2 to an UTF-8 path. + * + * @param strPath Wide char unicode path + * + * @return std::string path in UTF-8 unicode, with standard '/' directory + * separator. + */ +DROGON_EXPORT std::string fromNativePath(std::wstring strPath); +#else // _WIN32 +inline const std::string &toNativePath(const std::string &strPath) +{ + return strPath; +} +inline const std::string &fromNativePath(const std::string &strPath) +{ + return strPath; +} +#endif // _WIN32 + /// Replace all occurances of from to to inplace /** * @param from string to replace diff --git a/lib/src/CacheFile.cc b/lib/src/CacheFile.cc index d1c80fe7..3a0cb0a0 100644 --- a/lib/src/CacheFile.cc +++ b/lib/src/CacheFile.cc @@ -16,6 +16,7 @@ #include #ifdef _WIN32 #include +#include #else #include #include @@ -29,7 +30,8 @@ CacheFile::CacheFile(const std::string &path, bool autoDelete) #ifndef _MSC_VER file_ = fopen(path_.data(), "wb+"); #else - if (fopen_s(&file_, path_.data(), "wb+") != 0) + auto wPath{drogon::utils::toNativePath(path)}; + if (_wfopen_s(&file_, wPath.c_str(), L"wb+") != 0) { file_ = nullptr; } @@ -46,7 +48,8 @@ CacheFile::~CacheFile() { fclose(file_); #ifdef _WIN32 - _unlink(path_.data()); + auto wPath{drogon::utils::toNativePath(path_)}; + _wunlink(wPath.c_str()); #else unlink(path_.data()); #endif diff --git a/lib/src/ConfigLoader.cc b/lib/src/ConfigLoader.cc index aa19650e..6f5e9383 100644 --- a/lib/src/ConfigLoader.cc +++ b/lib/src/ConfigLoader.cc @@ -22,9 +22,14 @@ #include #ifndef _WIN32 #include +#define os_access access #else #include +#define os_access _waccess +#define R_OK 04 +#define W_OK 02 #endif +#include using namespace drogon; static bool bytesSize(std::string &sizeStr, size_t &size) @@ -94,27 +99,20 @@ static bool bytesSize(std::string &sizeStr, size_t &size) } ConfigLoader::ConfigLoader(const std::string &configFile) { -#ifdef _WIN32 - if (_access(configFile.c_str(), 0) != 0) -#else - if (access(configFile.c_str(), 0) != 0) -#endif + if (os_access(drogon::utils::toNativePath(configFile).c_str(), 0) != 0) { std::cerr << "Config file " << configFile << " not found!" << std::endl; exit(1); } -#ifdef _WIN32 - if (_access(configFile.c_str(), 04) != 0) -#else - if (access(configFile.c_str(), R_OK) != 0) -#endif + if (os_access(drogon::utils::toNativePath(configFile).c_str(), R_OK) != 0) { std::cerr << "No permission to read config file " << configFile << std::endl; exit(1); } configFile_ = configFile; - std::ifstream infile(configFile.c_str(), std::ifstream::in); + std::ifstream infile(drogon::utils::toNativePath(configFile).c_str(), + std::ifstream::in); if (infile) { try diff --git a/lib/src/HttpAppFrameworkImpl.cc b/lib/src/HttpAppFrameworkImpl.cc index 0951ca0e..9654135b 100644 --- a/lib/src/HttpAppFrameworkImpl.cc +++ b/lib/src/HttpAppFrameworkImpl.cc @@ -40,6 +40,7 @@ #include #include #include +#include "filesystem.h" #include #include @@ -58,8 +59,12 @@ #include #include #include +#define os_access access #else #include +#define os_access _waccess +#define R_OK 04 +#define W_OK 02 #endif using namespace drogon; @@ -393,20 +398,14 @@ HttpAppFramework &HttpAppFrameworkImpl::setLogPath( { if (logPath.empty()) return *this; -#ifdef _WIN32 - if (_access(logPath.c_str(), 0) != 0) -#else - if (access(logPath.c_str(), 0) != 0) -#endif + // std::filesystem does not provide a method to check access permissions, so + // keep existing code + if (os_access(utils::toNativePath(logPath).c_str(), 0) != 0) { std::cerr << "Log path does not exist!\n"; exit(1); } -#ifdef _WIN32 - if (_access(logPath.c_str(), 06) != 0) -#else - if (access(logPath.c_str(), R_OK | W_OK) != 0) -#endif + if (os_access(utils::toNativePath(logPath).c_str(), R_OK | W_OK) != 0) { std::cerr << "Unable to access log path!\n"; exit(1); @@ -493,11 +492,9 @@ void HttpAppFrameworkImpl::run() // set logger if (!logPath_.empty()) { -#ifdef _WIN32 - if (_access(logPath_.c_str(), 06) != 0) -#else - if (access(logPath_.c_str(), R_OK | W_OK) != 0) -#endif + // std::filesystem does not provide a method to check access + // permissions, so keep existing code + if (os_access(utils::toNativePath(logPath_).c_str(), R_OK | W_OK) != 0) { LOG_ERROR << "log file path not exist"; abort(); @@ -666,22 +663,14 @@ HttpAppFramework &HttpAppFrameworkImpl::setUploadPath( const std::string &uploadPath) { assert(!uploadPath.empty()); - if (uploadPath[0] == '/' || - (uploadPath.length() >= 2 && uploadPath[0] == '.' && - uploadPath[1] == '/') || - (uploadPath.length() >= 3 && uploadPath[0] == '.' && - uploadPath[1] == '.' && uploadPath[2] == '/') || - uploadPath == "." || uploadPath == "..") + + filesystem::path fsUploadPath(utils::toNativePath(uploadPath)); + if (!fsUploadPath.is_absolute()) { - uploadPath_ = uploadPath; - } - else - { - if (rootPath_[rootPath_.length() - 1] == '/') - uploadPath_ = rootPath_ + uploadPath; - else - uploadPath_ = rootPath_ + "/" + uploadPath; + filesystem::path fsRoot(utils::toNativePath(rootPath_)); + fsUploadPath = fsRoot / fsUploadPath; } + uploadPath_ = utils::fromNativePath(fsUploadPath.native()); return *this; } void HttpAppFrameworkImpl::findSessionForRequest(const HttpRequestImplPtr &req) diff --git a/lib/src/HttpFileImpl.cc b/lib/src/HttpFileImpl.cc index 69514c9f..8baef509 100644 --- a/lib/src/HttpFileImpl.cc +++ b/lib/src/HttpFileImpl.cc @@ -17,14 +17,16 @@ #include #include #include +// Switch between native c++17 or boost for c++14 +#ifdef HAS_STD_FILESYSTEM_PATH +#include +namespace stl = std; +#else // HAS_STD_FILESYSTEM_PATH +#include +namespace stl = boost::system; +#endif // HAS_STD_FILESYSTEM_PATH using namespace drogon; -// Verify if last char of path is a slash, otherwise, add the slash -static inline void ensureSlashPostfix(std::string &path) -{ - if (path[path.length() - 1] != '/') - path += '/'; -} int HttpFileImpl::save() const { @@ -36,59 +38,43 @@ int HttpFileImpl::save(const std::string &path) const assert(!path.empty()); if (fileName_.empty()) return -1; - std::string fileName; - if (path[0] == '/' || - (path.length() >= 2 && path[0] == '.' && path[1] == '/') || - (path.length() >= 3 && path[0] == '.' && path[1] == '.' && - path[2] == '/') || - path == "." || path == "..") + filesystem::path fsPath(utils::toNativePath(path)); + if (!fsPath.is_absolute() && + (!fsPath.has_parent_path() || + (fsPath.begin()->string() != "." && fsPath.begin()->string() != ".."))) { - // Absolute or relative path - fileName = path; + filesystem::path fsUploadPath(utils::toNativePath( + HttpAppFrameworkImpl::instance().getUploadPath())); + fsPath = fsUploadPath / fsPath; } - else - { - fileName = HttpAppFrameworkImpl::instance().getUploadPath(); - ensureSlashPostfix(fileName); - fileName += path; - } - if (utils::createPath(fileName) < 0) - return -1; - ensureSlashPostfix(fileName); - fileName += fileName_; - return saveTo(fileName); + filesystem::path fsFileName(utils::toNativePath(fileName_)); + return saveTo(fsPath / fsFileName); } int HttpFileImpl::saveAs(const std::string &fileName) const { assert(!fileName.empty()); - std::string pathAndFileName; - if (fileName[0] == '/' || - (fileName.length() >= 2 && fileName[0] == '.' && fileName[1] == '/') || - (fileName.length() >= 3 && fileName[0] == '.' && fileName[1] == '.' && - fileName[2] == '/')) + filesystem::path fsFileName(utils::toNativePath(fileName)); + if (!fsFileName.is_absolute() && (!fsFileName.has_parent_path() || + (fsFileName.begin()->string() != "." && + fsFileName.begin()->string() != ".."))) { - // Absolute or relative path - pathAndFileName = fileName; + filesystem::path fsUploadPath(utils::toNativePath( + HttpAppFrameworkImpl::instance().getUploadPath())); + fsFileName = fsUploadPath / fsFileName; } - else + if (fsFileName.has_parent_path()) { - pathAndFileName = HttpAppFrameworkImpl::instance().getUploadPath(); - ensureSlashPostfix(pathAndFileName); - pathAndFileName += fileName; - } - auto pathPos = pathAndFileName.rfind('/'); - if (pathPos != std::string::npos) - { - std::string path = pathAndFileName.substr(0, pathPos); - if (utils::createPath(path) < 0) + stl::error_code err; + filesystem::create_directories(fsFileName.parent_path(), err); + if (err) return -1; } - return saveTo(pathAndFileName); + return saveTo(fsFileName); } -int HttpFileImpl::saveTo(const std::string &pathAndFileName) const +int HttpFileImpl::saveTo(const filesystem::path &pathAndFileName) const { LOG_TRACE << "save uploaded file:" << pathAndFileName; - std::ofstream file(pathAndFileName, std::ios::binary); + std::ofstream file(pathAndFileName.native(), std::ios::binary); if (file.is_open()) { file.write(fileContent_.data(), fileContent_.size()); @@ -101,6 +87,7 @@ int HttpFileImpl::saveTo(const std::string &pathAndFileName) const return -1; } } + std::string HttpFileImpl::getMd5() const { return utils::getMd5(fileContent_.data(), fileContent_.size()); diff --git a/lib/src/HttpFileImpl.h b/lib/src/HttpFileImpl.h index 39fc82e4..09cfc2c4 100644 --- a/lib/src/HttpFileImpl.h +++ b/lib/src/HttpFileImpl.h @@ -15,12 +15,14 @@ #pragma once #include "HttpUtils.h" #include +#include "filesystem.h" #include #include #include #include #include + namespace drogon { class HttpFileImpl @@ -110,7 +112,8 @@ class HttpFileImpl /// Return the md5 string of the file std::string getMd5() const; - int saveTo(const std::string &pathAndFileName) const; + // int saveTo(const std::string &pathAndFileName) const; + int saveTo(const filesystem::path &pathAndFileName) const; void setRequest(const HttpRequestPtr &req) { requestPtr_ = req; @@ -122,4 +125,4 @@ class HttpFileImpl string_view fileContent_; HttpRequestPtr requestPtr_; }; -} // namespace drogon \ No newline at end of file +} // namespace drogon diff --git a/lib/src/HttpRequestImpl.cc b/lib/src/HttpRequestImpl.cc index 0bd4a369..de59a596 100644 --- a/lib/src/HttpRequestImpl.cc +++ b/lib/src/HttpRequestImpl.cc @@ -287,7 +287,8 @@ void HttpRequestImpl::appendToBuffer(trantor::MsgBuffer *output) const content.append("\"; filename=\""); content.append(file.fileName()); content.append("\"\r\n\r\n"); - std::ifstream infile(file.path(), std::ifstream::binary); + std::ifstream infile(utils::toNativePath(file.path()), + std::ifstream::binary); if (!infile) { LOG_ERROR << file.path() << " not found"; diff --git a/lib/src/HttpResponseImpl.cc b/lib/src/HttpResponseImpl.cc index b6b319a9..c0ef2f49 100644 --- a/lib/src/HttpResponseImpl.cc +++ b/lib/src/HttpResponseImpl.cc @@ -17,14 +17,19 @@ #include "HttpUtils.h" #include #include +#include "filesystem.h" #include #include #include #include #include -#ifdef _WIN32 -#define stat _stati64 +// Switch between native c++17 or boost for c++14 +#ifdef HAS_STD_FILESYSTEM_PATH +namespace stl = std; +#else +namespace stl = boost::system; #endif + using namespace trantor; using namespace drogon; @@ -235,7 +240,7 @@ HttpResponsePtr HttpResponse::newFileResponse( const std::string &attachmentFileName, ContentType type) { - std::ifstream infile(fullPath, std::ifstream::binary); + std::ifstream infile(utils::toNativePath(fullPath), std::ifstream::binary); LOG_TRACE << "send http file:" << fullPath; if (!infile) { @@ -345,19 +350,19 @@ void HttpResponseImpl::makeHeaderString(trantor::MsgBuffer &buffer) } else { - struct stat filestat + stl::error_code err; + filesystem::path fsSendfile(utils::toNativePath(sendfileName_)); + auto fileSize = filesystem::file_size(fsSendfile, err); + if (err) { - }; - if (stat(sendfileName_.data(), &filestat) < 0) - { - LOG_SYSERR << sendfileName_ << " stat error"; + LOG_SYSERR << fsSendfile << " stat error " << err.value() + << ": " << err.message(); return; } - len = snprintf( - buffer.beginWrite(), - buffer.writableBytes(), - contentLengthFormatString(), - filestat.st_size); + len = snprintf(buffer.beginWrite(), + buffer.writableBytes(), + contentLengthFormatString(), + fileSize); } buffer.hasWritten(len); if (headers_.find("connection") == headers_.end()) diff --git a/lib/src/StaticFileRouter.cc b/lib/src/StaticFileRouter.cc index 8f068259..afadeeff 100644 --- a/lib/src/StaticFileRouter.cc +++ b/lib/src/StaticFileRouter.cc @@ -23,11 +23,20 @@ #ifndef _WIN32 #include #else -#define stat _stati64 +#define stat _wstati64 #define S_ISREG(m) (((m)&0170000) == (0100000)) #define S_ISDIR(m) (((m)&0170000) == (0040000)) #endif #include +// Switch between native c++17 or boost for c++14 +#include "filesystem.h" +#ifdef HAS_STD_FILESYSTEM_PATH +#include +namespace stl = std; +#else +#include +namespace stl = boost::system; +#endif using namespace drogon; @@ -137,13 +146,14 @@ void StaticFileRouter::route( std::string filePath = location.realLocation_ + std::string{restOfThePath.data(), restOfThePath.length()}; - struct stat fileStat; - if (stat(filePath.c_str(), &fileStat) != 0) + filesystem::path fsFilePath(utils::toNativePath(filePath)); + stl::error_code err; + if (!filesystem::exists(fsFilePath, err)) { defaultHandler_(req, std::move(callback)); return; } - if (S_ISDIR(fileStat.st_mode)) + if (filesystem::is_directory(fsFilePath, err)) { // Check if path is eligible for an implicit index.html if (implicitPageEnable_) @@ -215,10 +225,11 @@ void StaticFileRouter::route( std::string directoryPath = HttpAppFrameworkImpl::instance().getDocumentRoot() + path; - struct stat fileStat; - if (stat(directoryPath.c_str(), &fileStat) == 0) + filesystem::path fsDirectoryPath(utils::toNativePath(directoryPath)); + stl::error_code err; + if (filesystem::exists(fsDirectoryPath, err)) { - if (S_ISDIR(fileStat.st_mode)) + if (filesystem::is_directory(fsDirectoryPath, err)) { // Check if path is eligible for an implicit index.html if (implicitPageEnable_) @@ -299,9 +310,15 @@ void StaticFileRouter::sendStaticFileResponse( } else { - struct stat fileStat; LOG_TRACE << "enabled LastModify"; - if (stat(filePath.c_str(), &fileStat) == 0 && + // std::filesystem::file_time_type::clock::to_time_t still not + // implemented by M$, even in c++20, so keep calls to stat() +#ifdef _WIN32 + struct _stati64 fileStat; +#else // _WIN32 + struct stat fileStat; +#endif // _WIN32 + if (stat(utils::toNativePath(filePath).c_str(), &fileStat) == 0 && S_ISREG(fileStat.st_mode)) { fileExists = true; @@ -361,9 +378,10 @@ void StaticFileRouter::sendStaticFileResponse( } if (!fileExists) { - struct stat fileStat; - if (stat(filePath.c_str(), &fileStat) != 0 || - !S_ISREG(fileStat.st_mode)) + filesystem::path fsFilePath(utils::toNativePath(filePath)); + stl::error_code err; + if (!filesystem::exists(fsFilePath, err) || + !filesystem::is_regular_file(fsFilePath, err)) { defaultHandler_(req, std::move(callback)); return; @@ -383,9 +401,10 @@ void StaticFileRouter::sendStaticFileResponse( { // Find compressed file first. auto brFileName = filePath + ".br"; - struct stat filestat; - if (stat(brFileName.c_str(), &filestat) == 0 && - S_ISREG(filestat.st_mode)) + filesystem::path fsBrFile(utils::toNativePath(brFileName)); + stl::error_code err; + if (filesystem::exists(fsBrFile, err) && + filesystem::is_regular_file(fsBrFile, err)) { resp = HttpResponse::newFileResponse(brFileName, @@ -399,9 +418,10 @@ void StaticFileRouter::sendStaticFileResponse( { // Find compressed file first. auto gzipFileName = filePath + ".gz"; - struct stat filestat; - if (stat(gzipFileName.c_str(), &filestat) == 0 && - S_ISREG(filestat.st_mode)) + filesystem::path fsGzipFile(utils::toNativePath(gzipFileName)); + stl::error_code err; + if (filesystem::exists(fsGzipFile, err) && + filesystem::is_regular_file(fsGzipFile, err)) { resp = HttpResponse::newFileResponse(gzipFileName, diff --git a/lib/src/Utilities.cc b/lib/src/Utilities.cc index e404be70..0351f14e 100644 --- a/lib/src/Utilities.cc +++ b/lib/src/Utilities.cc @@ -13,6 +13,7 @@ */ #include +#include "filesystem.h" #include #include #ifdef OpenSSL_FOUND @@ -51,6 +52,15 @@ #include #include +// Switch between native c++17 or boost for c++14 +#ifdef HAS_STD_FILESYSTEM_PATH +#include +namespace stl = std; +#else +#include +namespace stl = boost::system; +#endif + #ifdef _WIN32 char *strptime(const char *s, const char *f, struct tm *tm) { @@ -1043,61 +1053,70 @@ std::string formattedString(const char *format, ...) return strBuffer; } +#ifdef _WIN32 +std::string utf8_encode(const std::wstring &wstr) +{ + if (wstr.empty()) + return {}; + int nSizeNeeded = ::WideCharToMultiByte( + CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL); + std::string strTo(nSizeNeeded, 0); + ::WideCharToMultiByte(CP_UTF8, + 0, + &wstr[0], + (int)wstr.size(), + &strTo[0], + nSizeNeeded, + NULL, + NULL); + return strTo; +} +std::wstring utf8_decode(const std::string &str) +{ + if (str.empty()) + return {}; + int nSizeNeeded = + ::MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), NULL, 0); + std::wstring wstrTo(nSizeNeeded, 0); + ::MultiByteToWideChar( + CP_UTF8, 0, &str[0], (int)str.size(), &wstrTo[0], nSizeNeeded); + return wstrTo; +} + +std::wstring toNativePath(const std::string &strUtf8Path) +{ + // Consider path to be utf-8, to allow using paths with unicode characters + auto wPath{utf8_decode(strUtf8Path)}; + // Not needed: normalize path (just replaces '/' with '\') + filesystem::path fsPath(wPath); + // Not needed: normalize path (just replaces '/' with '\') + fsPath.make_preferred(); + return fsPath.native(); +} + +std::string fromNativePath(std::wstring wstrPath) +{ + std::replace(wstrPath.begin(), wstrPath.end(), L'\\', L'/'); + auto strPath{utf8_encode(wstrPath)}; + return strPath; +} +#endif // _WIN32 + int createPath(const std::string &path) { - auto tmpPath = path; - std::stack pathStack; -#ifdef _WIN32 - while (_access(tmpPath.c_str(), 06) != 0) -#else - while (access(tmpPath.c_str(), F_OK) != 0) -#endif + if (path.empty()) + return 0; + auto osPath{toNativePath(path)}; + if (osPath.back() != filesystem::path::preferred_separator) + osPath.push_back(filesystem::path::preferred_separator); + filesystem::path fsPath(osPath); + stl::error_code err; + filesystem::create_directories(fsPath, err); + if (err) { - if (tmpPath == "./" || tmpPath == "/") - return -1; - while (tmpPath[tmpPath.length() - 1] == '/') - tmpPath.resize(tmpPath.length() - 1); - auto pos = tmpPath.rfind('/'); - if (pos != std::string::npos) - { - pathStack.push(tmpPath.substr(pos)); - tmpPath = tmpPath.substr(0, pos + 1); - } - else - { - pathStack.push(tmpPath); - tmpPath.clear(); - break; - } - } - while (pathStack.size() > 0) - { - if (tmpPath.empty()) - { - tmpPath = pathStack.top(); - } - else - { - if (tmpPath[tmpPath.length() - 1] == '/') - { - tmpPath.append(pathStack.top()); - } - else - { - tmpPath.append("/").append(pathStack.top()); - } - } - pathStack.pop(); - -#ifdef _WIN32 - if (_mkdir(tmpPath.c_str()) == -1) -#else - if (mkdir(tmpPath.c_str(), 0755) == -1) -#endif - { - LOG_ERROR << "Can't create path:" << path; - return -1; - } + LOG_ERROR << "Error " << err.value() << " creating path " << osPath + << ": " << err.message(); + return -1; } return 0; } diff --git a/lib/src/filesystem.h b/lib/src/filesystem.h new file mode 100644 index 00000000..7e9eb102 --- /dev/null +++ b/lib/src/filesystem.h @@ -0,0 +1,54 @@ +/* @author An Tao + * + * Copyright 2018, An Tao. All rights reserved. + * https://github.com/an-tao/drogon + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Drogon + * + */ + +#pragma once + +/* Check of std::filesystem::path availability: + * - OS X: depends on the target OSX version (>= 10.15 Catalina) + * - Windows: Visual Studio >= 2019 (c++20) + * - Others: should already have it in c++17 + */ +#if (defined(__APPLE__) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 101500L) || \ + (defined(_WIN32) && defined(_MSC_VER) && _MSC_VER >= 1920) || \ + (!defined(__APPLE__) && !defined(_WIN32) && __cplusplus >= 201703L) +#define HAS_STD_FILESYSTEM_PATH +#endif + +#ifdef HAS_STD_FILESYSTEM_PATH +#include +#else +#include +#endif + +namespace drogon +{ +#ifdef HAS_STD_FILESYSTEM_PATH +namespace filesystem = std::filesystem; +#else +namespace filesystem = boost::filesystem; +#endif +} // namespace drogon + +namespace trantor +{ +inline LogStream &operator<<(LogStream &ls, const drogon::filesystem::path &v) +{ +#if defined(_WIN32) && defined(__cpp_char8_t) + // Convert UCS-2 to UTF-8, not ASCII - not needed on other OSes + auto u8path{v.u8string()}; + ls.append((const char *)u8path.data(), u8path.length()); +#else + // No need to convert + ls.append(v.string().data(), v.string().length()); +#endif + return ls; +} +} // namespace trantor diff --git a/trantor b/trantor index 15556700..d7f700ac 160000 --- a/trantor +++ b/trantor @@ -1 +1 @@ -Subproject commit 15556700e5ce305c7d3fd5a970a58ef6b8297e0c +Subproject commit d7f700ac7ca0b6c656755142378bc71cd34ecfed