diff --git a/httplib.h b/httplib.h index c621f5b..d75dcf0 100644 --- a/httplib.h +++ b/httplib.h @@ -2258,13 +2258,33 @@ make_basic_authentication_header(const std::string &username, namespace detail { +#if defined(_WIN32) +std::wstring u8string_to_wstring(const char *s) { + std::wstring ws; + auto len = static_cast(strlen(s)); + auto wlen = ::MultiByteToWideChar(CP_UTF8, 0, s, len, nullptr, 0); + if (wlen > 0) { + ws.resize(wlen); + wlen = ::MultiByteToWideChar(CP_UTF8, 0, s, len, const_cast(reinterpret_cast(ws.data())), wlen); + if (wlen != ws.size()) { + ws.clear(); + } + } + return ws; +} +#endif + struct FileStat { FileStat(const std::string &path); bool is_file() const; bool is_dir() const; private: +#if defined(_WIN32) + struct _stat st_; +#else struct stat st_; +#endif int ret_ = -1; }; @@ -2639,7 +2659,12 @@ inline bool is_valid_path(const std::string &path) { } inline FileStat::FileStat(const std::string &path) { +#if defined(_WIN32) + auto wpath = u8string_to_wstring(path.c_str()); + ret_ = _wstat(wpath.c_str(), &st_); +#else ret_ = stat(path.c_str(), &st_); +#endif } inline bool FileStat::is_file() const { return ret_ >= 0 && S_ISREG(st_.st_mode); @@ -2909,10 +2934,8 @@ inline bool mmap::open(const char *path) { close(); #if defined(_WIN32) - std::wstring wpath; - for (size_t i = 0; i < strlen(path); i++) { - wpath += path[i]; - } + auto wpath = u8string_to_wstring(path); + if (wpath.empty()) { return false; } #if _WIN32_WINNT >= _WIN32_WINNT_WIN8 hFile_ = ::CreateFile2(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, diff --git a/test/test.cc b/test/test.cc index 154b60c..556bf24 100644 --- a/test/test.cc +++ b/test/test.cc @@ -1,3 +1,4 @@ +// NOTE: This file should be saved as UTF-8 w/ BOM #include #include @@ -241,7 +242,7 @@ TEST(DecodeURLTest, PercentCharacter) { detail::decode_url( R"(descrip=Gastos%20%C3%A1%C3%A9%C3%AD%C3%B3%C3%BA%C3%B1%C3%91%206)", false), - R"(descrip=Gastos áéíóúñÑ 6)"); + u8"descrip=Gastos áéíóúñÑ 6"); } TEST(DecodeURLTest, PercentCharacterNUL) { @@ -267,9 +268,9 @@ TEST(EncodeQueryParamTest, ParseReservedCharactersTest) { } TEST(EncodeQueryParamTest, TestUTF8Characters) { - string chineseCharacters = "中国語"; - string russianCharacters = "дом"; - string brazilianCharacters = "óculos"; + string chineseCharacters = u8"中国語"; + string russianCharacters = u8"дом"; + string brazilianCharacters = u8"óculos"; EXPECT_EQ(detail::encode_query_param(chineseCharacters), "%E4%B8%AD%E5%9B%BD%E8%AA%9E"); @@ -5271,6 +5272,27 @@ TEST(MountTest, Redicect) { EXPECT_EQ(StatusCode::OK_200, res->status); } +TEST(MountTest, MultibytesPathName) { + Server svr; + + auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); }); + auto se = detail::scope_exit([&] { + svr.stop(); + listen_thread.join(); + ASSERT_FALSE(svr.is_running()); + }); + + svr.set_mount_point("/", "./www"); + svr.wait_until_ready(); + + Client cli("localhost", PORT); + + auto res = cli.Get(u8"/日本語Dir/日本語File.txt"); + ASSERT_TRUE(res); + EXPECT_EQ(StatusCode::OK_200, res->status); + EXPECT_EQ(u8"日本語コンテンツ", res->body); +} + TEST(KeepAliveTest, ReadTimeout) { Server svr; diff --git a/test/www/日本語Dir/日本語File.txt b/test/www/日本語Dir/日本語File.txt new file mode 100644 index 0000000..3cc1ce7 --- /dev/null +++ b/test/www/日本語Dir/日本語File.txt @@ -0,0 +1 @@ +日本語コンテンツ \ No newline at end of file