fix(elevator): make use of wide characters everywhere (#1559)

Switching the Windows language to a language encoded using UTF-16 (like
Japanese) revealed that our native elevator module garbles language
specific strings, making the child writer process unable to find an
image inside a language specific directory, and thus resulting in a
"File is not accessible" error.

As a solution, we make use of `std::wstring` and wide character Windows
API functions in the elevator module.

Change-Type: patch
Changelog-Entry: Fix "file is not accessible" error when flashing an image that lives inside a directory whose name is UTF-16 encoded on Windows.
Fixes: https://github.com/resin-io/etcher/issues/1459
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
This commit is contained in:
Juan Cruz Viotti 2017-07-05 14:13:36 -04:00 committed by GitHub
parent 80b588683e
commit fd109b5770
5 changed files with 50 additions and 43 deletions

View File

@ -29,13 +29,13 @@ NAN_METHOD(Elevate) {
return Nan::ThrowError("Callback must be a function");
}
std::vector<std::string> arguments =
std::vector<std::wstring> arguments =
etcher::v8utils::GetArguments(info[0].As<v8::Array>());
v8::Local<v8::Function> callback = info[1].As<v8::Function>();
etcher::ELEVATE_RESULT result = etcher::Elevate(
arguments.front(),
std::vector<std::string>(arguments.begin() + 1, arguments.end()));
std::vector<std::wstring>(arguments.begin() + 1, arguments.end()));
// Create results object
v8::Isolate *isolate = v8::Isolate::GetCurrent();
@ -51,8 +51,7 @@ NAN_METHOD(Elevate) {
YIELD_OBJECT(callback, results);
break;
default:
std::string details = etcher::ElevateResultToString(result);
YIELD_ERROR(callback, details.c_str());
YIELD_ERROR(callback, etcher::ElevateResultToString(result));
}
}

View File

@ -53,10 +53,10 @@ enum class ELEVATE_RESULT {
ELEVATE_UNKNOWN_ERROR
};
ELEVATE_RESULT Elevate(const std::string &command,
std::vector<std::string> arguments);
ELEVATE_RESULT Elevate(const std::wstring &command,
std::vector<std::wstring> arguments);
std::string ElevateResultToString(const ELEVATE_RESULT &result);
std::wstring ElevateResultToString(const ELEVATE_RESULT &result);
} // namespace etcher

View File

