#include "priv.h" // Build this file only on Win9X or NT4 #ifdef DOWNLEVEL_PLATFORM __inline LPWSTR StrCpyNXW(LPWSTR psz1, LPCWSTR psz2, int cchMax) { ASSERT(psz1); ASSERT(psz2); if (0 < cchMax) { // Leave room for the null terminator while (0 < --cchMax) { if ( !(*psz1++ = *psz2++) ) { --psz1; break; } } if (0 == cchMax) *psz1 = L'\0'; ASSERT(*psz1 == 0); } return psz1; } LPWSTR StrCpyNW(LPWSTR psz1, LPCWSTR psz2, int cchMax) { StrCpyNXW(psz1, psz2, cchMax); return psz1; } LPWSTR StrCpyW(LPWSTR psz1, LPCWSTR psz2) { LPWSTR psz = psz1; ASSERT(psz1); ASSERT(psz2); while (*psz1++ = *psz2++) ; return psz; } STDAPI_(DWORD) RegData_AtoW( IN LPCVOID pvData, IN DWORD dwSize, IN DWORD dwType, /*INOUT*/ LPDWORD pcbData ) { DWORD dwRet = NO_ERROR; if (REG_SZ == dwType || REG_EXPAND_SZ == dwType || REG_MULTI_SZ == dwType) { DWORD cbData = pcbData ? *pcbData : -1; if (pvData) { // Allocate a temporary buffer to work with int cch = MultiByteToWideChar(CP_ACP, 0, (LPSTR)pvData, cbData, NULL, 0); LPWSTR pwszT = (LPWSTR)LocalAlloc(LPTR, CbFromCchW(cch)); if (!pwszT) dwRet = ERROR_NOT_ENOUGH_MEMORY; else { if (CbFromCchW(cch) > dwSize) dwRet = ERROR_MORE_DATA; else { // Convert it to the temporary buffer MultiByteToWideChar(CP_ACP, 0, (LPSTR)pvData, cbData, pwszT, cch); // Copy it back to the output buffer StrCpyW((LPWSTR)pvData, pwszT); } LocalFree(pwszT); } // If string data, make room for unicode if (pcbData) { (*pcbData) = cch * sizeof(WCHAR); } } else if (pcbData) { // We don't have the data so guess (actual value may be less) // PERF: Does this need to be exact? For now this seem sufficient. (Stevepro) (*pcbData) *= sizeof(WCHAR); } } return dwRet; } /*---------------------------------------------------------- Purpose: Recursively delete the key, including all child values and keys. Mimics what RegDeleteKey does in Win95. Returns: Cond: -- */ DWORD DeleteKeyRecursively( IN HKEY hkey, IN LPCSTR pszSubKey) { DWORD dwRet; HKEY hkSubKey; // Open the subkey so we can enumerate any children dwRet = RegOpenKeyExA(hkey, pszSubKey, 0, MAXIMUM_ALLOWED, &hkSubKey); if (ERROR_SUCCESS == dwRet) { DWORD dwIndex; CHAR szSubKeyName[MAX_PATH + 1]; DWORD cchSubKeyName = ARRAYSIZE(szSubKeyName); CHAR szClass[MAX_PATH]; DWORD cbClass = ARRAYSIZE(szClass); // I can't just call RegEnumKey with an ever-increasing index, because // I'm deleting the subkeys as I go, which alters the indices of the // remaining subkeys in an implementation-dependent way. In order to // be safe, I have to count backwards while deleting the subkeys. // Find out how many subkeys there are dwRet = RegQueryInfoKeyA(hkSubKey, szClass, &cbClass, NULL, &dwIndex, // The # of subkeys -- all we need NULL, NULL, NULL, NULL, NULL, NULL, NULL); if (NO_ERROR == dwRet) { // dwIndex is now the count of subkeys, but it needs to be // zero-based for RegEnumKey, so I'll pre-decrement, rather // than post-decrement. while (ERROR_SUCCESS == RegEnumKeyA(hkSubKey, --dwIndex, szSubKeyName, cchSubKeyName)) { DeleteKeyRecursively(hkSubKey, szSubKeyName); } } RegCloseKey(hkSubKey); dwRet = RegDeleteKeyA(hkey, pszSubKey); } return dwRet; } /*---------------------------------------------------------- Purpose: Recursively delete the key, including all child values and keys. Returns: Cond: -- */ STDAPI_(DWORD) SHDeleteKeyA( IN HKEY hkey, IN LPCSTR pszSubKey) { DWORD dwRet; if (g_bRunOnNT) { dwRet = DeleteKeyRecursively(hkey, pszSubKey); } else { // On Win95, RegDeleteKey does what we want dwRet = RegDeleteKeyA(hkey, pszSubKey); } return dwRet; } /*---------------------------------------------------------- Purpose: Recursively delete the key, including all child values and keys. Returns: Cond: -- */ STDAPI_(DWORD) SHDeleteKeyW( IN HKEY hkey, IN LPCWSTR pwszSubKey) { DWORD dwRet; CHAR sz[MAX_PATH]; WideCharToMultiByte(CP_ACP, 0, pwszSubKey, -1, sz, SIZECHARS(sz), NULL, NULL); if (g_bRunOnNT) { dwRet = DeleteKeyRecursively(hkey, sz); } else { // On Win95, RegDeleteKey does what we want dwRet = RegDeleteKeyA(hkey, sz); } return dwRet; } /*---------------------------------------------------------- Purpose: Behaves just like RegQueryValueEx, except if the data type is REG_EXPAND_SZ, then this goes ahead and expands out the string. *pdwType will always be massaged to REG_SZ if this happens. Returns: Cond: -- */ STDAPI_(DWORD) SHQueryValueExA( IN HKEY hkey, IN LPCSTR pszValue, IN LPDWORD lpReserved, OUT LPDWORD pdwType, OUT LPVOID pvData, IN OUT LPDWORD pcbData) { DWORD dwRet; DWORD cbSize; DWORD dwType; LPSTR lpsz; // Trying to get back data if (pcbData) cbSize = *pcbData; // Size of output buffer dwRet = RegQueryValueExA(hkey, pszValue, lpReserved, &dwType, pvData, &cbSize); // Normally, we'd be done with this. But do some extra work // if this is an expandable string (something that has system // variables in it), or if we need to pad the buffer. if (NO_ERROR == dwRet) { // Note: on Win95, RegSetValueEx will always write the // full string out, including the null terminator. On NT, // it won't unless the write length was specified. // Hence, we have the following check. // Pad the buffer, in case the string didn't have a null // terminator when it was stored? if (REG_SZ == dwType) { // Yes if (pvData && cbSize < *pcbData) { LPSTR lpszData = pvData; lpszData[cbSize] = '\0'; } } // Expand the string? else if (REG_EXPAND_SZ == dwType) { // Yes // Use a temporary buffer to expand if (pvData) { lpsz = (LPSTR)LocalAlloc(LPTR, *pcbData); if ( !lpsz ) return ERROR_OUTOFMEMORY; cbSize = ExpandEnvironmentStringsA(pvData, lpsz, *pcbData); // NT screws up the cbSize returned... if (cbSize > 0) cbSize = lstrlenA(lpsz) + 1; if (cbSize > 0 && cbSize <= *pcbData) lstrcpynA(pvData, lpsz, *pcbData); else dwRet = GetLastError(); } else { // // Find out the length of the expanded string // we have to call in and actually get the data to do this // CHAR szBuff[1]; lpsz = (LPSTR)LocalAlloc(LPTR, cbSize); if (!lpsz) return ERROR_OUTOFMEMORY; dwRet = RegQueryValueExA(hkey, pszValue, lpReserved, NULL, (LPBYTE)lpsz, &cbSize); if (NO_ERROR == dwRet) { // dummy buffer required... DWORD cbExpand = ExpandEnvironmentStringsA(lpsz, szBuff, ARRAYSIZE(szBuff)); cbSize = max(cbExpand, cbSize); } } LocalFree(lpsz); // Massage dwType so that callers always see REG_SZ dwType = REG_SZ; } } if (pdwType) *pdwType = dwType; if (pcbData) *pcbData = cbSize; return dwRet; } #ifdef UNICODE /*---------------------------------------------------------- Purpose: Behaves just like RegQueryValueEx, except if the data type is REG_EXPAND_SZ, then this goes ahead and expands out the string. *pdwType will always be massaged to REG_SZ if this happens. Returns: Cond: -- */ STDAPI_(DWORD) SHQueryValueExW( IN HKEY hkey, IN LPCWSTR pwszValue, IN LPDWORD lpReserved, OUT LPDWORD pdwType, OUT LPVOID pvData, IN OUT LPDWORD pcbData) { DWORD dwRet; DWORD cbSize; DWORD dwType; LPWSTR lpsz; if ( !g_bRunOnNT ) { CHAR szValue[MAX_PATH]; LPSTR pszValue = NULL; DWORD dwOriginalSize = 0; if (pcbData) dwOriginalSize = *pcbData; if (pwszValue) { WideCharToMultiByte(CP_ACP, 0, pwszValue, -1, szValue, SIZECHARS(szValue), NULL, NULL); pszValue = szValue; } if (!pdwType) pdwType = &dwType; dwRet = SHQueryValueExA(hkey, pszValue, lpReserved, pdwType, pvData, pcbData); if (NO_ERROR == dwRet) dwRet = RegData_AtoW(pvData, dwOriginalSize, *pdwType, pcbData); // Thunk data from ANSI->UNICODE if needed. } else { // Running on NT // Trying to get back data if (pcbData) cbSize = *pcbData; // Size of output buffer dwRet = RegQueryValueExW(hkey, pwszValue, lpReserved, &dwType, pvData, &cbSize); // Normally, we'd be done with this. But do some extra work // if this is an expandable string (something that has system // variables in it), or if we need to pad the buffer. if (NO_ERROR == dwRet) { // Note: on Win95, RegSetValueEx will always write the // full string out, including the null terminator. On NT, // it won't unless the write length was specified. // Hence, we have the following check. // Pad the buffer, in case the string didn't have a null // terminator when it was stored? if (REG_SZ == dwType) { // Yes if (pvData && cbSize + sizeof(WCHAR) <= *pcbData) { LPWSTR lpszData = pvData; lpszData[cbSize / sizeof(WCHAR)] = '\0'; } } // Expand the string? else if (REG_EXPAND_SZ == dwType) { if (pvData) { // Yes // Use a temporary buffer to expand lpsz = (LPWSTR)LocalAlloc(LPTR, *pcbData); if (!lpsz) return ERROR_OUTOFMEMORY; cbSize = CbFromCchW(ExpandEnvironmentStringsW(pvData, lpsz, *pcbData / sizeof(WCHAR))); if (cbSize > 0 && cbSize <= *pcbData) StrCpyNW(pvData, lpsz, *pcbData / sizeof(WCHAR)); else dwRet = GetLastError(); } else { // // Find out the length of the expanded string // we have to call in and actually get the data to do this // WCHAR szBuff[1]; // Find out the length of the expanded string // lpsz = (LPWSTR)LocalAlloc(LPTR, cbSize); if (!lpsz) return ERROR_OUTOFMEMORY; dwRet = RegQueryValueExW(hkey, pwszValue, lpReserved, NULL, (LPBYTE)lpsz, &cbSize); if (NO_ERROR == dwRet) { DWORD cbExpand = CbFromCchW(ExpandEnvironmentStringsW(lpsz, szBuff, ARRAYSIZE(szBuff))); cbSize = max(cbExpand, cbSize); } } LocalFree(lpsz); // Massage dwType so that callers always see REG_SZ dwType = REG_SZ; } } if (pdwType) *pdwType = dwType; if (pcbData) *pcbData = cbSize; } return dwRet; } #endif //UNICODE /* * @doc INTERNAL * * @func int | SHAnsiToUnicodeNativeCP | * * Convert an ANSI string to a UNICODE string via the * specified Windows code page. If the source string is too large * for the destination buffer, then as many characters as * possible are copied. * * The resulting output string is always null-terminated. * * @parm UINT | uiCP | * * The code page in which to perform the conversion. * This must be a Windows code page. * * @parm LPCSTR | pszSrc | * * Source buffer containing ANSI string to be converted. * * @parm int | cchSrc | * * Source buffer length, including terminating null. * * @parm LPWSTR | pwszDst | * * Destination buffer to receive converted UNICODE string. * * @parm int | cwchBuf | * * Size of the destination buffer in s. * * @returns * * On success, the number of characters copied to the output * buffer is returned, including the terminating null. */ int SHAnsiToUnicodeNativeCP(UINT uiCP, LPCSTR pszSrc, int cchSrc, LPWSTR pwszDst, int cwchBuf) { int cwchRc = 0; /* Assume failure */ /* * Checks the caller should've made. */ ASSERT(IS_VALID_STRING_PTRA(pszSrc, -1)); ASSERT(cchSrc == lstrlenA(pszSrc) + 1); ASSERT(IS_VALID_WRITE_BUFFER(pwszDst, WCHAR, cwchBuf)); ASSERT(pszSrc != NULL); ASSERT(uiCP != 1200 && uiCP != 65000 && uiCP != 50000 && uiCP != 65001); ASSERT(pwszDst); ASSERT(cwchBuf); cwchRc = MultiByteToWideChar(uiCP, 0, pszSrc, cchSrc, pwszDst, cwchBuf); if (cwchRc) { /* * The output buffer was big enough; no double-buffering * needed. */ } else if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { /* * The output buffer wasn't big enough. Need to double-buffer. */ int cwchNeeded = MultiByteToWideChar(uiCP, 0, pszSrc, cchSrc, NULL, 0); ASSERT(cwchRc == 0); /* In case we fail later */ if (cwchNeeded) { LPWSTR pwsz = (LPWSTR)LocalAlloc(LMEM_FIXED, cwchNeeded * SIZEOF(WCHAR)); if (pwsz) { cwchRc = MultiByteToWideChar(uiCP, 0, pszSrc, cchSrc, pwsz, cwchNeeded); if (cwchRc) { StrCpyNW(pwszDst, pwsz, cwchBuf); cwchRc = cwchBuf; } LocalFree(pwsz); } } } else { /* Possibly unsupported code page */ ASSERT(!"Unexpected error in MultiByteToWideChar"); } return cwchRc; } int SHAnsiToUnicodeCP_ACP(LPCSTR pszSrc, LPWSTR pwszDst, int cwchBuf) { int cwchRc = 0; /* Assume failure */ ASSERT(IS_VALID_STRING_PTRA(pszSrc, -1)); ASSERT(IS_VALID_WRITE_BUFFER(pwszDst, WCHAR, cwchBuf)); /* * Sanity check - NULL source string is treated as a null string. */ if (pszSrc == NULL) { pszSrc = ""; } /* * Sanity check - Output buffer must be non-NULL and must be of * nonzero size. */ if (pwszDst && cwchBuf) { int cchSrc; pwszDst[0] = 0; /* In case of error */ cchSrc = lstrlenA(pszSrc) + 1; cwchRc = SHAnsiToUnicodeNativeCP(CP_ACP, pszSrc, cchSrc, pwszDst, cwchBuf); } return cwchRc; } /* * @doc EXTERNAL * * @func int | SHAnsiToUnicode | * * Convert an ANSI string to a UNICODE string via the * code page. If the source string is too large * for the destination buffer, then as many characters as * possible are copied. * * The resulting output string is always null-terminated. * * @parm LPCSTR | pszSrc | * * Source buffer containing ANSI string to be converted. * * @parm LPWSTR | pwszDst | * * Destination buffer to receive converted UNICODE string. * * @parm int | cwchBuf | * * Size of the destination buffer in s. * * @returns * * On success, the number of characters copied to the output * buffer is returned, including the terminating null. * */ int SHAnsiToUnicode(LPCSTR pszSrc, LPWSTR pwszDst, int cwchBuf) { return SHAnsiToUnicodeCP_ACP(pszSrc, pwszDst, cwchBuf); } // dupe a string using the task allocator for returing from a COM interface // These functions use SHAlloc, so they cannot go into shlwapi. STDAPI SHStrDupA(LPCSTR psz, WCHAR **ppwsz) { DWORD cch = MultiByteToWideChar(CP_ACP, 0, psz, -1, NULL, 0); *ppwsz = (WCHAR *)CoTaskMemAlloc((cch + 1) * SIZEOF(WCHAR)); if (*ppwsz) { MultiByteToWideChar(CP_ACP, 0, psz, -1, *ppwsz, cch); return S_OK; } return E_OUTOFMEMORY; } // dupe a string using the task allocator for returing from a COM interface STDAPI SHStrDupW(LPCWSTR psz, WCHAR **ppwsz) { *ppwsz = (WCHAR *)CoTaskMemAlloc((lstrlenW(psz) + 1) * SIZEOF(WCHAR)); if (*ppwsz) { StrCpyW(*ppwsz, psz); return S_OK; } return E_OUTOFMEMORY; } // Modifies: // szRoot // // Returns: // TRUE if a drive root was found // FALSE otherwise // STDAPI_(BOOL) PathStripToRoot( LPTSTR szRoot) { while(!PathIsRoot(szRoot)) { if (!PathRemoveFileSpec(szRoot)) { // If we didn't strip anything off, // must be current drive return(FALSE); } } return(TRUE); } // // SHStringFromGUIDA // // converts GUID into (...) form without leading identifier; returns // amount of data copied to lpsz if successful; 0 if buffer too small. // // An endian-dependant map of what bytes go where in the GUID // text representation. // // Do NOT use the TEXT() macro in GuidMap... they're intended to be bytes // static const BYTE c_rgbGuidMap[] = { 3, 2, 1, 0, '-', 5, 4, '-', 7, 6, '-', 8, 9, '-', 10, 11, 12, 13, 14, 15 }; static const CHAR c_szDigitsA[] = "0123456789ABCDEF"; static const WCHAR c_szDigitsW[] = TEXTW("0123456789ABCDEF"); STDAPI_(int) SHStringFromGUIDA( UNALIGNED REFGUID rguid, LPSTR psz, int cchMax) { int i; const BYTE * pBytes = (const BYTE *) rguid; if (cchMax < GUIDSTR_MAX) return 0; #ifdef BIG_ENDIAN // This is the slow, but portable version wnsprintf(psz, cchMax,"{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", rguid->Data1, rguid->Data2, rguid->Data3, rguid->Data4[0], rguid->Data4[1], rguid->Data4[2], rguid->Data4[3], rguid->Data4[4], rguid->Data4[5], rguid->Data4[6], rguid->Data4[7]); #else // The following algorithm is faster than the wsprintf. *psz++ = '{'; for (i = 0; i < SIZEOF(c_rgbGuidMap); i++) { if (c_rgbGuidMap[i] == '-') // don't TEXT() this line { *psz++ = '-'; } else { // Convert a byte-value into a character representation *psz++ = c_szDigitsA[ (pBytes[c_rgbGuidMap[i]] & 0xF0) >> 4 ]; *psz++ = c_szDigitsA[ (pBytes[c_rgbGuidMap[i]] & 0x0F) ]; } } *psz++ = '}'; *psz = '\0'; #endif /* !BIG_ENDIAN */ return GUIDSTR_MAX; } STDAPI_(int) SHStringFromGUIDW( UNALIGNED REFGUID rguid, LPWSTR psz, int cchMax) { int i; const BYTE * pBytes = (const BYTE *) rguid; if (cchMax < GUIDSTR_MAX) return 0; #ifdef BIG_ENDIAN // This is the slow, but portable version wnsprintfW(psz, cchMax, L"{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", rguid->Data1, rguid->Data2, rguid->Data3, rguid->Data4[0], rguid->Data4[1], rguid->Data4[2], rguid->Data4[3], rguid->Data4[4], rguid->Data4[5], rguid->Data4[6], rguid->Data4[7]); #else // The following algorithm is faster than the wsprintf. *psz++ = TEXTW('{'); for (i = 0; i < SIZEOF(c_rgbGuidMap); i++) { if (c_rgbGuidMap[i] == '-') // don't TEXT() this line { *psz++ = TEXTW('-'); } else { // Convert a byte-value into a character representation *psz++ = c_szDigitsW[ (pBytes[c_rgbGuidMap[i]] & 0xF0) >> 4 ]; *psz++ = c_szDigitsW[ (pBytes[c_rgbGuidMap[i]] & 0x0F) ]; } } *psz++ = TEXTW('}'); *psz = TEXTW('\0'); #endif /* !BIG_ENDIAN */ return GUIDSTR_MAX; } // // Why do we use the unsafe version? // // - Unsafe is much faster. // // - The safe version isn't safe after all and serves only to mask // existing bugs. The situation the safe version "saves" is if // two threads both try to atomicrelease the same object. This // means that at the same moment, both threads think the object // is alive. Change the timing slightly, and now one thread // atomicreleases the object before the other one, so the other // thread is now using an object after the first thread already // atomicreleased it. Bug. // STDAPI_(void) IUnknown_AtomicRelease(void **ppunk) { #if 1 // Unsafe if (ppunk && *ppunk) { IUnknown* punk = *(IUnknown**)ppunk; *ppunk = NULL; punk->lpVtbl->Release(punk); } #else // Safe if (ppunk) { IUnknown* punk = (IUnknown *)InterlockedExchangePointer(ppunk, NULL); if (punk) { punk->Release(); } } #endif } #endif //DOWNLEVEL_PLATFORM