//+--------------------------------------------------------------------------- // // 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 #include // // 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); }