@ -16,11 +16,11 @@
#include "os/elevate.h"
static std::string JoinArguments(std::vector<std::string> arguments) {
std::ostringstream result;
static std::wstring JoinArguments(std::vector<std::wstring> arguments) {
std::wostringstream result;
std::copy(arguments.begin(), arguments.end(),
std::ostream_iterator<std::string>(result, " "));
std::ostream_iterator<std::wstring, wchar_t>(result, L" "));
return result.str();
}
@ -28,22 +28,22 @@ static std::string JoinArguments(std::vector<std::string> arguments) {
// Make sure to delete the result after you're done
// with it by calling `delete[] result;`.
// See http://stackoverflow.com/a/1201471
static LPCTSTR ConvertStringToLPCTSTR(const std::string &string) {
char *result = new char[string.size() + 1];
static LPCWSTR ConvertStringToLPCWSTR(const std::wstring &string) {
wchar_t *result = new wchar_t[string.size() + 1];
std::copy(string.begin(), string.end(), result);
result[string.size()] = 0;
return result;
}
etcher::ELEVATE_RESULT etcher::Elevate(const std::string &command,
std::vector<std::string> arguments) {
etcher::ELEVATE_RESULT etcher::Elevate(const std::wstring &command,
std::vector<std::wstring> arguments) {
// Initialize the SHELLEXECUTEINFO structure. We zero it out
// in order to be on the safe side, and set cbSize to the size
// of the structure as recommend by MSDN
// See: https://msdn.microsoft.com/en-us/library/windows/desktop/bb759784(v=vs.85).aspx
SHELLEXECUTEINFO shellExecuteInfo;
SHELLEXECUTEINFOW shellExecuteInfo;
ZeroMemory(&shellExecuteInfo, sizeof(shellExecuteInfo));
shellExecuteInfo.cbSize = sizeof(SHELLEXECUTEINFO);
shellExecuteInfo.cbSize = sizeof(SHELLEXECUTEINFOW);
// Flags that indicate the content and validity of the other structure member.
shellExecuteInfo.fMask =
@ -60,7 +60,7 @@ etcher::ELEVATE_RESULT etcher::Elevate(const std::string &command,
SEE_MASK_FLAG_NO_UI;
// The action to be performed.
shellExecuteInfo.lpVerb = TEXT("runas");
shellExecuteInfo.lpVerb = L"runas";
// Run the file in the background
shellExecuteInfo.nShow = SW_HIDE;
@ -70,14 +70,14 @@ etcher::ELEVATE_RESULT etcher::Elevate(const std::string &command,
// Set file and parameters
// We can't just assign the result of `.c_str()`, since
// that pointer is owned by the `std::string` instance,
// that pointer is owned by the `std::wstring` instance,
// and will not be safe after the instance is destroyed.
LPCTSTR file = ConvertStringToLPCTSTR(command);
LPCTSTR argv = ConvertStringToLPCTSTR(JoinArguments(arguments));
LPCWSTR file = ConvertStringToLPCWSTR(command);
LPCWSTR argv = ConvertStringToLPCWSTR(JoinArguments(arguments));
shellExecuteInfo.lpFile = file;
shellExecuteInfo.lpParameters = argv;
BOOL executeResult = ShellExecuteEx(&shellExecuteInfo);
BOOL executeResult = ShellExecuteExW(&shellExecuteInfo);
delete[] file;
delete[] argv;
@ -127,32 +127,32 @@ etcher::ELEVATE_RESULT etcher::Elevate(const std::string &command,
return etcher::ELEVATE_RESULT::ELEVATE_SUCCESS;
}
std::string
std::wstring
etcher::ElevateResultToString(const etcher::ELEVATE_RESULT &result) {
switch (result) {
case etcher::ELEVATE_RESULT::ELEVATE_SUCCESS:
return "Success";
return L"Success";
case etcher::ELEVATE_RESULT::ELEVATE_CANCELLED:
return "The user cancelled the elevation request";
return L"The user cancelled the elevation request";
case etcher::ELEVATE_RESULT::ELEVATE_FILE_NOT_FOUND:
return "The specified file was not found";
return L"The specified file was not found";
case etcher::ELEVATE_RESULT::ELEVATE_PATH_NOT_FOUND:
return "The specified path was not found";
return L"The specified path was not found";
case etcher::ELEVATE_RESULT::ELEVATE_DDE_FAIL:
return "The Dynamic Data Exchange (DDE) transaction failed";
return L"The Dynamic Data Exchange (DDE) transaction failed";
case etcher::ELEVATE_RESULT::ELEVATE_NO_ASSOCIATION:
return "There is no application associated with the "
"specified file name extension";
return L"There is no application associated with the "
"specified file name extension";
case etcher::ELEVATE_RESULT::ELEVATE_ACCESS_DENIED:
return "Access to the specified file is denied";
return L"Access to the specified file is denied";
case etcher::ELEVATE_RESULT::ELEVATE_DLL_NOT_FOUND:
return "One of the library files necessary to run the "
"application can't be found";
return L"One of the library files necessary to run the "
"application can't be found";
case etcher::ELEVATE_RESULT::ELEVATE_NOT_ENOUGH_MEMORY:
return "There is not enough memory to perform the specified action";
return L"There is not enough memory to perform the specified action";
case etcher::ELEVATE_RESULT::ELEVATE_SHARING_VIOLATION:
return "A sharing violation occurred";
return L"A sharing violation occurred";
default:
return "Unknown error";
return L"Unknown error";
}
}

View File

@ -16,14 +16,17 @@
#include "utils/v8utils.h"
std::vector<std::string>
std::vector<std::wstring>
etcher::v8utils::GetArguments(v8::Local<v8::Array> arguments) {
std::vector<std::string> result(0);
std::vector<std::wstring> result(0);
for (uint32_t index = 0; index < arguments->Length(); index++) {
std::string argument(
// See https://stackoverflow.com/q/15615136/1641422
std::string stringArgument(
*v8::String::Utf8Value(arguments->Get(index)->ToString()));
result.push_back(argument);
std::wstring_convert<std::codecvt_utf8<wchar_t>> conversion;
result.push_back(conversion.from_bytes(stringArgument));
}
return result;

View File

@ -20,18 +20,23 @@
#include <nan.h>
#include <string>
#include <vector>
#include <codecvt>
namespace etcher {
namespace v8utils {
std::vector<std::string> GetArguments(v8::Local<v8::Array> arguments);
std::vector<std::wstring> GetArguments(v8::Local<v8::Array> arguments);
} // namespace v8utils
} // namespace etcher
#define YIELD_ERROR(CALLBACK, ERROR) \
{ \
v8::Local<v8::Value> argv[1] = {Nan::Error((ERROR))}; \
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (CALLBACK), 1, \
argv); \
const wchar_t *message = (ERROR).c_str(); \
v8::Local<v8::Value> argv[1] = { \
Nan::Error(v8::String::NewFromTwoByte(isolate, \
(const uint16_t *)message)) \
}; \
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (CALLBACK), \
1, argv); \
} \
return;