windows-nt/Source/XPSP1/NT/shell/shlwapi/native.cpp
2020-09-26 16:20:57 +08:00

509 lines
15 KiB
C++

// Contains code that needs to be dual compiled, once for ansi and once for unicode
#include "priv.h"
#include <memt.h>
#ifdef _X86_
#include <w95wraps.h>
#endif
BOOL _PathAppend(LPCTSTR pszBase, LPCTSTR pszAppend, LPTSTR pszOut, DWORD cchOut)
{
DWORD cchBase = lstrlen(pszBase);
// +1 is one for the whack
if (cchOut > cchBase + lstrlen(pszAppend) + 1)
{
StrCpy(pszOut, pszBase);
pszOut+=cchBase;
*pszOut++ = TEXT('\\');
StrCpy(pszOut, pszAppend);
return TRUE;
}
return FALSE;
}
LWSTDAPI AssocMakeFileExtsToApplication(ASSOCMAKEF flags, LPCTSTR pszExt, LPCTSTR pszApplication)
{
RIP(!IsOS(OS_WHISTLERORGREATER));
DWORD err = ERROR_SUCCESS;
WCHAR sz[MAX_PATH];
SHTCharToUnicode(pszExt, sz, ARRAYSIZE(sz));
HKEY hk = SHGetShellKey(SHELLKEY_HKCU_FILEEXTS, sz, pszApplication != NULL);
if (hk)
{
if (pszApplication)
{
err = SHSetValue(hk, NULL, TEXT("Application"),
REG_SZ, pszApplication, CbFromCch(lstrlen(pszApplication) +1));
}
else // we should always clear
err = SHDeleteValue(hk, NULL, TEXT("Application"));
RegCloseKey(hk);
}
else
err = GetLastError();
return HRESULT_FROM_WIN32(err);
}
HRESULT _AllocValueString(HKEY hkey, LPCTSTR pszKey, LPCTSTR pszVal, LPTSTR *ppsz)
{
DWORD cb, err;
err = SHGetValue(hkey, pszKey, pszVal, NULL, NULL, &cb);
ASSERT(ppsz);
*ppsz = NULL;
if (NOERROR == err)
{
LPTSTR psz = (LPTSTR) LocalAlloc(LPTR, cb);
if (psz)
{
err = SHGetValue(hkey, pszKey, pszVal, NULL, (LPVOID)psz, &cb);
if (NOERROR == err)
*ppsz = psz;
else
LocalFree(psz);
}
else
err = ERROR_OUTOFMEMORY;
}
return HRESULT_FROM_WIN32(err);
}
// <Swipped from the NT5 version of Shell32>
#define SZ_REGKEY_FILEASSOCIATION TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileAssociation")
LWSTDAPI_(void) PrettifyFileDescription(LPTSTR pszDesc, LPCTSTR pszCutList)
{
LPTSTR pszCutListReg;
if (!pszDesc || !*pszDesc)
return;
// get the Cut list from registry
// this is MULTI_SZ
if (S_OK == _AllocValueString(HKEY_LOCAL_MACHINE, SZ_REGKEY_FILEASSOCIATION, TEXT("CutList"), &pszCutListReg))
{
pszCutList = pszCutListReg;
}
if (pszCutList)
{
// cut strings in cut list from file description
for (LPCTSTR pszCut = pszCutList; *pszCut; pszCut = pszCut + lstrlen(pszCut) + 1)
{
LPTSTR pch = StrRStrI(pszDesc, NULL, pszCut);
// cut the exact substring from the end of file description
if (pch && !*(pch + lstrlen(pszCut)))
{
*pch = '\0';
// remove trailing spaces
for (--pch; (pch >= pszDesc) && (TEXT(' ') == *pch); pch--)
*pch = 0;
break;
}
}
if (pszCutListReg)
LocalFree(pszCutListReg);
}
}
/*
<Swipped from the NT5 version of Shell32>
GetFileDescription retrieves the friendly name from a file's verion rsource.
The first language we try will be the first item in the
"\VarFileInfo\Translations" section; if there's nothing there,
we try the one coded into the IDS_VN_FILEVERSIONKEY resource string.
If we can't even load that, we just use English (040904E4). We
also try English with a null codepage (04090000) since many apps
were stamped according to an old spec which specified this as
the required language instead of 040904E4.
If there is no FileDescription in version resource, return the file name.
Parameters:
LPCTSTR pszPath: full path of the file
LPTSTR pszDesc: pointer to the buffer to receive friendly name. If NULL,
*pcchDesc will be set to the length of friendly name in
characters, including ending NULL, on successful return.
UINT *pcchDesc: length of the buffer in characters. On successful return,
it contains number of characters copied to the buffer,
including ending NULL.
Return:
TRUE on success, and FALSE otherwise
*/
BOOL WINAPI SHGetFileDescription(LPCTSTR pszPath, LPCTSTR pszVersionKeyIn, LPCTSTR pszCutListIn, LPTSTR pszDesc, UINT *pcchDesc)
{
UINT cchValue = 0;
TCHAR szPath[MAX_PATH], *pszValue = NULL;
DWORD dwAttribs;
DWORD dwHandle; /* version subsystem handle */
DWORD dwVersionSize; /* size of the version data */
LPTSTR lpVersionBuffer = NULL; /* pointer to version data */
TCHAR szVersionKey[60]; /* big enough for anything we need */
struct _VERXLATE
{
WORD wLanguage;
WORD wCodePage;
} *lpXlate; /* ptr to translations data */
ASSERT(pszPath && *pszPath && pcchDesc);
if (!PathFileExistsAndAttributes(pszPath, &dwAttribs))
{
return FALSE;
}
// copy the path to the dest dir
lstrcpyn(szPath, pszPath, ARRAYSIZE(szPath));
if ((dwAttribs & FILE_ATTRIBUTE_DIRECTORY) ||
PathIsUNCServer(pszPath) ||
PathIsUNCServerShare(pszPath))
{
// bail in the \\server, \\server\share, and directory case or else GetFileVersionInfo() will try
// to do a LoadLibraryEx() on the path (which will fail, but not before we seach the entire include
// path which can take a long time)
goto Exit;
}
dwVersionSize = GetFileVersionInfoSize(szPath, &dwHandle);
if (dwVersionSize == 0L)
goto Exit; /* no version info */
lpVersionBuffer = (LPTSTR)LocalAlloc(LPTR, dwVersionSize);
if (lpVersionBuffer == NULL)
goto Exit;
if (!GetFileVersionInfo(szPath, dwHandle, dwVersionSize, lpVersionBuffer))
goto Exit;
// Try same language as the caller
if (pszVersionKeyIn)
{
lstrcpyn(szVersionKey, pszVersionKeyIn, ARRAYSIZE(szVersionKey));
if (VerQueryValue(lpVersionBuffer, szVersionKey, (void **)&pszValue, &cchValue))
{
goto Exit;
}
}
// Try first language this supports
// Look for translations
if (VerQueryValue(lpVersionBuffer, TEXT("\\VarFileInfo\\Translation"),
(void **)&lpXlate, &cchValue)
&& cchValue)
{
wnsprintf(szVersionKey, ARRAYSIZE(szVersionKey), TEXT("\\StringFileInfo\\%04X%04X\\FileDescription"),
lpXlate[0].wLanguage, lpXlate[0].wCodePage);
if (VerQueryValue(lpVersionBuffer, szVersionKey, (void **)&pszValue, &cchValue))
goto Exit;
}
#ifdef UNICODE
// try English, unicode code page
lstrcpy(szVersionKey, TEXT("\\StringFileInfo\\040904B0\\FileDescription"));
if (VerQueryValue(lpVersionBuffer, szVersionKey, (void **)&pszValue, &cchValue))
goto Exit;
#endif
// try English
lstrcpy(szVersionKey, TEXT("\\StringFileInfo\\040904E4\\FileDescription"));
if (VerQueryValue(lpVersionBuffer, szVersionKey, (void **)&pszValue, &cchValue))
goto Exit;
// try English, null codepage
lstrcpy(szVersionKey, TEXT("\\StringFileInfo\\04090000\\FileDescription"));
if (VerQueryValue(lpVersionBuffer, szVersionKey, (void **)&pszValue, &cchValue))
goto Exit;
Exit:
if (!pszValue || !*pszValue)
{
// Could not find FileVersion info in a reasonable format, return file name
PathRemoveExtension(szPath);
pszValue = PathFindFileName(szPath);
cchValue = lstrlen(pszValue);
}
PrettifyFileDescription(pszValue, pszCutListIn);
cchValue = lstrlen(pszValue) + 1;
if (!pszDesc) // only want to know the length of the friendly name
*pcchDesc = cchValue;
else
{
*pcchDesc = min(*pcchDesc, cchValue);
lstrcpyn(pszDesc, pszValue, *pcchDesc);
}
if (lpVersionBuffer)
LocalFree(lpVersionBuffer);
return TRUE;
}
// Convert LPTSTR to LPSTR and return TRUE if the LPSTR can
// be converted back to LPTSTR without unacceptible data loss
//
BOOL DoesStringRoundTrip(LPCTSTR pwszIn, LPSTR pszOut, UINT cchOut)
{
#ifdef UNICODE
// On NT5 we have to be more stringent since you can switch UI
// languages on the fly, thereby breaking this constant codepage
// assumption inherent in the downlevel implementations.
//
// we have to support the function being called with a null pszOut
// just to determine if pwszIn will roundtrip
//
if (g_bRunningOnNT5OrHigher)
{
LPCTSTR pIn = pwszIn;
LPSTR pOut = pszOut;
UINT cch = cchOut;
while (*pIn)
{
if (*pIn > ((TCHAR)127))
{
if (cchOut) // caller has provided a buffer
{
#ifdef DEBUG
SHUnicodeToAnsiCP(CP_ACPNOVALIDATE, pwszIn, pszOut, cchOut);
#else
SHUnicodeToAnsi(pwszIn, pszOut, cchOut);
#endif
}
return FALSE;
}
if (cch) // we have a buffer and it still has space
{
*pOut++ = (char)*pIn;
if (!--cch)
{
break; // out buffer filled, leave.
}
}
pIn++;
}
// Null terminate the out buffer
if (cch)
{
*pOut = '\0';
}
else if (cchOut)
{
*(pOut-1) = '\0';
}
// Everything was low ascii, no dbcs worries and it will always round-trip
return TRUE;
}
else
// but we probably don't want to change downlevel shell behavior
// in this regard, so we keep that implementation:
//
{
BOOL fRet = FALSE;
WCHAR wszTemp[MAX_PATH];
LPWSTR pwszTemp = wszTemp;
UINT cchTemp = ARRAYSIZE(wszTemp);
// We better have enough room for the buffer.
if (ARRAYSIZE(wszTemp) < cchOut)
{
pwszTemp = (LPWSTR)LocalAlloc(LPTR, cchOut*sizeof(WCHAR));
cchTemp = cchOut;
}
if (pwszTemp)
{
#ifdef DEBUG
SHUnicodeToAnsiCP(CP_ACPNOVALIDATE, pwszIn, pszOut, cchOut);
#else
SHUnicodeToAnsi(pwszIn, pszOut, cchOut);
#endif
SHAnsiToUnicode(pszOut, pwszTemp, cchTemp);
fRet = StrCmpCW(pwszIn, pwszTemp) == 0; // are they the same?
if (pwszTemp != wszTemp)
{
LocalFree(pwszTemp);
}
}
return fRet;
}
#else
StrCpyN(pszOut, pwszIn, cchOut);
return TRUE;
#endif
}
DWORD _ExpandRegString(PTSTR pszData, DWORD cchData, DWORD *pcchSize)
{
DWORD err = ERROR_OUTOFMEMORY;
PTSTR psz = StrDup(pszData);
if (psz)
{
// now we will try to expand back into the target buffer
// NOTE we deliberately dont use SHExpandEnvironmentStrings
// since it will not give us the size we need
// we have to use
#ifdef UNICODE
*pcchSize = ExpandEnvironmentStringsW(psz, pszData, cchData);
#else
*pcchSize = ExpandEnvironmentStringsA(psz, pszData, cchData);
#endif
if (*pcchSize > 0)
{
if (*pcchSize <= cchData)
{
err = NO_ERROR;
}
else
{
// pcchSize returns the needed size
err = ERROR_MORE_DATA;
}
}
else
err = GetLastError();
LocalFree(psz);
}
return err;
}
DWORD _SizeExpandString(HKEY hk, PCTSTR pszValue, void *pvData, DWORD *pcbSize)
{
DWORD err = ERROR_OUTOFMEMORY;
// *pcbSize is the size required by RegQueryValueEx
// Find out the length of the expanded string
// we have to call in and actually get the data to do this
PTSTR psz;
if (SUCCEEDED(SHLocalAlloc(*pcbSize + sizeof(TCHAR), &psz)))
{
err = RegQueryValueEx(hk, pszValue, NULL, NULL, (LPBYTE)psz, pcbSize);
// ASSERT(err != ERROR_MORE_DATA);
if (NO_ERROR == err)
{
TCHAR szBuff[1];
// insure NULL termination with our extra char
psz[*pcbSize/sizeof(TCHAR)] = 0;
#ifdef UNICODE
DWORD cbExpand = CbFromCchW(ExpandEnvironmentStringsW(psz, szBuff, ARRAYSIZE(szBuff)));
#else
DWORD cbExpand = CbFromCchA(ExpandEnvironmentStringsA(psz, szBuff, ARRAYSIZE(szBuff)));
#endif
if (cbExpand > *pcbSize)
*pcbSize = cbExpand;
}
LocalFree(psz);
// if pvData is NULL then we return success (caller is sizing)
// if pvData is non-NULL we need to return ERROR_MORE_DATA
if (err == NO_ERROR && pvData)
err = ERROR_MORE_DATA;
}
return err;
}
#ifdef UNICODE
#define FixupRegString FixupRegStringW
#else
#define FixupRegString FixupRegStringA
#endif
STDAPI_(DWORD) FixupRegString(HKEY hk, PCTSTR pszValue, BOOL fExpand, DWORD err, void *pvData, DWORD *pcbData, DWORD *pcbSize)
{
BOOL fNeedsSize = FALSE;
if ((NO_ERROR == err && pvData))
{
PTSTR pszData = (PTSTR)pvData;
DWORD cchSize = *pcbSize / sizeof(TCHAR);
DWORD cchData = *pcbData / sizeof(TCHAR);
// Note: on Win95, RegSetValueEx will always write the
// full string out, including the null terminator. On NT,
// it won't unless the right length was specified.
// Hence, we have the following check for termination
// FRINGE: if you get back exactly the size you asked for
// and its not NULL terminated, then we need to
// treat it like ERROR_MORE_DATA
// if the value was empty, treat it like a non-terminated string
if (!cchSize || pszData[cchSize - 1])
{
// this string was not NULL terminated
if (cchData > cchSize)
{
// NULL terminate just in case
pszData[cchSize++] = 0;
}
else
{
ASSERT(cchData == cchSize);
fNeedsSize = TRUE;
}
}
if (!fNeedsSize)
{
// this will expand if necessary and
// if the expand overflows, return ERROR_MORE_DATA
if (fExpand)
err = _ExpandRegString(pszData, cchData, &cchSize);
*pcbSize = CbFromCch(cchSize);
}
}
else if (pcbData && (err == ERROR_MORE_DATA || (NO_ERROR == err && !pvData)))
{
// we need to calculate if:
// 1. the RegQueryValueEx() says there is not enough room
// 2. the caller is requesting the size
fNeedsSize = TRUE;
}
if (fNeedsSize )
{
if (fExpand)
{
err = _SizeExpandString(hk, pszValue, pvData, pcbSize);
}
else
{
pcbSize += sizeof(TCHAR);
err = pvData ? ERROR_MORE_DATA : NO_ERROR;
}
}
return err;
}