diff --git a/lib/error.js b/lib/error.js index e56e6cc..11b1216 100644 --- a/lib/error.js +++ b/lib/error.js @@ -1,5 +1,6 @@ module.exports = { 0x2: 'ERROR_FILE_NOT_FOUND', 0x3: 'ERROR_PATH_NOT_FOUND', + 0x5: 'ERROR_ACCESS_DENIED', 0x6: 'ERROR_INVALID_HANDLE' }; diff --git a/lib/native/adv_api.js b/lib/native/adv_api.js index af76b82..1be58f3 100644 --- a/lib/native/adv_api.js +++ b/lib/native/adv_api.js @@ -20,10 +20,10 @@ var advApi = ffi.Library('Advapi32', { _Reserved_ LPDWORD lpReserved, _Out_opt_ LPDWORD lpType, _Out_opt_ LPBYTE lpData, - _Inout_opt_ LPDWORD lpcbDataRegOpenKeyExA + _Inout_opt_ LPDWORD lpcbData ); */ - RegQueryValueExA: ['long', [types.HKEY, 'string', 'pointer', types.LPDWORD, types.LPBYTE, types.LPDWORD]], + RegQueryValueExW: ['long', [types.HKEY, types.LPCWSTR, 'pointer', types.LPDWORD, types.LPBYTE, types.LPDWORD]], /* LONG WINAPI RegOpenKeyEx( _In_ HKEY hKey, @@ -33,7 +33,7 @@ var advApi = ffi.Library('Advapi32', { _Out_ PHKEY phkResult ); */ - RegOpenKeyExA: ['long', ['longlong', 'string', types.DWORD, types.REGSAM, types.PHKEY]], + RegOpenKeyExW: ['long', ['longlong', types.LPCWSTR, types.DWORD, types.REGSAM, types.PHKEY]], /* LONG WINAPI RegSetValueEx( _In_ HKEY hKey, @@ -44,7 +44,7 @@ var advApi = ffi.Library('Advapi32', { _In_ DWORD cbData ); */ - RegSetValueExA: ['long', [types.HKEY, 'string', 'pointer', types.DWORD, types.LPBYTE, types.DWORD]], + RegSetValueExW: ['long', [types.HKEY, types.LPCWSTR, 'pointer', types.DWORD, types.LPBYTE, types.DWORD]], /** * LONG WINAPI RegCreateKeyEx( _In_ HKEY hKey, @@ -58,14 +58,14 @@ var advApi = ffi.Library('Advapi32', { _Out_opt_ LPDWORD lpdwDisposition ); */ - RegCreateKeyExA: ['long', [types.HKEY, 'string', 'pointer', 'pointer', types.DWORD, types.REGSAM, 'pointer', types.PHKEY, 'pointer']], + RegCreateKeyExW: ['long', [types.HKEY, types.LPCWSTR, 'pointer', 'pointer', types.DWORD, types.REGSAM, 'pointer', types.PHKEY, 'pointer']], /* LONG WINAPI RegDeleteTree( _In_ HKEY hKey, _In_opt_ LPCTSTR lpSubKey ); */ - RegDeleteTreeA: ['long', [types.HKEY, 'string']], + RegDeleteTreeW: ['long', [types.HKEY, types.LPCWSTR]], /* LONG WINAPI RegDeleteValue( _In_ HKEY hKey, @@ -73,7 +73,7 @@ var advApi = ffi.Library('Advapi32', { ); */ - RegDeleteValueA: ['long', [types.HKEY, 'string']], + RegDeleteValueW: ['long', [types.HKEY, types.LPCWSTR]], /* LONG WINAPI RegCloseKey( _In_ HKEY hKey diff --git a/lib/native/shell32.js b/lib/native/shell32.js index 5e8f9d6..46d9782 100644 --- a/lib/native/shell32.js +++ b/lib/native/shell32.js @@ -10,7 +10,7 @@ var shell32 = new ffi.Library('Shell32', { _Inout_ SHELLEXECUTEINFO *pExecInfo ); */ - ShellExecuteExA: ['bool', [SHELLEXECUTEINFOPtr]] + ShellExecuteExW: ['bool', [SHELLEXECUTEINFOPtr]] }); module.exports = shell32; diff --git a/lib/ref-LPWSTR.js b/lib/ref-LPWSTR.js new file mode 100644 index 0000000..aaba74e --- /dev/null +++ b/lib/ref-LPWSTR.js @@ -0,0 +1,57 @@ +'use strict'; +var ref = require('ref'); + +/** + * This is a simple implementation of a zero-terminated UTF-16 string (Windows LPWSTR) for ref. + * + * There is an alternative implementation in the ref-wchar package, ref-wchar.string, but that uses + * iconv for translation between Node's native UTF-8 and UTF-16, which is a large dependency, and does not + * provide a UTF-16-from-string method that we need here either. This version uses Buffer's built-in utf16le + * support. + */ +var LPWSTR = Object.create(ref.types.CString); +LPWSTR.name = 'LPWSTR'; + +var encoding = 'utf16le'; + +function isNotNullOrUndefined(input) { + return (input !== null) && (typeof (input) !== 'undefined'); +} + +LPWSTR.toString = function toString(buffer) { + if (isNotNullOrUndefined(buffer)) { + // Strip trailing nul if present + var length = buffer.length; + if ((length > 2) && (buffer.readInt16LE(length - 2) === 0)) { + length -= 2; + } + return buffer.toString(encoding, 0, length); + } + + // Pass null or undefined input through unchanged. + return buffer; +}; + +LPWSTR.fromString = function fromString(input) { + if (isNotNullOrUndefined(input)) { + // Use 'new Buffer' not 'Buffer.from' for Node v4.1 compatibility + return new Buffer(input + '\0', encoding); + } + return null; +}; + +LPWSTR.get = function get(buf, offset) { + var _buf = buf.readPointer(offset); + if (_buf.isNull()) { + return null; + } + var stringBuf = _buf.reinterpretUntilZeros(2); + return LPWSTR.toString(stringBuf); +}; + +LPWSTR.set = function set(buf, offset, val) { + var _buf = Buffer.isBuffer(val) ? val : LPWSTR.fromString(val); + return buf.writePointer(_buf, offset); +}; + +module.exports = LPWSTR; diff --git a/lib/registry.js b/lib/registry.js index 1cc4194..80ae7f0 100644 --- a/lib/registry.js +++ b/lib/registry.js @@ -17,7 +17,7 @@ var api = { var pHkey = ref.alloc(types.PHKEY, new Buffer(ref.sizeof.pointer)); debug('PHKEY LENGTH: ' + pHkey.deref().length); - var result = advApi.RegOpenKeyExA(preDefinedKey, subKeyName, 0, accessLevel, pHkey); + var result = advApi.RegOpenKeyExW(preDefinedKey, subKeyName, 0, accessLevel, pHkey); debug('result:' + result); if (result !== 0) { throw 'Failed to open key error: ' + error[result]; @@ -30,9 +30,9 @@ var api = { // RegOpenKeyEx can also take an HKEY in addition to a predefined value var advApi2 = ffi.Library('Advapi32', { - RegOpenKeyExA: ['long', [types.HKEY, 'string', types.DWORD, types.REGSAM, types.PHKEY]] + RegOpenKeyExW: ['long', [types.HKEY, types.LPCWSTR, types.DWORD, types.REGSAM, types.PHKEY]] }); - var result = advApi2.RegOpenKeyExA(keyObject.handle.deref(), subKeyName, 0, accessLevel, pHkey); + var result = advApi2.RegOpenKeyExW(keyObject.handle.deref(), subKeyName, 0, accessLevel, pHkey); if (result !== 0) { throw 'Failed to open key error: ' + error[result]; @@ -44,7 +44,7 @@ var api = { var pKeyDataLength = ref.alloc(types.LPDWORD, new Buffer(ref.sizeof.pointer)), pKeyType = ref.alloc(types.LPDWORD, new Buffer(ref.sizeof.pointer)); // QUERY FOR VALUE SIZE & TYPE - var result = advApi.RegQueryValueExA(key.handle.deref(), valueName, null, pKeyType, null, pKeyDataLength); + var result = advApi.RegQueryValueExW(key.handle.deref(), valueName, null, pKeyType, null, pKeyDataLength); // READ VALUE var value = new Buffer(pKeyDataLength.readUInt32LE()), valueType = pKeyType.readUInt32LE(); @@ -52,7 +52,7 @@ var api = { case windef.REG_VALUE_TYPE.REG_SZ: case windef.REG_VALUE_TYPE.REG_EXPAND_SZ: case windef.REG_VALUE_TYPE.REG_LINK: - value.type = types.LPCTSR; + value.type = types.LPCWSTR; break; case windef.REG_VALUE_TYPE.REG_BINARY: value.type = types.PVOID; @@ -67,16 +67,14 @@ var api = { } // READ VALUE - result = advApi.RegQueryValueExA(key.handle.deref(), valueName, null, pKeyType, value, pKeyDataLength); + result = advApi.RegQueryValueExW(key.handle.deref(), valueName, null, pKeyType, value, pKeyDataLength); if (result !== 0) { throw 'Failed to open key error: ' + error[result]; } - if (value.type === types.LPTSR) { - // TODO not sure why buffer's utf8 parsing leaves in the unicode null - // escape sequence. This is a work-around (at least on node 4.1) - value = value.toString().replace('\u0000', ''); + if (value.type === types.LPCWSTR) { + value = types.LPCWSTR.toString(value); } return value; @@ -93,24 +91,21 @@ var api = { case windef.REG_VALUE_TYPE.REG_SZ: case windef.REG_VALUE_TYPE.REG_EXPAND_SZ: case windef.REG_VALUE_TYPE.REG_LINK: - buffer = new Buffer(value, 'utf8'); + buffer = types.LPCWSTR.fromString(value); byte = ref.alloc(types.LPBYTE, buffer); - debug('content length:' + Buffer.byteLength(value, 'utf8')); - debug(value); - debug(buffer.length); - result = advApi.RegSetValueExA(key.handle.deref(), valueName, null, valueType, byte.deref(), Buffer.byteLength(value, 'utf8')); + result = advApi.RegSetValueExW(key.handle.deref(), valueName, null, valueType, byte.deref(), buffer.length); break; case windef.REG_VALUE_TYPE.REG_BINARY: // we assume that the value is a buffer since it should be binary data buffer = value; byte = ref.alloc(types.LPBYTE, buffer); - result = advApi.RegSetValueExA(key.handle.deref(), valueName, null, valueType, byte.deref(), buffer.length); + result = advApi.RegSetValueExW(key.handle.deref(), valueName, null, valueType, byte.deref(), buffer.length); break; case windef.REG_VALUE_TYPE.REG_DWORD: case windef.REG_VALUE_TYPE.REG_DWORD_BIG_ENDIAN: case windef.REG_VALUE_TYPE.REG_DWORD_LITTLE_ENDIAN: buffer = new Buffer(4, value); - result = advApi.RegSetValueExA(key.handle.deref(), valueName, null, valueType, byte.deref(), buffer.length); + result = advApi.RegSetValueExW(key.handle.deref(), valueName, null, valueType, byte.deref(), buffer.length); break; default: throw 'The type ' + valueType + ' is currently unsupported'; @@ -123,21 +118,21 @@ var api = { createKey: function (key, subKeyName, accessLevel) { var pHkey = ref.alloc(types.PHKEY, new Buffer(ref.sizeof.pointer)); - var result = advApi.RegCreateKeyExA(key.handle.deref(), subKeyName, null, null, windef.REG_OPTION_NON_VOLATILE, accessLevel, null, pHkey, null); + var result = advApi.RegCreateKeyExW(key.handle.deref(), subKeyName, null, null, windef.REG_OPTION_NON_VOLATILE, accessLevel, null, pHkey, null); if (result !== 0) { throw 'Failed to open key error: ' + error[result]; } }, deleteKey: function (key, subKeyName) { - var result = advApi.RegDeleteTreeA(key.handle.deref(), subKeyName); + var result = advApi.RegDeleteTreeW(key.handle.deref(), subKeyName); if (result !== 0) { - throw 'Failed to open key error ' + result + ':' + error[result]; + throw 'Failed to delete key error ' + result + ':' + error[result]; } }, deleteValue: function (key, value) { - var result = advApi.RegDeleteValueA(key.handle.deref(), value); + var result = advApi.RegDeleteValueW(key.handle.deref(), value); if (result !== 0) { throw 'Failed to delete value error ' + result + ':' + error[result]; diff --git a/lib/types.js b/lib/types.js index 8c1914f..f860bad 100644 --- a/lib/types.js +++ b/lib/types.js @@ -1,4 +1,6 @@ -var ref = require('ref'); +'use strict'; +var ref = require('ref'), + LPWSTR = require('./ref-LPWSTR.js'); var types = { REGSAM: ref.types.ulong, @@ -10,7 +12,8 @@ var types = { PVOID: ref.refType('pointer'), HANDLE: ref.refType(ref.types.void), HINSTANCE: ref.refType(ref.types.void), - LPCTSTR: ref.refType(ref.types.CString), + LPCSTR: ref.refType(ref.types.CString), + LPCWSTR: LPWSTR, STRING: ref.types.CString, INT: ref.types.int, LPVOID: ref.refType(ref.types.void) diff --git a/lib/utils.js b/lib/utils.js index 0ace701..d1bf5c4 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -40,7 +40,7 @@ module.exports = { hProcess: ref.alloc(types.HANDLE) }); - shell32.ShellExecuteExA.async(shellexecuteinfoval.ref(), callback); + shell32.ShellExecuteExW.async(shellexecuteinfoval.ref(), callback); }, associateExeForFile: function (handlerName, handlerDescription, iconPath, exePath, extensionName) { var key = registry.openKeyFromPredefined(windef.HKEY.HKEY_CLASSES_ROOT, '', windef.KEY_ACCESS.KEY_ALL_ACCESS); diff --git a/lib/windef.js b/lib/windef.js index 35b907c..0288caf 100644 --- a/lib/windef.js +++ b/lib/windef.js @@ -71,14 +71,14 @@ module.exports = { cbSize: types.DWORD, fMask: types.ULONG, hwnd: types.HWND, - lpVerb: types.STRING, - lpFile: types.STRING, - lpParameters: types.STRING, - lpDirectory: types.STRING, + lpVerb: types.LPCWSTR, + lpFile: types.LPCWSTR, + lpParameters: types.LPCWSTR, + lpDirectory: types.LPCWSTR, nShow: types.INT, hInstApp: types.HINSTANCE, lpIDList: types.LPVOID, - lpClass: types.STRING, + lpClass: types.LPCWSTR, hkeyClass: types.HKEY, dwHotKey: types.DWORD, DUMMYUNIONNAME: DUMMYUNIONNAME, diff --git a/test/mock/adv_api.js b/test/mock/adv_api.js index f55dea9..9c41c1f 100644 --- a/test/mock/adv_api.js +++ b/test/mock/adv_api.js @@ -45,8 +45,8 @@ var advApi = { _Inout_opt_ LPDWORD lpcbData ); */ - RegQueryValueExA: function (hKey, valueName, shouldBeNull, lpType, lpData, lpcbData) { - debug('RegQueryValueExA'); + RegQueryValueExW: function (hKey, valueName, shouldBeNull, lpType, lpData, lpcbData) { + debug('RegQueryValueExW'); if (lpData === null) { debug(keys[hKey.address()].values.test_value_name); lpType.writeUInt32LE(windef.REG_VALUE_TYPE.REG_SZ, 0); @@ -54,7 +54,7 @@ var advApi = { return 0; } - lpData.write(keys[hKey.address()].values[valueName].value, 'utf8'); + keys[hKey.address()].values[valueName].valueBuffer.copy(lpData); lpType.writeUInt16LE(windef.REG_VALUE_TYPE.REG_SZ); return 0; }, @@ -67,9 +67,9 @@ var advApi = { _Out_ PHKEY phkResult ); */ - RegOpenKeyExA: function (hKey, subKeyName, shouldBeZero, accessLevel, pHkey) { + RegOpenKeyExW: function (hKey, subKeyName, shouldBeZero, accessLevel, pHkey) { var accessLevelFound = findValueInHash(accessLevel, windef.KEY_ACCESS); - debug('Mock: RegOpenKeyExA subkey: ' + subKeyName); + debug('Mock: RegOpenKeyExW subkey: ' + subKeyName); if (hKey.address) { debug('Mock: hKey address:' + hKey.address()); } @@ -123,8 +123,8 @@ var advApi = { _In_ DWORD cbData ); */ - RegSetValueExA: function (hKey, valueName, shouldBeNull, valueType, valueBuffer, bufferLength) { - debug('Mock: RegSetValueExA'); + RegSetValueExW: function (hKey, valueName, shouldBeNull, valueType, valueBuffer, bufferLength) { + debug('Mock: RegSetValueExW'); // predefined key if (typeof hKey === 'number') { assert(findValueInHash(hKey, windef.HKEY), 'Mock: Invalid predefined key specified'); @@ -136,9 +136,16 @@ var advApi = { assert(valueBuffer.constructor === Buffer); assert(typeof bufferLength === 'number'); + // Use the passed length, not the length of valueBuffer + var value = ref.reinterpret(valueBuffer, bufferLength, 0); + + // Copy the value to a new buffer and store that + var valueCopy = new Buffer(bufferLength); + value.copy(valueCopy, 0, 0, bufferLength); + keys[hKey.address()].values[valueName] = { valueType: valueType, - value: ref.readCString(valueBuffer), + valueBuffer: valueCopy, length: bufferLength }; return 0; @@ -156,8 +163,8 @@ var advApi = { _Out_opt_ LPDWORD lpdwDisposition ); */ - RegCreateKeyExA: function (hKey, subKeyName, shouldBeNull, shouldBeNull2, securityAttributes, accessLevel, shouldBeNull3, pHkey, shouldBeNull4) { - debug('Mock: RegCreateKeyExA'); + RegCreateKeyExW: function (hKey, subKeyName, shouldBeNull, shouldBeNull2, securityAttributes, accessLevel, shouldBeNull3, pHkey, shouldBeNull4) { + debug('Mock: RegCreateKeyExW'); assert(hKey.constructor === Buffer); assert(typeof subKeyName === 'string'); assert(shouldBeNull === null); @@ -192,7 +199,7 @@ var advApi = { _In_opt_ LPCTSTR lpSubKey ); */ - RegDeleteTreeA: function (hKey, subKeyName) { + RegDeleteTreeW: function (hKey, subKeyName) { if (typeof hKey === 'number') { assert(findValueInHash(hKey, windef.HKEY), 'Mock: Invalid predefined key specified'); } else { diff --git a/test/mock/ffi.js b/test/mock/ffi.js index 292c568..7f61af1 100644 --- a/test/mock/ffi.js +++ b/test/mock/ffi.js @@ -8,19 +8,19 @@ module.exports = { var lib; switch (libFile) { case 'Advapi32': - assert(funcs.RegOpenKeyExA.constructor === Array); - if(funcs.RegOpenKeyExA[1][0].indirection === types.HKEY.indirection && - funcs.RegOpenKeyExA[1][0].name === types.HKEY.name) { + assert(funcs.RegOpenKeyExW.constructor === Array); + if(funcs.RegOpenKeyExW[1][0].indirection === types.HKEY.indirection && + funcs.RegOpenKeyExW[1][0].name === types.HKEY.name) { // this is redefition for the library only specifying // a different key type lib = advApi; break; } - assert(funcs.RegQueryValueExA.constructor === Array); - assert(funcs.RegCreateKeyExA.constructor === Array); - assert(funcs.RegDeleteTreeA.constructor === Array); + assert(funcs.RegQueryValueExW.constructor === Array); + assert(funcs.RegCreateKeyExW.constructor === Array); + assert(funcs.RegDeleteTreeW.constructor === Array); assert(funcs.RegCloseKey.constructor === Array); - assert(funcs.RegSetValueExA.constructor === Array); + assert(funcs.RegSetValueExW.constructor === Array); assert(typeof funcs === 'object'); lib = advApi; break; diff --git a/test/mock/shell32.js b/test/mock/shell32.js index dd502ef..7621775 100644 --- a/test/mock/shell32.js +++ b/test/mock/shell32.js @@ -34,10 +34,10 @@ var SHELLEXECUTEINFO = struct({ }); var shell32Mock = { - ShellExecuteExA: function () { + ShellExecuteExW: function () { } }; -shell32Mock.ShellExecuteExA.async = function (type, cb) { +shell32Mock.ShellExecuteExW.async = function (type, cb) { debug('async'); debug(type.deref().lpFile); assert.deepEqual(type.type.fields.cbSize, SHELLEXECUTEINFO.fields.cbSize);