forked from HowardHinnant/date
Allow set_install()
on Windows to use a file path containing Unicode
Anywhere we utilize that install file path, we carefully convert it to UTF-16 and utilize wide character helpers for file manipulation
This commit is contained in:
committed by
Howard Hinnant
parent
d9049ee697
commit
529a09267f
155
src/tz.cpp
155
src/tz.cpp
@@ -198,6 +198,35 @@ namespace
|
||||
using co_task_mem_ptr = std::unique_ptr<wchar_t[], task_mem_deleter>;
|
||||
}
|
||||
|
||||
static
|
||||
std::wstring
|
||||
convert_utf8_to_utf16(const std::string& s)
|
||||
{
|
||||
std::wstring out;
|
||||
const int size = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0);
|
||||
|
||||
if (size == 0)
|
||||
{
|
||||
std::string msg = "Failed to determine required size when converting \"";
|
||||
msg += s;
|
||||
msg += "\" to UTF-16.";
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
|
||||
out.resize(size);
|
||||
const int check = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, &out[0], size);
|
||||
|
||||
if (size != check)
|
||||
{
|
||||
std::string msg = "Failed to convert \"";
|
||||
msg += s;
|
||||
msg += "\" to UTF-16.";
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// We might need to know certain locations even if not using the remote API,
|
||||
// so keep these routines out of that block for now.
|
||||
static
|
||||
@@ -267,6 +296,89 @@ get_download_folder()
|
||||
|
||||
# endif // !_WIN32
|
||||
|
||||
/*
|
||||
* This class is provided to mimic the following usage of `ifstream`:
|
||||
*
|
||||
* std::ifstream is(filename);
|
||||
*
|
||||
* file_streambuf ibuf(filename);
|
||||
* std::istream is(&ibuf);
|
||||
*
|
||||
* This is required because `ifstream` does not support opening files
|
||||
* containing wide characters on Windows. On Windows, `file_streambuf` uses
|
||||
* `file_open()` to convert the file name to UTF-16 before opening it with
|
||||
* `_wfopen()`.
|
||||
*
|
||||
* Note that this is not an exact re-implementation of `ifstream`,
|
||||
* but is enough for usage here.
|
||||
*
|
||||
* It is partially based on these two implementations:
|
||||
* - fdinbuf from http://www.josuttis.com/cppcode/fdstream.html
|
||||
* - stdiobuf https://stackoverflow.com/questions/12342542/convert-file-to-ifstream-c-android-ndk
|
||||
*
|
||||
* Apparently MSVC provides non-standard overloads of `ifstream` that support
|
||||
* a `const wchar_t*` file name, but MinGW does not https://stackoverflow.com/a/822032
|
||||
*/
|
||||
class file_streambuf
|
||||
: public std::streambuf
|
||||
{
|
||||
private:
|
||||
FILE* file_;
|
||||
static const int buffer_size_ = 1024;
|
||||
char buffer_[buffer_size_];
|
||||
|
||||
public:
|
||||
~file_streambuf()
|
||||
{
|
||||
if (file_)
|
||||
{
|
||||
::fclose(file_);
|
||||
}
|
||||
}
|
||||
file_streambuf(const file_streambuf&) = delete;
|
||||
file_streambuf& operator=(const file_streambuf&) = delete;
|
||||
|
||||
file_streambuf(const std::string& filename)
|
||||
: file_(file_open(filename))
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual
|
||||
int_type
|
||||
underflow()
|
||||
{
|
||||
if (gptr() == egptr() && file_)
|
||||
{
|
||||
const size_t size = ::fread(buffer_, 1, buffer_size_, file_);
|
||||
setg(buffer_, buffer_, buffer_ + size);
|
||||
}
|
||||
return (gptr() == egptr())
|
||||
? traits_type::eof()
|
||||
: traits_type::to_int_type(*gptr());
|
||||
}
|
||||
|
||||
private:
|
||||
FILE*
|
||||
file_open(const std::string& filename)
|
||||
{
|
||||
# ifdef _WIN32
|
||||
std::wstring wfilename = convert_utf8_to_utf16(filename);
|
||||
FILE* file = ::_wfopen(wfilename.c_str(), L"rb");
|
||||
# else // !_WIN32
|
||||
FILE* file = ::fopen(filename.c_str(), "rb");
|
||||
# endif // _WIN32
|
||||
if (file == NULL)
|
||||
{
|
||||
std::string msg = "Error opening file \"";
|
||||
msg += filename;
|
||||
msg += "\".";
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // !USE_OS_TZDB
|
||||
|
||||
namespace date
|
||||
@@ -559,15 +671,8 @@ load_timezone_mappings_from_xml_file(const std::string& input_path)
|
||||
std::vector<detail::timezone_mapping> mappings;
|
||||
std::string line;
|
||||
|
||||
std::ifstream is(input_path);
|
||||
if (!is.is_open())
|
||||
{
|
||||
// We don't emit file exceptions because that's an implementation detail.
|
||||
std::string msg = "Error opening time zone mapping file \"";
|
||||
msg += input_path;
|
||||
msg += "\".";
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
file_streambuf ibuf(input_path);
|
||||
std::istream is(&ibuf);
|
||||
|
||||
auto error = [&input_path, &line_num](const char* info)
|
||||
{
|
||||
@@ -697,7 +802,6 @@ load_timezone_mappings_from_xml_file(const std::string& input_path)
|
||||
}
|
||||
}
|
||||
|
||||
is.close();
|
||||
return mappings;
|
||||
}
|
||||
|
||||
@@ -2836,7 +2940,8 @@ bool
|
||||
file_exists(const std::string& filename)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return ::_access(filename.c_str(), 0) == 0;
|
||||
std::wstring wfilename = convert_utf8_to_utf16(filename);
|
||||
return ::_waccess(wfilename.c_str(), 0) == 0;
|
||||
#else
|
||||
return ::access(filename.c_str(), F_OK) == 0;
|
||||
#endif
|
||||
@@ -3413,16 +3518,27 @@ std::string
|
||||
get_version(const std::string& path)
|
||||
{
|
||||
std::string version;
|
||||
std::ifstream infile(path + "version");
|
||||
if (infile.is_open())
|
||||
|
||||
std::string path_version = path + "version";
|
||||
|
||||
if (file_exists(path_version))
|
||||
{
|
||||
file_streambuf inbuf(path_version);
|
||||
std::istream infile(&inbuf);
|
||||
|
||||
infile >> version;
|
||||
|
||||
if (!infile.fail())
|
||||
return version;
|
||||
}
|
||||
else
|
||||
|
||||
std::string path_news = path + "NEWS";
|
||||
|
||||
if (file_exists(path_news))
|
||||
{
|
||||
infile.open(path + "NEWS");
|
||||
file_streambuf inbuf(path_news);
|
||||
std::istream infile(&inbuf);
|
||||
|
||||
while (infile)
|
||||
{
|
||||
infile >> version;
|
||||
@@ -3433,6 +3549,7 @@ get_version(const std::string& path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error("Unable to get Timezone database version from " + path);
|
||||
}
|
||||
|
||||
@@ -3504,7 +3621,13 @@ init_tzdb()
|
||||
|
||||
for (const auto& filename : files)
|
||||
{
|
||||
std::ifstream infile(path + filename);
|
||||
std::string file_path = path + filename;
|
||||
if (!file_exists(file_path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
file_streambuf inbuf(file_path);
|
||||
std::istream infile(&inbuf);
|
||||
while (infile)
|
||||
{
|
||||
std::getline(infile, line);
|
||||
|
Reference in New Issue
Block a user