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.

649 lines
20 KiB

  1. #include <windows.h>
  2. #include <shlwapi.h>
  3. #include <tchar.h>
  4. #include <winhttp.h>
  5. #include "iucommon.h"
  6. #include "logging.h"
  7. #include "download.h"
  8. #include "dlutil.h"
  9. #include "malloc.h"
  10. #include "wusafefn.h"
  11. ///////////////////////////////////////////////////////////////////////////////
  12. //
  13. typedef BOOL (WINAPI *pfn_OpenProcessToken)(HANDLE, DWORD, PHANDLE);
  14. typedef BOOL (WINAPI *pfn_OpenThreadToken)(HANDLE, DWORD, BOOL, PHANDLE);
  15. typedef BOOL (WINAPI *pfn_SetThreadToken)(PHANDLE, HANDLE);
  16. typedef BOOL (WINAPI *pfn_GetTokenInformation)(HANDLE, TOKEN_INFORMATION_CLASS, LPVOID, DWORD, PDWORD);
  17. typedef BOOL (WINAPI *pfn_IsValidSid)(PSID);
  18. typedef BOOL (WINAPI *pfn_AllocateAndInitializeSid)(PSID_IDENTIFIER_AUTHORITY, BYTE, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, PSID);
  19. typedef BOOL (WINAPI *pfn_EqualSid)(PSID, PSID);
  20. typedef PVOID (WINAPI *pfn_FreeSid)(PSID);
  21. const TCHAR c_szRPWU[] = _T("Software\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate");
  22. const TCHAR c_szRVTransport[] = _T("DownloadTransport");
  23. const TCHAR c_szAdvapi32[] = _T("advapi32.dll");
  24. // ***************************************************************************
  25. static
  26. BOOL AmIPrivileged(void)
  27. {
  28. LOG_Block("AmINotPrivileged()");
  29. pfn_AllocateAndInitializeSid pfnAllocateAndInitializeSid = NULL;
  30. pfn_GetTokenInformation pfnGetTokenInformation = NULL;
  31. pfn_OpenProcessToken pfnOpenProcessToken = NULL;
  32. pfn_OpenThreadToken pfnOpenThreadToken = NULL;
  33. pfn_SetThreadToken pfnSetThreadToken = NULL;
  34. pfn_IsValidSid pfnIsValidSid = NULL;
  35. pfn_EqualSid pfnEqualSid = NULL;
  36. pfn_FreeSid pfnFreeSid = NULL;
  37. HMODULE hmod = NULL;
  38. SID_IDENTIFIER_AUTHORITY siaNT = SECURITY_NT_AUTHORITY;
  39. TOKEN_USER *ptu = NULL;
  40. HANDLE hToken = NULL, hTokenImp = NULL;
  41. DWORD cb, cbGot, i;
  42. PSID psid = NULL;
  43. BOOL fRet = FALSE;
  44. DWORD rgRIDs[3] = { SECURITY_LOCAL_SYSTEM_RID,
  45. SECURITY_LOCAL_SERVICE_RID,
  46. SECURITY_NETWORK_SERVICE_RID };
  47. hmod = LoadLibraryFromSystemDir(c_szAdvapi32);
  48. if (hmod == NULL)
  49. goto done;
  50. pfnAllocateAndInitializeSid = (pfn_AllocateAndInitializeSid)GetProcAddress(hmod, "AllocateAndInitializeSid");
  51. pfnGetTokenInformation = (pfn_GetTokenInformation)GetProcAddress(hmod, "GetTokenInformation");
  52. pfnOpenProcessToken = (pfn_OpenProcessToken)GetProcAddress(hmod, "OpenProcessToken");
  53. pfnOpenThreadToken = (pfn_OpenThreadToken)GetProcAddress(hmod, "OpenThreadToken");
  54. pfnSetThreadToken = (pfn_SetThreadToken)GetProcAddress(hmod, "SetThreadToken");
  55. pfnIsValidSid = (pfn_IsValidSid)GetProcAddress(hmod, "IsValidSid");
  56. pfnEqualSid = (pfn_EqualSid)GetProcAddress(hmod, "EqualSid");
  57. pfnFreeSid = (pfn_FreeSid)GetProcAddress(hmod, "FreeSid");
  58. if (pfnAllocateAndInitializeSid == NULL ||
  59. pfnGetTokenInformation == NULL ||
  60. pfnOpenProcessToken == NULL ||
  61. pfnOpenThreadToken == NULL ||
  62. pfnSetThreadToken == NULL ||
  63. pfnIsValidSid == NULL ||
  64. pfnEqualSid == NULL ||
  65. pfnFreeSid == NULL)
  66. {
  67. SetLastError(ERROR_PROC_NOT_FOUND);
  68. goto done;
  69. }
  70. // need the process token
  71. fRet = (*pfnOpenProcessToken)(GetCurrentProcess(), TOKEN_READ, &hToken);
  72. if (fRet == FALSE)
  73. {
  74. if (GetLastError() == ERROR_ACCESS_DENIED)
  75. {
  76. fRet = (*pfnOpenThreadToken)(GetCurrentThread(),
  77. TOKEN_READ | TOKEN_IMPERSONATE,
  78. TRUE, &hTokenImp);
  79. if (fRet == FALSE)
  80. goto done;
  81. fRet = (*pfnSetThreadToken)(NULL, NULL);
  82. fRet = (*pfnOpenProcessToken)(GetCurrentProcess(), TOKEN_READ,
  83. &hToken);
  84. if ((*pfnSetThreadToken)(NULL, hTokenImp) == FALSE)
  85. fRet = FALSE;
  86. }
  87. if (fRet == FALSE)
  88. goto done;
  89. }
  90. // need the SID from the token
  91. fRet = (*pfnGetTokenInformation)(hToken, TokenUser, NULL, 0, &cb);
  92. if (fRet != FALSE && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
  93. {
  94. fRet = FALSE;
  95. goto done;
  96. }
  97. ptu = (TOKEN_USER *)HeapAlloc(GetProcessHeap(), 0, cb);
  98. if (ptu == NULL)
  99. {
  100. SetLastError(ERROR_OUTOFMEMORY);
  101. fRet = FALSE;
  102. goto done;
  103. }
  104. fRet = (*pfnGetTokenInformation)(hToken, TokenUser, (LPVOID)ptu, cb,
  105. &cbGot);
  106. if (fRet == FALSE)
  107. goto done;
  108. fRet = (*pfnIsValidSid)(ptu->User.Sid);
  109. if (fRet == FALSE)
  110. goto done;
  111. // loop thru & check against the SIDs we are interested in
  112. for (i = 0; i < 3; i++)
  113. {
  114. fRet = (*pfnAllocateAndInitializeSid)(&siaNT, 1, rgRIDs[i], 0, 0, 0,
  115. 0, 0, 0, 0, &psid);
  116. if (fRet == FALSE)
  117. goto done;
  118. fRet = (*pfnIsValidSid)(psid);
  119. if (fRet == FALSE)
  120. goto done;
  121. // if we get a SID match, then return TRUE
  122. fRet = (*pfnEqualSid)(psid, ptu->User.Sid);
  123. (*pfnFreeSid)(psid);
  124. psid = NULL;
  125. if (fRet)
  126. {
  127. fRet = TRUE;
  128. goto done;
  129. }
  130. }
  131. // only way to get here is to fail all the SID checks above. So we ain't
  132. // privileged. Yeehaw.
  133. fRet = FALSE;
  134. done:
  135. // if we had an impersonation token on the thread, put it back in place.
  136. if (ptu != NULL)
  137. HeapFree(GetProcessHeap(), 0, ptu);
  138. if (hToken != NULL)
  139. CloseHandle(hToken);
  140. if (hTokenImp != NULL)
  141. CloseHandle(hTokenImp);
  142. if (psid != NULL && pfnFreeSid != NULL)
  143. (*pfnFreeSid)(psid);
  144. if (hmod != NULL)
  145. FreeLibrary(hmod);
  146. return fRet;
  147. }
  148. #if defined(DEBUG) || defined(DBG)
  149. // **************************************************************************
  150. static
  151. BOOL CheckDebugRegKey(DWORD *pdwAllowed)
  152. {
  153. LOG_Block("CheckDebugRegKey()");
  154. DWORD dw, dwType, dwValue, cb;
  155. HKEY hkey = NULL;
  156. BOOL fRet = FALSE;
  157. // explictly do not initialize *pdwAllowed. We only want it overwritten
  158. // if the reg key is properly set
  159. dw = RegOpenKeyEx(HKEY_LOCAL_MACHINE, c_szRPWU, 0, KEY_READ, &hkey);
  160. if (dw != ERROR_SUCCESS)
  161. goto done;
  162. cb = sizeof(dwValue);
  163. dw = RegQueryValueEx(hkey, c_szRVTransport, 0, &dwType, (LPBYTE)&dwValue,
  164. &cb);
  165. if (dw != ERROR_SUCCESS)
  166. goto done;
  167. // set this to 3 so we'll fall down into the error case below
  168. if (dwType != REG_DWORD)
  169. dwValue = 3;
  170. fRet = TRUE;
  171. switch(dwValue)
  172. {
  173. case 0:
  174. *pdwAllowed = 0;
  175. break;
  176. case 1:
  177. *pdwAllowed = WUDF_ALLOWWINHTTPONLY;
  178. break;
  179. case 2:
  180. *pdwAllowed = WUDF_ALLOWWININETONLY;
  181. break;
  182. default:
  183. LOG_Internet(_T("Bad reg value in DownloadTransport. Ignoring."));
  184. fRet = FALSE;
  185. break;
  186. }
  187. done:
  188. if (hkey != NULL)
  189. RegCloseKey(hkey);
  190. return fRet;
  191. }
  192. #endif
  193. // **************************************************************************
  194. DWORD GetAllowedDownloadTransport(DWORD dwFlagsInitial)
  195. {
  196. DWORD dwFlags = (dwFlagsInitial & WUDF_TRANSPORTMASK);
  197. #if defined(UNICODE)
  198. // don't bother checking if we're local system if we're already using
  199. // wininet
  200. if ((dwFlags & WUDF_ALLOWWININETONLY) == 0)
  201. {
  202. if (AmIPrivileged() == FALSE)
  203. dwFlags = WUDF_ALLOWWININETONLY;
  204. }
  205. #if defined(DEBUG) || defined(DBG)
  206. CheckDebugRegKey(&dwFlags);
  207. #endif // defined(DEBUG) || defined(DBG)
  208. #else // defined(UNICODE)
  209. // only allow wininet on ANSI
  210. dwFlags = WUDF_ALLOWWININETONLY;
  211. #endif // defined(UNICODE)
  212. return (dwFlags | (dwFlagsInitial & ~WUDF_TRANSPORTMASK));
  213. }
  214. ///////////////////////////////////////////////////////////////////////////////
  215. //
  216. // **************************************************************************
  217. static inline
  218. BOOL IsServerFileDifferentWorker(FILETIME &ftServerTime,
  219. DWORD dwServerFileSize, HANDLE hFile)
  220. {
  221. LOG_Block("IsServerFileNewerWorker()");
  222. FILETIME ftCreateTime;
  223. DWORD cbLocalFile;
  224. // By default, always return TRUE so we can download a new file..
  225. BOOL fRet = TRUE;
  226. // if we don't have a valid file handle, just return TRUE to download a
  227. // new copy
  228. if (hFile == INVALID_HANDLE_VALUE)
  229. goto done;
  230. cbLocalFile = GetFileSize(hFile, NULL);
  231. LOG_Internet(_T("IsServerFileNewer: Local size: %d. Remote size: %d"),
  232. cbLocalFile, dwServerFileSize);
  233. // if the sizes are not equal, then return TRUE
  234. if (cbLocalFile != dwServerFileSize)
  235. goto done;
  236. if (GetFileTime(hFile, &ftCreateTime, NULL, NULL))
  237. {
  238. LOG_Internet(_T("IsServerFileNewer: Local time: %x%0x. Remote time: %x%0x."),
  239. ftCreateTime.dwHighDateTime, ftCreateTime.dwLowDateTime,
  240. ftServerTime.dwHighDateTime, ftServerTime.dwLowDateTime);
  241. // if the local file has a different timestamp, then return TRUE.
  242. fRet = (CompareFileTime(&ftCreateTime, &ftServerTime) != 0);
  243. }
  244. done:
  245. return fRet;
  246. }
  247. // **************************************************************************
  248. BOOL IsServerFileDifferentW(FILETIME &ftServerTime, DWORD dwServerFileSize,
  249. LPCWSTR wszLocalFile)
  250. {
  251. LOG_Block("IsServerFileDifferentW()");
  252. HANDLE hFile = INVALID_HANDLE_VALUE;
  253. BOOL fRet = TRUE;
  254. // if we have an error opening the file, just return TRUE to download a
  255. // new copy
  256. hFile = CreateFileW(wszLocalFile, GENERIC_READ, FILE_SHARE_READ, NULL,
  257. OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  258. if (hFile == INVALID_HANDLE_VALUE)
  259. {
  260. LOG_Internet(_T("IsServerFileDifferent: %ls does not exist."), wszLocalFile);
  261. return TRUE;
  262. }
  263. else
  264. {
  265. fRet = IsServerFileDifferentWorker(ftServerTime, dwServerFileSize, hFile);
  266. CloseHandle(hFile);
  267. return fRet;
  268. }
  269. }
  270. // **************************************************************************
  271. BOOL IsServerFileDifferentA(FILETIME &ftServerTime, DWORD dwServerFileSize,
  272. LPCSTR szLocalFile)
  273. {
  274. LOG_Block("IsServerFileDifferentA()");
  275. HANDLE hFile = INVALID_HANDLE_VALUE;
  276. // if we have an error opening the file, just return TRUE to download a
  277. // new copy
  278. hFile = CreateFileA(szLocalFile, GENERIC_READ, FILE_SHARE_READ, NULL,
  279. OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  280. if (hFile == INVALID_HANDLE_VALUE)
  281. {
  282. LOG_Internet(_T("IsServerFileDifferent: %s does not exist."), szLocalFile);
  283. return TRUE;
  284. }
  285. else
  286. {
  287. BOOL fRet;
  288. fRet = IsServerFileDifferentWorker(ftServerTime, dwServerFileSize, hFile);
  289. CloseHandle(hFile);
  290. return fRet;
  291. }
  292. }
  293. // **************************************************************************
  294. // helper function to handle quit events
  295. //
  296. // return TRUE if okay to continue
  297. // return FALSE if we should quit now!
  298. BOOL HandleEvents(HANDLE *phEvents, UINT nEventCount)
  299. {
  300. LOG_Block("HandleEvents()");
  301. DWORD dwWait;
  302. // is there any events to handle?
  303. if (phEvents == NULL || nEventCount == 0)
  304. return TRUE;
  305. // we only want to check the signaled status, so don't bother waiting
  306. dwWait = WaitForMultipleObjects(nEventCount, phEvents, FALSE, 0);
  307. if (dwWait == WAIT_TIMEOUT)
  308. {
  309. return TRUE;
  310. }
  311. else
  312. {
  313. LOG_Internet(_T("HandleEvents: A quit event was signaled. Aborting..."));
  314. return FALSE;
  315. }
  316. }
  317. ///////////////////////////////////////////////////////////////////////////////
  318. //
  319. // **************************************************************************
  320. HRESULT PerformDownloadToFile(pfn_ReadDataFromSite pfnRead,
  321. HINTERNET hRequest,
  322. HANDLE hFile, DWORD cbFile,
  323. DWORD cbBuffer,
  324. HANDLE *rghEvents, DWORD cEvents,
  325. PFNDownloadCallback fpnCallback, LPVOID pCallbackData,
  326. DWORD *pcbDownloaded)
  327. {
  328. LOG_Block("PerformDownloadToFile()");
  329. HRESULT hr = S_OK;
  330. PBYTE pbBuffer = NULL;
  331. DWORD cbDownloaded = 0, cbRead, cbWritten;
  332. LONG lCallbackRequest = 0;
  333. pbBuffer = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbBuffer);
  334. if (pbBuffer == NULL)
  335. {
  336. hr = E_OUTOFMEMORY;
  337. LOG_ErrorMsg(hr);
  338. goto done;
  339. }
  340. // Download the File
  341. for(;;)
  342. {
  343. if ((*pfnRead)(hRequest, pbBuffer, cbBuffer, &cbRead) == FALSE)
  344. {
  345. hr = HRESULT_FROM_WIN32(GetLastError());
  346. if (FAILED(hr))
  347. {
  348. LOG_ErrorMsg(hr);
  349. goto done;
  350. }
  351. }
  352. if (cbRead == 0)
  353. {
  354. BYTE bTemp[32];
  355. // Make one final call to WinHttpReadData to commit the file to
  356. // Cache. (the download is not complete otherwise)
  357. (*pfnRead)(hRequest, bTemp, ARRAYSIZE(bTemp), &cbRead);
  358. break;
  359. }
  360. cbDownloaded += cbRead;
  361. if (fpnCallback != NULL)
  362. {
  363. fpnCallback(pCallbackData, DOWNLOAD_STATUS_OK, cbFile, cbRead, NULL,
  364. &lCallbackRequest);
  365. if (lCallbackRequest == 4)
  366. {
  367. // QuitEvent was Signaled.. abort requested. We will do
  368. // another callback and pass the Abort State back
  369. fpnCallback(pCallbackData, DOWNLOAD_STATUS_ABORTED, cbFile, cbRead, NULL, NULL);
  370. hr = E_ABORT; // set return result to abort.
  371. goto done;
  372. }
  373. }
  374. if (WriteFile(hFile, pbBuffer, cbRead, &cbWritten, NULL) == FALSE)
  375. {
  376. hr = HRESULT_FROM_WIN32(GetLastError());
  377. LOG_ErrorMsg(hr);
  378. goto done;
  379. }
  380. if (HandleEvents(rghEvents, cEvents) == FALSE)
  381. {
  382. // we need to quit the download clean up, send abort event and clean up what we've downloaded
  383. if (fpnCallback != NULL)
  384. fpnCallback(pCallbackData, DOWNLOAD_STATUS_ABORTED, cbFile, cbRead, NULL, NULL);
  385. hr = E_ABORT; // set return result to abort.
  386. goto done;
  387. }
  388. }
  389. if (pcbDownloaded != NULL)
  390. *pcbDownloaded = cbDownloaded;
  391. done:
  392. SafeHeapFree(pbBuffer);
  393. return hr;
  394. }
  395. ///////////////////////////////////////////////////////////////////////////////
  396. //
  397. struct MY_OSVERSIONINFOEX
  398. {
  399. OSVERSIONINFOEX osvi;
  400. LCID lcidCompare;
  401. };
  402. static MY_OSVERSIONINFOEX g_myosvi;
  403. static BOOL g_fInit = FALSE;
  404. // **************************************************************************
  405. // Loads the current OS version info if needed, and returns a pointer to
  406. // a cached copy of it.
  407. const OSVERSIONINFOEX* GetOSVersionInfo(void)
  408. {
  409. if (g_fInit == FALSE)
  410. {
  411. OSVERSIONINFOEX* pOSVI = &g_myosvi.osvi;
  412. g_myosvi.osvi.dwOSVersionInfoSize = sizeof(g_myosvi.osvi);
  413. GetVersionEx((OSVERSIONINFO*)&g_myosvi.osvi);
  414. // WinXP-specific stuff
  415. if ((pOSVI->dwMajorVersion > 5) ||
  416. (pOSVI->dwMajorVersion == 5 && pOSVI->dwMinorVersion >= 1))
  417. g_myosvi.lcidCompare = LOCALE_INVARIANT;
  418. else
  419. g_myosvi.lcidCompare = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT);
  420. g_fInit = TRUE;
  421. }
  422. return &g_myosvi.osvi;
  423. }
  424. // **************************************************************************
  425. // String lengths can be -1 if the strings are null-terminated.
  426. int LangNeutralStrCmpNIA(LPCSTR psz1, int cch1, LPCSTR psz2, int cch2)
  427. {
  428. if (g_fInit == FALSE)
  429. GetOSVersionInfo();
  430. int nCompare = CompareStringA(g_myosvi.lcidCompare,
  431. NORM_IGNORECASE,
  432. psz1, cch1,
  433. psz2, cch2);
  434. return (nCompare - 2); // convert from (1, 2, 3) to (-1, 0, 1)
  435. }
  436. // **************************************************************************
  437. // Finds the first instance of pszSearchFor in pszSearchIn, case-insensitive.
  438. // Returns an index into pszSearchIn if found, or -1 if not.
  439. // You can pass -1 for either or both of the lengths.
  440. int LangNeutralStrStrNIA(LPCSTR pszSearchIn, int cchSearchIn,
  441. LPCSTR pszSearchFor, int cchSearchFor)
  442. {
  443. char chLower, chUpper;
  444. if (cchSearchIn == -1)
  445. cchSearchIn = lstrlenA(pszSearchIn);
  446. if (cchSearchFor == -1)
  447. cchSearchFor = lstrlenA(pszSearchFor);
  448. // Note: since this is lang-neutral, we can assume no DBCS search chars
  449. chLower = (char)CharLowerA(MAKEINTRESOURCEA(*pszSearchFor));
  450. chUpper = (char)CharUpperA(MAKEINTRESOURCEA(*pszSearchFor));
  451. // Note: since search-for is lang-neutral, we can ignore any DBCS chars
  452. // in search-in
  453. for (int ichIn = 0; ichIn <= cchSearchIn - cchSearchFor; ichIn++)
  454. {
  455. if (pszSearchIn[ichIn] == chLower || pszSearchIn[ichIn] == chUpper)
  456. {
  457. if (LangNeutralStrCmpNIA(pszSearchIn + ichIn + 1, cchSearchFor - 1,
  458. pszSearchFor + 1, cchSearchFor - 1) == 0)
  459. {
  460. return ichIn;
  461. }
  462. }
  463. }
  464. return -1;
  465. }
  466. // **************************************************************************
  467. // Opens the given file and looks for "<html" (case-insensitive) within the
  468. // first 200 characters. If there are any binary chars before "<html", the
  469. // file is assumed to *not* be HTML.
  470. // Returns S_OK if so, S_FALSE if not, or an error if file couldn't be opened.
  471. HRESULT IsFileHtml(LPCTSTR pszFileName)
  472. {
  473. LOG_Block("IsFileHtml()");
  474. HRESULT hr = S_FALSE;
  475. LPCSTR pszFile;
  476. HANDLE hFile = INVALID_HANDLE_VALUE;
  477. HANDLE hMapping = NULL;
  478. LPVOID pvMem = NULL;
  479. DWORD cbFile;
  480. hFile = CreateFile(pszFileName, GENERIC_READ,
  481. FILE_SHARE_READ | FILE_SHARE_WRITE,
  482. NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
  483. if (hFile == INVALID_HANDLE_VALUE)
  484. {
  485. hr = HRESULT_FROM_WIN32(GetLastError());
  486. LOG_ErrorMsg(hr);
  487. goto done;
  488. }
  489. cbFile = GetFileSize(hFile, NULL);
  490. if (cbFile == 0)
  491. goto done;
  492. // Only examine the 1st 200 bytes
  493. if (cbFile > 200)
  494. cbFile = 200;
  495. hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, cbFile, NULL);
  496. if (hMapping == NULL)
  497. {
  498. hr = HRESULT_FROM_WIN32(GetLastError());
  499. LOG_ErrorMsg(hr);
  500. goto done;
  501. }
  502. pvMem = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, cbFile);
  503. if (pvMem == NULL)
  504. {
  505. hr = HRESULT_FROM_WIN32(GetLastError());
  506. LOG_ErrorMsg(hr);
  507. goto done;
  508. }
  509. pszFile = (LPCSTR)pvMem;
  510. int ichHtml = LangNeutralStrStrNIA(pszFile, cbFile, "<html", 5);
  511. if (ichHtml != -1)
  512. {
  513. // Looks like html...
  514. hr = S_OK;
  515. // Just make sure there aren't any binary chars before the <HTML> tag
  516. for (int ich = 0; ich < ichHtml; ich++)
  517. {
  518. char ch = pszFile[ich];
  519. if (ch < 32 && ch != '\t' && ch != '\r' && ch != '\n')
  520. {
  521. // Found a binary character (before <HTML>)
  522. hr = S_FALSE;
  523. break;
  524. }
  525. }
  526. }
  527. done:
  528. if (pvMem != NULL)
  529. UnmapViewOfFile(pvMem);
  530. if (hMapping != NULL)
  531. CloseHandle(hMapping);
  532. if (hFile != INVALID_HANDLE_VALUE)
  533. CloseHandle(hFile);
  534. return hr;
  535. }