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

651 lines
22 KiB
C++

//+---------------------------------------------------------------------------
//
// File: inistr.cpp
//
// Contents: SHGet/SetIniStringW implementations, which save strings into
// INI files in a manner that survives the round trip to disk.
//
//----------------------------------------------------------------------------
#include "priv.h"
#define _SHELL32_
#define _SHDOCVW_
#include "unicwrap.h"
#include <platform.h>
#include <mlang.h>
//
// Do this in every wrapper function that has an output parameter.
// It raises assertion failures on the main code path so that
// the same assertions are raised on NT and 95. The CStrOut class
// doesn't like it when you say that an output buffer is NULL yet
// has nonzero length. Without this macro, the bug would go undetected
// on NT and appear only on Win95.
//
#define VALIDATE_OUTBUF(s, cch) ASSERT((s) != NULL || (cch) == 0)
//----------------------------------------------------------------------------
//
// The basic problem is that INI files are ANSI-only, so any UNICODE
// string you put into it won't round-trip.
//
// So the solution is to record UNICODE strings in UTF7. Why UTF7?
// Because we can't use UTF8, since XxxPrivateProfileStringW will try
// to convert the 8-bit values to/from UNICODE and mess them up. Since
// some of the 8-bit values might not even be valid (e.g., a DBCS lead
// byte followed by an illegal trail byte), we cannot assume that the
// string will survive the ANSI -> UNICODE -> ANSI round-trip.
//
// The UTF7 string is stored in a [Section.W] section, under the
// same key name. The original ANSI string is stored in a [Section.A]
// section, again, with the same key name.
//
// (We separate the A/W from the section name with a dot so it is less
// likely that we will accidentally collide with other section names.
//
// We store the original ANSI string twice so we can compare the two
// and see if a downlevel app (e.g., IE4) has changed the [Section]
// version. If so, then we ignore the [Section.W] version since it's stale.
//
// If the original string is already 7-bit clean, then no UTF7 string is
// recorded.
//
BOOL
Is7BitClean(LPCWSTR pwsz)
{
for ( ; *pwsz; pwsz++) {
if ((UINT)*pwsz > 127)
return FALSE;
}
return TRUE;
}
//----------------------------------------------------------------------------
//
// Yet another conversion class -- this one is for creating the
// variants of a section name.
//
// Note! Since INI files are ASCII, section names are necessarily 7-bit
// clean, so we can cheat a lot of stuff.
//
class CStrSectionX : public CConvertStrW
{
public:
CStrSectionX(LPCWSTR pwszSection);
};
//
// We append a dot an an A or W to the section name.
//
#define SECTION_SUFFIX_LEN 2
CStrSectionX::CStrSectionX(LPCWSTR pwszSection)
{
ASSERT(_pwstr == NULL);
if (pwszSection) {
ASSERT(Is7BitClean(pwszSection));
UINT cwchNeeded = lstrlenW(pwszSection) + SECTION_SUFFIX_LEN + 1;
if (cwchNeeded > ARRAYSIZE(_awch)) {
_pwstr = new WCHAR[cwchNeeded];
} else {
_pwstr = _awch;
}
if (_pwstr) {
// Build the string initially with ".A" stuck on the end
// It will later get changed to a ".W"
lstrcpyW(_pwstr, pwszSection);
lstrcatW(_pwstr, L".A");
}
}
}
//----------------------------------------------------------------------------
//
// Mini-class for keeping track of UTF7 strings. These are kept in ANSI
// most of the time since that's what ConvertINetUnicodeToMultiByte uses.
//
// The UTF7 shadow is prefixed by a checksum of the original string, which
// we use on read-back to see if the shadow still corresponds to the
// original string.
//
class CStrUTF7 : public CConvertStr
{
public:
inline CStrUTF7() : CConvertStr(CP_ACP) { };
void SetUnicode(LPCWSTR pwszValue);
};
//
// Note that this can be slow since it happens only when we encounter
// a non-ANSI character.
//
void CStrUTF7::SetUnicode(LPCWSTR pwszValue)
{
int cwchLen = lstrlenW(pwszValue);
HRESULT hres;
DWORD dwMode;
int cwchLenT = cwchLen;
// Save room for terminating NULL. We must convert the NULL separately
// because UTF7 does not translate NULL to NULL.
int cchNeeded = ARRAYSIZE(_ach) - 1;
dwMode = 0;
hres = ConvertINetUnicodeToMultiByte(&dwMode, CP_UTF7, pwszValue,
&cwchLenT, _ach,
&cchNeeded);
if (SUCCEEDED(hres)) {
ASSERT(cchNeeded + 1 <= ARRAYSIZE(_ach));
_pstr = _ach;
} else {
_pstr = new CHAR[cchNeeded + 1];
if (!_pstr)
return; // No string - tough
cwchLenT = cwchLen;
dwMode = 0;
hres = ConvertINetUnicodeToMultiByte(&dwMode, CP_UTF7, pwszValue,
&cwchLenT, _pstr,
&cchNeeded);
if (FAILED(hres)) { // Couldn't convert - tough
Free();
return;
}
}
// Terminate explicitly since UTF7 doesn't.
_pstr[cchNeeded] = '\0';
}
//
// pwszSection = section name into which to write pwszValue (UNICODE)
// pwszSectionA = section name into which to write ANSI shadow
// pwszKey = key name for both pwszValue and strUTF7
// pwszFileName = file name
//
// pwszSectionA can be NULL if a low-memory condition was encountered.
//
// strUTF7 can be NULL, meaning that the shadows should be deleted.
//
// Write pwszSection first, followed by pwszSectionA, then pwszSectionW.
// This ensures that the backwards-compatibility string comes first in
// the file, in case there are apps that assume such.
//
// pwszSectionW is computed from pwszSectionA by changing the last "A"
// to a "W". pwszSecionW gets the UTF7-encoded unicode string.
// strUTF7 might be NULL, meaning that we should delete the shadow strings.
//
BOOL WritePrivateProfileStringMultiW(LPCWSTR pwszSection, LPCWSTR pwszValue,
LPWSTR pwszSectionA, CStrUTF7& strUTF7,
LPCWSTR pwszKey, LPCWSTR pwszFileName)
{
BOOL fRc = WritePrivateProfileStringW(pwszSection, pwszKey, pwszValue, pwszFileName);
if (pwszSectionA) {
//
// Write the [Section.A] key, or delete it if there is no UTF7.
//
WritePrivateProfileStringW(pwszSectionA, pwszKey,
strUTF7 ? pwszValue : NULL, pwszFileName);
//
// Now change pwszSectionA to pwszSectionW so we can write out
// the UTF7 encoding.
//
pwszSectionA[lstrlenW(pwszSectionA) - 1] = TEXT('W');
CStrInW strUTF7W(strUTF7);
// This really writes [Section.W]
WritePrivateProfileStringW(pwszSectionA, pwszKey, strUTF7W, pwszFileName);
}
return fRc;
}
BOOL WritePrivateProfileStringMultiA(LPCWSTR pwszSection, LPCWSTR pwszValue,
LPWSTR pwszSectionA, CStrUTF7& strUTF7,
LPCWSTR pwszKey, LPCWSTR pwszFileName)
{
CStrIn strSection(pwszSection);
CStrIn strSectionA(pwszSectionA);
CStrIn strValue(pwszValue);
CStrIn strKey(pwszKey);
CPPFIn strFileName(pwszFileName); // PrivateProfile filename needs special class
BOOL fRc = WritePrivateProfileStringA(strSection, strKey, strValue, strFileName);
if ((LPSTR)strSectionA) {
//
// Write the [Section.A] key, or delete it if there is no UTF7.
//
WritePrivateProfileStringA(strSectionA, strKey,
#if defined(UNIX) && !defined(ux10)
strUTF7 ? (LPSTR)strValue : (LPSTR)NULL, strFileName);
#else
strUTF7 ? strValue : NULL, strFileName);
#endif
//
// Now change strSectionA to strSectionW so we can write out
// the UTF7 encoding.
//
strSectionA[lstrlenA(strSectionA) - 1] = 'W';
// This really writes [Section.W]
WritePrivateProfileStringA(strSectionA, strKey, strUTF7, strFileName);
}
return fRc;
}
BOOL WINAPI
SHSetIniStringW(LPCWSTR pwszSection, LPCWSTR pwszKey, LPCWSTR pwszValue, LPCWSTR pwszFileName)
{
// We have no way of encoding these two, so they had better by 7-bit clean
// We also do not support "delete entire section"
AssertMsg(pwszSection != NULL,
TEXT("SHSetIniStringW: Section name cannot be NULL; bug in caller"));
AssertMsg(Is7BitClean(pwszSection),
TEXT("SHSetIniStringW: Section name not 7-bit clean; bug in caller"));
AssertMsg(pwszKey != NULL,
TEXT("SHSetIniStringW: Key name cannot be NULL; bug in caller"));
AssertMsg(!pwszKey || Is7BitClean(pwszKey),
TEXT("SHSetIniStringW: Key name not 7-bit clean; bug in caller"));
CStrSectionX strSectionX(pwszSection);
CStrUTF7 strUTF7; // Assume no UTF7 needed.
if (strSectionX && pwszKey && pwszValue && !Is7BitClean(pwszValue)) {
//
// The value is not 7-bit clean. Must create a UTF7 version.
//
strUTF7.SetUnicode(pwszValue);
}
if (g_bRunningOnNT)
return WritePrivateProfileStringMultiW(pwszSection, pwszValue,
strSectionX, strUTF7,
pwszKey, pwszFileName);
else
return WritePrivateProfileStringMultiA(pwszSection, pwszValue,
strSectionX, strUTF7,
pwszKey, pwszFileName);
}
//
// Keep calling GetPrivateProfileString with bigger and bigger buffers
// until we get the entire string. Start with MAX_PATH, since that's
// usually plenty big enough.
//
// The returned buffer must be freed with LocalFree, not delete[].
//
LPVOID GetEntirePrivateProfileStringAorW(
LPCVOID pszSection,
LPCVOID pszKey,
LPCVOID pszFileName,
BOOL fUnicode)
{
int CharSize = fUnicode ? sizeof(WCHAR) : sizeof(CHAR);
UINT cchResult = MAX_PATH;
LPVOID pszResult = LocalAlloc(LMEM_FIXED, cchResult * CharSize);
LPVOID pszFree = pszResult;
while (pszResult) {
UINT cchRc;
if (fUnicode)
cchRc = GetPrivateProfileStringW((LPCWSTR)pszSection,
(LPCWSTR)pszKey,
L"",
(LPWSTR)pszResult, cchResult,
(LPCWSTR)pszFileName);
else
cchRc = GetPrivateProfileStringA((LPCSTR)pszSection,
(LPCSTR)pszKey,
"",
(LPSTR)pszResult, cchResult,
(LPCSTR)pszFileName);
if (cchRc < cchResult - 1)
return pszResult;
// Buffer too small - iterate
cchResult *= 2;
LPVOID pszNew = LocalReAlloc(pszResult, cchResult * CharSize, LMEM_MOVEABLE);
pszFree = pszResult;
pszResult = pszNew;
}
//
// Memory allocation failed; free pszFree while we still can.
//
if (pszFree)
LocalFree(pszFree);
return NULL;
}
DWORD GetPrivateProfileStringMultiW(LPCWSTR pwszSection, LPCWSTR pwszKey,
LPWSTR pwszSectionA,
LPWSTR pwszReturnedString, DWORD cchSize,
LPCWSTR pwszFileName)
{
LPWSTR pwszValue = NULL;
LPWSTR pwszValueA = NULL;
LPWSTR pwszUTF7 = NULL;
DWORD dwRc;
pwszValue = (LPWSTR)GetEntirePrivateProfileStringAorW(
pwszSection, pwszKey,
pwszFileName, TRUE);
if (pwszValue) {
//
// If the value is an empty string, then don't waste your
// time trying to get the UNICODE version - the UNICODE version
// of the empty string is the empty string.
//
// Otherwise, get the ANSI shadow hidden in [Section.A]
// and see if it matches. If not, then the file was edited
// by a downlevel app and we should just use pwszValue after all.
if (pwszValue[0] &&
(pwszValueA = (LPWSTR)GetEntirePrivateProfileStringAorW(
pwszSectionA, pwszKey,
pwszFileName, TRUE)) != NULL &&
lstrcmpW(pwszValue, pwszValueA) == 0) {
// our shadow is still good - run with it
// Change [Section.A] to [Section.W]
pwszSectionA[lstrlenW(pwszSectionA) - 1] = TEXT('W');
pwszUTF7 = (LPWSTR)GetEntirePrivateProfileStringAorW(
pwszSectionA, pwszKey,
pwszFileName, TRUE);
CStrIn strUTF7(pwszUTF7);
dwRc = 0; // Assume something goes wrong
if (strUTF7) {
dwRc = SHAnsiToUnicodeCP(CP_UTF7, strUTF7, pwszReturnedString, cchSize);
}
if (dwRc == 0) {
// Problem converting to UTF7 - just use the ANSI version
dwRc = SHUnicodeToUnicode(pwszValue, pwszReturnedString, cchSize);
}
} else {
// String was empty or couldn't get [Section.A] shadow or
// shadow doesn't match. Just use the regular string.
dwRc = SHUnicodeToUnicode(pwszValue, pwszReturnedString, cchSize);
}
// The SHXxxToYyy functions include the terminating zero,
// which we want to exclude.
if (dwRc > 0)
dwRc--;
} else {
// Fatal error reading values from file; just use the boring API
dwRc = GetPrivateProfileStringW(pwszSection,
pwszKey,
L"",
pwszReturnedString, cchSize,
pwszFileName);
}
if (pwszValue)
LocalFree(pwszValue);
if (pwszValueA)
LocalFree(pwszValueA);
if (pwszUTF7)
LocalFree(pwszUTF7);
return dwRc;
}
DWORD GetPrivateProfileStringMultiA(LPCWSTR pwszSection, LPCWSTR pwszKey,
LPWSTR pwszSectionA,
LPWSTR pwszReturnedString, DWORD cchSize,
LPCWSTR pwszFileName)
{
CStrIn strSection(pwszSection);
CStrIn strSectionA(pwszSectionA);
CStrIn strKey(pwszKey);
CPPFIn strFileName(pwszFileName); // PrivateProfile filename needs special class
LPSTR pszValue = NULL;
LPSTR pszValueA = NULL;
LPSTR pszUTF7 = NULL;
DWORD dwRc;
if (strSectionA &&
(pszValue = (LPSTR)GetEntirePrivateProfileStringAorW(
strSection, strKey,
strFileName, FALSE)) != NULL) {
//
// If the value is an empty string, then don't waste your
// time trying to get the UNICODE version - the UNICODE version
// of the empty string is the empty string.
//
// Otherwise, get the ANSI shadow hidden in [Section.A]
// and see if it matches. If not, then the file was edited
// by a downlevel app and we should just use pwszValue after all.
if (pszValue[0] &&
(pszValueA = (LPSTR)GetEntirePrivateProfileStringAorW(
strSectionA, strKey,
strFileName, FALSE)) != NULL &&
lstrcmpA(pszValue, pszValueA) == 0) {
// our shadow is still good - run with it
// Change [Section.A] to [Section.W]
strSectionA[lstrlenA(strSectionA) - 1] = 'W';
pszUTF7 = (LPSTR)GetEntirePrivateProfileStringAorW(
strSectionA, strKey,
strFileName, FALSE);
dwRc = 0; // Assume something goes wrong
if (pszUTF7) {
dwRc = SHAnsiToUnicodeCP(CP_UTF7, pszUTF7, pwszReturnedString, cchSize);
}
if (dwRc == 0) {
// Problem converting to UTF7 - just use the ANSI version
dwRc = SHAnsiToUnicode(pszValue, pwszReturnedString, cchSize);
}
} else {
// String was empty or couldn't get [Section.A] shadow or
// shadow doesn't match. Just use the regular string.
dwRc = SHAnsiToUnicode(pszValue, pwszReturnedString, cchSize);
}
// The SHXxxToYyy functions include the terminating zero,
// which we want to exclude.
if (dwRc > 0)
dwRc--;
} else {
// Fatal error reading values from file; just use the boring API
CStrOut strOut(pwszReturnedString, cchSize);
dwRc = GetPrivateProfileStringA(strSection,
strKey,
"",
strOut, cchSize,
strFileName);
strOut.ConvertIncludingNul();
}
if (pszValue)
LocalFree(pszValue);
if (pszValueA)
LocalFree(pszValueA);
if (pszUTF7)
LocalFree(pszUTF7);
return dwRc;
}
DWORD WINAPI SHGetIniStringW(LPCWSTR pwszSection, LPCWSTR pwszKey, LPWSTR pwszReturnedString, DWORD cchSize, LPCWSTR pwszFileName)
{
VALIDATE_OUTBUF(pwszReturnedString, cchSize);
// We have no way of encoding these two, so they had better by 7-bit clean
// We also do not support "get all section names" or "get entire section".
AssertMsg(pwszSection != NULL,
TEXT("SHGetIniStringW: Section name cannot be NULL; bug in caller"));
AssertMsg(Is7BitClean(pwszSection),
TEXT("SHGetIniStringW: Section name not 7-bit clean; bug in caller"));
AssertMsg(pwszKey != NULL,
TEXT("SHGetIniStringW: Key name cannot be NULL; bug in caller"));
AssertMsg(Is7BitClean(pwszKey),
TEXT("SHGetIniStringW: Key name not 7-bit clean; bug in caller"));
CStrSectionX strSectionX(pwszSection);
if (g_bRunningOnNT)
return GetPrivateProfileStringMultiW(pwszSection, pwszKey,
strSectionX,
pwszReturnedString, cchSize,
pwszFileName);
else
return GetPrivateProfileStringMultiA(pwszSection, pwszKey,
strSectionX,
pwszReturnedString, cchSize,
pwszFileName);
}
//+---------------------------------------------------------------------------
//
// CreateURLFileContents
//
// shdocvw.dll and url.dll need to create in-memory images
// of URL files, so this helper function does all the grunky work so they
// can remain insulated from the way we encode Unicode strings into INI files.
// The resulting memory should be freed via GlobalFree().
//
// Writes a string into the URL file. If fWrite is FALSE, then
// then just do the math and don't actually write anything. This lets us
// use one function to handle both the measurement pass and the rendering
// pass.
//
LPSTR AddToURLFileContents(LPSTR pszFile, LPCSTR psz, BOOL fWrite)
{
int cch = lstrlenA(psz);
if (fWrite) {
memcpy(pszFile, psz, cch);
}
pszFile += cch;
return pszFile;
}
LPSTR AddURLFileSection(LPSTR pszFile, LPCSTR pszSuffix, LPCSTR pszUrl, BOOL fWrite)
{
pszFile = AddToURLFileContents(pszFile, "[InternetShortcut", fWrite);
pszFile = AddToURLFileContents(pszFile, pszSuffix, fWrite);
pszFile = AddToURLFileContents(pszFile, "]\r\nURL=", fWrite);
pszFile = AddToURLFileContents(pszFile, pszUrl, fWrite);
pszFile = AddToURLFileContents(pszFile, "\r\n", fWrite);
return pszFile;
}
//
// The file consists of an [InternetShortcut] section, followed if
// necessary by [InternetShortcut.A] and [InternetShortcut.W].
//
LPSTR AddURLFileContents(LPSTR pszFile, LPCSTR pszUrl, LPCSTR pszUTF7, BOOL fWrite)
{
pszFile = AddURLFileSection(pszFile, "", pszUrl, fWrite);
if (pszUTF7) {
pszFile = AddURLFileSection(pszFile, ".A", pszUrl, fWrite);
pszFile = AddURLFileSection(pszFile, ".W", pszUTF7, fWrite);
}
return pszFile;
}
//
// Returns number of bytes in file contents (not including trailing NULL),
// or an OLE error code. If ppszOut is NULL, then no contents are returned.
//
HRESULT GenerateURLFileContents(LPCWSTR pwszUrl, LPCSTR pszUrl, LPSTR *ppszOut)
{
HRESULT hr = 0;
if (ppszOut)
*ppszOut = NULL;
if (pwszUrl && pszUrl) {
CStrUTF7 strUTF7; // Assume no UTF7 needed.
if (!Is7BitClean(pwszUrl)) {
//
// The value is not 7-bit clean. Must create a UTF7 version.
//
strUTF7.SetUnicode(pwszUrl);
}
hr = PtrToUlong(AddURLFileContents(NULL, pszUrl, strUTF7, FALSE));
ASSERT(SUCCEEDED(hr));
if (ppszOut) {
*ppszOut = (LPSTR)GlobalAlloc(GMEM_FIXED, hr + 1);
if (*ppszOut) {
LPSTR pszEnd = AddURLFileContents(*ppszOut, pszUrl, strUTF7, TRUE);
*pszEnd = '\0';
} else {
hr = E_OUTOFMEMORY;
}
}
}
//
// Double-check the value we return.
//
if (SUCCEEDED(hr) && ppszOut) {
ASSERT(hr == lstrlenA(*ppszOut));
}
return hr;
}
HRESULT CreateURLFileContentsW(LPCWSTR pwszUrl, LPSTR *ppszOut)
{
CStrIn strUrl(pwszUrl);
return GenerateURLFileContents(pwszUrl, strUrl, ppszOut);
}
HRESULT CreateURLFileContentsA(LPCSTR pszUrl, LPSTR *ppszOut)
{
CStrInW strUrl(pszUrl);
return GenerateURLFileContents(strUrl, pszUrl, ppszOut);
}
DWORD SHGetIniStringUTF7W(LPCWSTR lpSection, LPCWSTR lpKey, LPWSTR lpBuf, DWORD nSize, LPCWSTR lpFile)
{
if (*lpKey == CH_CANBEUNICODEW)
return SHGetIniStringW(lpSection, lpKey+1, lpBuf, nSize, lpFile);
else
return GetPrivateProfileStringWrapW(lpSection, lpKey, L"", lpBuf, nSize, lpFile);
}
BOOL SHSetIniStringUTF7W(LPCWSTR lpSection, LPCWSTR lpKey, LPCWSTR lpString, LPCWSTR lpFile)
{
if (*lpKey == CH_CANBEUNICODEW)
return SHSetIniStringW(lpSection, lpKey+1, lpString, lpFile);
else
return WritePrivateProfileStringWrapW(lpSection, lpKey, lpString, lpFile);
}