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

View File

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

View File

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

View File

@ -16,14 +16,17 @@
#include "utils/v8utils.h" #include "utils/v8utils.h"
std::vector<std::string> std::vector<std::wstring>
etcher::v8utils::GetArguments(v8::Local<v8::Array> arguments) { 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++) { 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())); *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; return result;

View File

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