Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

545 lines
19 KiB

  1. //+---------------------------------------------------------------------------
  2. //
  3. // File: inistr.cpp
  4. //
  5. // Contents: SHGet/SetIniStringW implementations, which save strings into
  6. // INI files in a manner that survives the round trip to disk.
  7. //
  8. //----------------------------------------------------------------------------
  9. #include "priv.h"
  10. #define _SHELL32_
  11. #define _SHDOCVW_
  12. #include <platform.h>
  13. #include <mlang.h>
  14. #include "cstrinout.h"
  15. //
  16. // Do this in every wrapper function that has an output parameter.
  17. // It raises assertion failures on the main code path so that
  18. // the same assertions are raised on NT and 95. The CStrOut class
  19. // doesn't like it when you say that an output buffer is NULL yet
  20. // has nonzero length. Without this macro, the bug would go undetected
  21. // on NT and appear only on Win95.
  22. //
  23. #define VALIDATE_OUTBUF(s, cch) ASSERT((s) != NULL || (cch) == 0)
  24. //----------------------------------------------------------------------------
  25. //
  26. // The basic problem is that INI files are ANSI-only, so any UNICODE
  27. // string you put into it won't round-trip.
  28. //
  29. // So the solution is to record UNICODE strings in UTF7. Why UTF7?
  30. // Because we can't use UTF8, since XxxPrivateProfileStringW will try
  31. // to convert the 8-bit values to/from UNICODE and mess them up. Since
  32. // some of the 8-bit values might not even be valid (e.g., a DBCS lead
  33. // byte followed by an illegal trail byte), we cannot assume that the
  34. // string will survive the ANSI -> UNICODE -> ANSI round-trip.
  35. //
  36. // The UTF7 string is stored in a [Section.W] section, under the
  37. // same key name. The original ANSI string is stored in a [Section.A]
  38. // section, again, with the same key name.
  39. //
  40. // (We separate the A/W from the section name with a dot so it is less
  41. // likely that we will accidentally collide with other section names.
  42. //
  43. // We store the original ANSI string twice so we can compare the two
  44. // and see if a downlevel app (e.g., IE4) has changed the [Section]
  45. // version. If so, then we ignore the [Section.W] version since it's stale.
  46. //
  47. // If the original string is already 7-bit clean, then no UTF7 string is
  48. // recorded.
  49. //
  50. BOOL
  51. Is7BitClean(LPCWSTR pwsz)
  52. {
  53. for ( ; *pwsz; pwsz++) {
  54. if ((UINT)*pwsz > 127)
  55. return FALSE;
  56. }
  57. return TRUE;
  58. }
  59. //----------------------------------------------------------------------------
  60. //
  61. // Yet another conversion class -- this one is for creating the
  62. // variants of a section name.
  63. //
  64. // Note! Since INI files are ASCII, section names are necessarily 7-bit
  65. // clean, so we can cheat a lot of stuff.
  66. //
  67. class CStrSectionX : public CConvertStrW
  68. {
  69. public:
  70. CStrSectionX(LPCWSTR pwszSection);
  71. };
  72. //
  73. // We append a dot an an A or W to the section name.
  74. //
  75. #define SECTION_SUFFIX_LEN 2
  76. CStrSectionX::CStrSectionX(LPCWSTR pwszSection)
  77. {
  78. ASSERT(_pwstr == NULL);
  79. if (pwszSection) {
  80. int cch_pwstr;
  81. ASSERT(Is7BitClean(pwszSection));
  82. UINT cwchNeeded = lstrlenW(pwszSection) + SECTION_SUFFIX_LEN + 1;
  83. if (cwchNeeded > ARRAYSIZE(_awch)) {
  84. _pwstr = new WCHAR[cwchNeeded];
  85. cch_pwstr = cwchNeeded;
  86. } else {
  87. _pwstr = _awch;
  88. cch_pwstr = ARRAYSIZE(_awch);
  89. }
  90. if (_pwstr) {
  91. // Build the string initially with ".A" stuck on the end
  92. // It will later get changed to a ".W"
  93. StringCchCopyW(_pwstr, cch_pwstr, pwszSection);
  94. StringCchCatW(_pwstr, cch_pwstr, L".A");
  95. } else {
  96. _pwstr = _awch;
  97. _awch[0] = L'\0';
  98. }
  99. }
  100. }
  101. //----------------------------------------------------------------------------
  102. //
  103. // Mini-class for keeping track of UTF7 strings. These are kept in ANSI
  104. // most of the time since that's what ConvertINetUnicodeToMultiByte uses.
  105. //
  106. // The UTF7 shadow is prefixed by a checksum of the original string, which
  107. // we use on read-back to see if the shadow still corresponds to the
  108. // original string.
  109. //
  110. class CStrUTF7 : public CConvertStr
  111. {
  112. public:
  113. inline CStrUTF7() : CConvertStr(CP_ACP) { };
  114. void SetUnicode(LPCWSTR pwszValue);
  115. };
  116. //
  117. // Note that this can be slow since it happens only when we encounter
  118. // a non-ANSI character.
  119. //
  120. void CStrUTF7::SetUnicode(LPCWSTR pwszValue)
  121. {
  122. int cwchLen = lstrlenW(pwszValue);
  123. HRESULT hres;
  124. DWORD dwMode;
  125. int cwchLenT = cwchLen;
  126. // Save room for terminating NULL. We must convert the NULL separately
  127. // because UTF7 does not translate NULL to NULL.
  128. int cchNeeded = ARRAYSIZE(_ach) - 1;
  129. dwMode = 0;
  130. hres = ConvertINetUnicodeToMultiByte(&dwMode, CP_UTF7, pwszValue,
  131. &cwchLenT, _ach,
  132. &cchNeeded);
  133. if (SUCCEEDED(hres)) {
  134. ASSERT(cchNeeded + 1 <= ARRAYSIZE(_ach));
  135. _pstr = _ach;
  136. } else {
  137. _pstr = new CHAR[cchNeeded + 1];
  138. if (!_pstr)
  139. return; // No string - tough
  140. cwchLenT = cwchLen;
  141. dwMode = 0;
  142. hres = ConvertINetUnicodeToMultiByte(&dwMode, CP_UTF7, pwszValue,
  143. &cwchLenT, _pstr,
  144. &cchNeeded);
  145. if (FAILED(hres)) { // Couldn't convert - tough
  146. Free();
  147. return;
  148. }
  149. }
  150. // Terminate explicitly since UTF7 doesn't.
  151. _pstr[cchNeeded] = '\0';
  152. }
  153. //
  154. // pwszSection = section name into which to write pwszValue (UNICODE)
  155. // pwszSectionA = section name into which to write ANSI shadow
  156. // pwszKey = key name for both pwszValue and strUTF7
  157. // pwszFileName = file name
  158. //
  159. // pwszSectionA can be NULL if a low-memory condition was encountered.
  160. //
  161. // strUTF7 can be NULL, meaning that the shadows should be deleted.
  162. //
  163. // Write pwszSection first, followed by pwszSectionA, then pwszSectionW.
  164. // This ensures that the backwards-compatibility string comes first in
  165. // the file, in case there are apps that assume such.
  166. //
  167. // pwszSectionW is computed from pwszSectionA by changing the last "A"
  168. // to a "W". pwszSecionW gets the UTF7-encoded unicode string.
  169. // strUTF7 might be NULL, meaning that we should delete the shadow strings.
  170. //
  171. BOOL WritePrivateProfileStringMultiW(LPCWSTR pwszSection, LPCWSTR pwszValue,
  172. LPWSTR pwszSectionA, CStrUTF7& strUTF7,
  173. LPCWSTR pwszKey, LPCWSTR pwszFileName)
  174. {
  175. BOOL fRc = WritePrivateProfileStringW(pwszSection, pwszKey, pwszValue, pwszFileName);
  176. if (pwszSectionA) {
  177. //
  178. // Write the [Section.A] key, or delete it if there is no UTF7.
  179. //
  180. WritePrivateProfileStringW(pwszSectionA, pwszKey,
  181. strUTF7 ? pwszValue : NULL, pwszFileName);
  182. //
  183. // Now change pwszSectionA to pwszSectionW so we can write out
  184. // the UTF7 encoding.
  185. //
  186. pwszSectionA[lstrlenW(pwszSectionA) - 1] = TEXT('W');
  187. CStrInW strUTF7W(strUTF7);
  188. if (strUTF7W.strlen())
  189. {
  190. // This really writes [Section.W]
  191. WritePrivateProfileStringW(pwszSectionA, pwszKey, strUTF7W, pwszFileName);
  192. }
  193. }
  194. return fRc;
  195. }
  196. BOOL WINAPI
  197. SHSetIniStringW(LPCWSTR pwszSection, LPCWSTR pwszKey, LPCWSTR pwszValue, LPCWSTR pwszFileName)
  198. {
  199. // We have no way of encoding these two, so they had better by 7-bit clean
  200. // We also do not support "delete entire section"
  201. AssertMsg(pwszSection != NULL,
  202. TEXT("SHSetIniStringW: Section name cannot be NULL; bug in caller"));
  203. AssertMsg(Is7BitClean(pwszSection),
  204. TEXT("SHSetIniStringW: Section name not 7-bit clean; bug in caller"));
  205. AssertMsg(pwszKey != NULL,
  206. TEXT("SHSetIniStringW: Key name cannot be NULL; bug in caller"));
  207. AssertMsg(!pwszKey || Is7BitClean(pwszKey),
  208. TEXT("SHSetIniStringW: Key name not 7-bit clean; bug in caller"));
  209. CStrSectionX strSectionX(pwszSection);
  210. CStrUTF7 strUTF7; // Assume no UTF7 needed.
  211. if (strSectionX && pwszKey && pwszValue && !Is7BitClean(pwszValue)) {
  212. //
  213. // The value is not 7-bit clean. Must create a UTF7 version.
  214. //
  215. strUTF7.SetUnicode(pwszValue);
  216. }
  217. return WritePrivateProfileStringMultiW(pwszSection, pwszValue,
  218. strSectionX, strUTF7,
  219. pwszKey, pwszFileName);
  220. }
  221. //
  222. // Keep calling GetPrivateProfileString with bigger and bigger buffers
  223. // until we get the entire string. Start with MAX_PATH, since that's
  224. // usually plenty big enough.
  225. //
  226. // The returned buffer must be freed with LocalFree, not delete[].
  227. //
  228. LPVOID GetEntirePrivateProfileStringAorW(
  229. LPCVOID pszSection,
  230. LPCVOID pszKey,
  231. LPCVOID pszFileName,
  232. BOOL fUnicode)
  233. {
  234. int CharSize = fUnicode ? sizeof(WCHAR) : sizeof(CHAR);
  235. UINT cchResult = MAX_PATH;
  236. LPVOID pszResult = LocalAlloc(LMEM_FIXED, cchResult * CharSize);
  237. LPVOID pszFree = pszResult;
  238. while (pszResult) {
  239. UINT cchRc;
  240. if (fUnicode)
  241. cchRc = GetPrivateProfileStringW((LPCWSTR)pszSection,
  242. (LPCWSTR)pszKey,
  243. L"",
  244. (LPWSTR)pszResult, cchResult,
  245. (LPCWSTR)pszFileName);
  246. else
  247. cchRc = GetPrivateProfileStringA((LPCSTR)pszSection,
  248. (LPCSTR)pszKey,
  249. "",
  250. (LPSTR)pszResult, cchResult,
  251. (LPCSTR)pszFileName);
  252. if (cchRc < cchResult - 1)
  253. return pszResult;
  254. // Buffer too small - iterate
  255. cchResult *= 2;
  256. LPVOID pszNew = LocalReAlloc(pszResult, cchResult * CharSize, LMEM_MOVEABLE);
  257. pszFree = pszResult;
  258. pszResult = pszNew;
  259. }
  260. //
  261. // Memory allocation failed; free pszFree while we still can.
  262. //
  263. if (pszFree)
  264. LocalFree(pszFree);
  265. return NULL;
  266. }
  267. DWORD GetPrivateProfileStringMultiW(LPCWSTR pwszSection, LPCWSTR pwszKey,
  268. LPWSTR pwszSectionA,
  269. LPWSTR pwszReturnedString, DWORD cchSize,
  270. LPCWSTR pwszFileName)
  271. {
  272. LPWSTR pwszValue = NULL;
  273. LPWSTR pwszValueA = NULL;
  274. LPWSTR pwszUTF7 = NULL;
  275. DWORD dwRc;
  276. pwszValue = (LPWSTR)GetEntirePrivateProfileStringAorW(
  277. pwszSection, pwszKey,
  278. pwszFileName, TRUE);
  279. if (pwszValue) {
  280. //
  281. // If the value is an empty string, then don't waste your
  282. // time trying to get the UNICODE version - the UNICODE version
  283. // of the empty string is the empty string.
  284. //
  285. // Otherwise, get the ANSI shadow hidden in [Section.A]
  286. // and see if it matches. If not, then the file was edited
  287. // by a downlevel app and we should just use pwszValue after all.
  288. if (pwszValue[0] &&
  289. (pwszValueA = (LPWSTR)GetEntirePrivateProfileStringAorW(
  290. pwszSectionA, pwszKey,
  291. pwszFileName, TRUE)) != NULL &&
  292. lstrcmpW(pwszValue, pwszValueA) == 0) {
  293. // our shadow is still good - run with it
  294. // Change [Section.A] to [Section.W]
  295. pwszSectionA[lstrlenW(pwszSectionA) - 1] = TEXT('W');
  296. pwszUTF7 = (LPWSTR)GetEntirePrivateProfileStringAorW(
  297. pwszSectionA, pwszKey,
  298. pwszFileName, TRUE);
  299. CStrIn strUTF7(pwszUTF7);
  300. dwRc = 0; // Assume something goes wrong
  301. if (strUTF7.strlen()) {
  302. dwRc = SHAnsiToUnicodeCP(CP_UTF7, strUTF7, pwszReturnedString, cchSize);
  303. }
  304. if (dwRc == 0) {
  305. // Problem converting to UTF7 - just use the ANSI version
  306. dwRc = SHUnicodeToUnicode(pwszValue, pwszReturnedString, cchSize);
  307. }
  308. } else {
  309. // String was empty or couldn't get [Section.A] shadow or
  310. // shadow doesn't match. Just use the regular string.
  311. dwRc = SHUnicodeToUnicode(pwszValue, pwszReturnedString, cchSize);
  312. }
  313. // The SHXxxToYyy functions include the terminating zero,
  314. // which we want to exclude.
  315. if (dwRc > 0)
  316. dwRc--;
  317. } else {
  318. // Fatal error reading values from file; just use the boring API
  319. dwRc = GetPrivateProfileStringW(pwszSection,
  320. pwszKey,
  321. L"",
  322. pwszReturnedString, cchSize,
  323. pwszFileName);
  324. }
  325. if (pwszValue)
  326. LocalFree(pwszValue);
  327. if (pwszValueA)
  328. LocalFree(pwszValueA);
  329. if (pwszUTF7)
  330. LocalFree(pwszUTF7);
  331. return dwRc;
  332. }
  333. DWORD WINAPI SHGetIniStringW(LPCWSTR pwszSection, LPCWSTR pwszKey, LPWSTR pwszReturnedString, DWORD cchSize, LPCWSTR pwszFileName)
  334. {
  335. VALIDATE_OUTBUF(pwszReturnedString, cchSize);
  336. // We have no way of encoding these two, so they had better by 7-bit clean
  337. // We also do not support "get all section names" or "get entire section".
  338. AssertMsg(pwszSection != NULL,
  339. TEXT("SHGetIniStringW: Section name cannot be NULL; bug in caller"));
  340. AssertMsg(Is7BitClean(pwszSection),
  341. TEXT("SHGetIniStringW: Section name not 7-bit clean; bug in caller"));
  342. AssertMsg(pwszKey != NULL,
  343. TEXT("SHGetIniStringW: Key name cannot be NULL; bug in caller"));
  344. AssertMsg(Is7BitClean(pwszKey),
  345. TEXT("SHGetIniStringW: Key name not 7-bit clean; bug in caller"));
  346. CStrSectionX strSectionX(pwszSection);
  347. if (!strSectionX[0])
  348. {
  349. return 0; // out of memory
  350. }
  351. return GetPrivateProfileStringMultiW(pwszSection, pwszKey,
  352. strSectionX,
  353. pwszReturnedString, cchSize,
  354. pwszFileName);
  355. }
  356. //+---------------------------------------------------------------------------
  357. //
  358. // CreateURLFileContents
  359. //
  360. // shdocvw.dll and url.dll need to create in-memory images
  361. // of URL files, so this helper function does all the grunky work so they
  362. // can remain insulated from the way we encode Unicode strings into INI files.
  363. // The resulting memory should be freed via GlobalFree().
  364. //
  365. // Writes a string into the URL file. If fWrite is FALSE, then
  366. // then just do the math and don't actually write anything. This lets us
  367. // use one function to handle both the measurement pass and the rendering
  368. // pass.
  369. //
  370. LPSTR AddToURLFileContents(LPSTR pszFile, LPCSTR psz, BOOL fWrite)
  371. {
  372. int cch = lstrlenA(psz);
  373. if (fWrite) {
  374. memcpy(pszFile, psz, cch * sizeof(char));
  375. }
  376. pszFile += cch;
  377. return pszFile;
  378. }
  379. LPSTR AddURLFileSection(LPSTR pszFile, LPCSTR pszSuffix, LPCSTR pszUrl, BOOL fWrite)
  380. {
  381. pszFile = AddToURLFileContents(pszFile, "[InternetShortcut", fWrite);
  382. pszFile = AddToURLFileContents(pszFile, pszSuffix, fWrite);
  383. pszFile = AddToURLFileContents(pszFile, "]\r\nURL=", fWrite);
  384. pszFile = AddToURLFileContents(pszFile, pszUrl, fWrite);
  385. pszFile = AddToURLFileContents(pszFile, "\r\n", fWrite);
  386. return pszFile;
  387. }
  388. //
  389. // The file consists of an [InternetShortcut] section, followed if
  390. // necessary by [InternetShortcut.A] and [InternetShortcut.W].
  391. //
  392. LPSTR AddURLFileContents(LPSTR pszFile, LPCSTR pszUrl, LPCSTR pszUTF7, BOOL fWrite)
  393. {
  394. pszFile = AddURLFileSection(pszFile, "", pszUrl, fWrite);
  395. if (pszUTF7) {
  396. pszFile = AddURLFileSection(pszFile, ".A", pszUrl, fWrite);
  397. pszFile = AddURLFileSection(pszFile, ".W", pszUTF7, fWrite);
  398. }
  399. return pszFile;
  400. }
  401. //
  402. // Returns number of bytes in file contents (not including trailing NULL),
  403. // or an OLE error code. If ppszOut is NULL, then no contents are returned.
  404. //
  405. HRESULT GenerateURLFileContents(LPCWSTR pwszUrl, LPCSTR pszUrl, LPSTR *ppszOut)
  406. {
  407. HRESULT hr = 0;
  408. if (ppszOut)
  409. *ppszOut = NULL;
  410. if (pwszUrl && pszUrl) {
  411. CStrUTF7 strUTF7; // Assume no UTF7 needed.
  412. if (!Is7BitClean(pwszUrl)) {
  413. //
  414. // The value is not 7-bit clean. Must create a UTF7 version.
  415. //
  416. strUTF7.SetUnicode(pwszUrl);
  417. }
  418. hr = PtrToUlong(AddURLFileContents(NULL, pszUrl, strUTF7, FALSE));
  419. ASSERT(SUCCEEDED(hr));
  420. if (ppszOut) {
  421. *ppszOut = (LPSTR)GlobalAlloc(GMEM_FIXED, hr + 1);
  422. if (*ppszOut) {
  423. LPSTR pszEnd = AddURLFileContents(*ppszOut, pszUrl, strUTF7, TRUE);
  424. *pszEnd = '\0';
  425. } else {
  426. hr = E_OUTOFMEMORY;
  427. }
  428. }
  429. }
  430. //
  431. // Double-check the value we return.
  432. //
  433. if (SUCCEEDED(hr) && ppszOut) {
  434. ASSERT(hr == lstrlenA(*ppszOut));
  435. }
  436. return hr;
  437. }
  438. HRESULT CreateURLFileContentsW(LPCWSTR pwszUrl, LPSTR *ppszOut)
  439. {
  440. if (pwszUrl)
  441. {
  442. CStrIn strUrl(pwszUrl);
  443. if (strUrl.strlen())
  444. {
  445. return GenerateURLFileContents(pwszUrl, strUrl, ppszOut);
  446. }
  447. return E_OUTOFMEMORY;
  448. }
  449. return E_INVALIDARG;
  450. }
  451. HRESULT CreateURLFileContentsA(LPCSTR pszUrl, LPSTR *ppszOut)
  452. {
  453. if (pszUrl)
  454. {
  455. CStrInW strUrl(pszUrl);
  456. if (strUrl.strlen())
  457. {
  458. return GenerateURLFileContents(strUrl, pszUrl, ppszOut);
  459. }
  460. return E_OUTOFMEMORY;
  461. }
  462. return E_INVALIDARG;
  463. }
  464. DWORD SHGetIniStringUTF7W(LPCWSTR lpSection, LPCWSTR lpKey, LPWSTR lpBuf, DWORD nSize, LPCWSTR lpFile)
  465. {
  466. if (*lpKey == CH_CANBEUNICODEW)
  467. return SHGetIniStringW(lpSection, lpKey+1, lpBuf, nSize, lpFile);
  468. else
  469. return GetPrivateProfileStringW(lpSection, lpKey, L"", lpBuf, nSize, lpFile);
  470. }
  471. BOOL SHSetIniStringUTF7W(LPCWSTR lpSection, LPCWSTR lpKey, LPCWSTR lpString, LPCWSTR lpFile)
  472. {
  473. if (*lpKey == CH_CANBEUNICODEW)
  474. return SHSetIniStringW(lpSection, lpKey+1, lpString, lpFile);
  475. else
  476. return WritePrivateProfileStringW(lpSection, lpKey, lpString, lpFile);
  477. }