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.

617 lines
23 KiB

  1. /*--------------------------------------------------------------------------*
  2. *
  3. * Microsoft Windows
  4. * Copyright (C) Microsoft Corporation, 1999 - 1999
  5. *
  6. * File: xmlfile.cpp
  7. *
  8. * Contents: Implements extracting console icon from XML file
  9. *
  10. * History: 17-Dec-99 audriusz Created
  11. *
  12. *--------------------------------------------------------------------------*/
  13. #include "stdafx.h"
  14. #include "shlobj.h"
  15. #include "Extract.h"
  16. #include "base64.h"
  17. #include "xmlfile.h"
  18. #include "strings.h"
  19. //---------------------------------------------------------------------------
  20. // static (private) implementation helpers used thru this file
  21. //---------------------------------------------------------------------------
  22. static bool FindStringInData( LPCSTR &pstrSource, int nBytesPerChar, DWORD &dwCharsLeft, LPCSTR pstrKey);
  23. static HRESULT DecodeBase64Fragment( LPCSTR &pstrSource, int nBytesPerChar, DWORD &dwCharsLeft, HGLOBAL& hgResult);
  24. static HRESULT FindAndReadIconData(LPCSTR &pstrSource, int nBytesPerChar, DWORD &dwCharsLeft, LPCSTR strIconName, HGLOBAL& hglobIcon);
  25. static HRESULT LoadIconFromHGlobal(HGLOBAL hData, HICON& hIcon);
  26. static HRESULT ValidateXMLDocument(LPCSTR &pFileData, DWORD &dwLen, int *piBytesPerEnglishChar = NULL);
  27. // following function is a friend of class CXMLPersistableIcon. If renaming, take this into accnt
  28. static HRESULT LoadIconFromXMLData(LPCSTR pFileData, DWORD dwLen, CPersistableIcon &persistableIcon);
  29. /***************************************************************************\
  30. *
  31. * FUNCTION: FindStringInData
  32. *
  33. * PURPOSE: This function locates the string in provided data
  34. * NOTE - it matches first byte only (codepage of UNICODE string is ignored)
  35. *
  36. * PARAMETERS:
  37. * LPCSTR &pstrSource - [in/out] - data to search thru / possition
  38. * of the first char following the found match
  39. * int nBytesPerChar - [in] - width of the character
  40. * ( only the first byte of each character will be examined )
  41. * DWORD &dwCharsLeft - [in/out] - init. data len / data left after matching string
  42. * LPCSTR pstrKey - [in] - substring to search
  43. *
  44. * RETURNS:
  45. * bool - true if succeeded
  46. *
  47. \***************************************************************************/
  48. // Following sample illustrates algorithm used for the search.
  49. // we will try to locate "Console" in the string "Microsoft Management Console"
  50. //------------------------------------------------
  51. // Standard search (a la strstr)
  52. //------------------------------------------------
  53. // 1.
  54. // Microsoft Management Console
  55. // Console <- comparing (fails - move to the next char)
  56. // 2.
  57. // Microsoft Management Console
  58. // Console <- comparing (fails - move to the next char)
  59. // ------------------------ (19 steps skipped)
  60. // 22.
  61. // Console <- comparing (succeeds)
  62. //------------------------------------------------
  63. // More inteligent search
  64. //------------------------------------------------
  65. // 1. ! <- last char in searched seq
  66. // Microsoft Management Console
  67. // Console <- comparing (fails - last char in searched seq is 'o';
  68. // and 'o' is 3rd character from the end in the key;
  69. // so we can advance by 2 chars to match it)
  70. // 2. ! <- matching 'o' to last 'o' in the key
  71. // Microsoft Management Console
  72. // Console <- comparing (fails - last char in searched seq is 't';
  73. // 't' is not in the key
  74. // so we can advance by key length (7 chars) to skip it)
  75. // 3. ! <- pos following the last char in searched seq
  76. // Microsoft Management Console
  77. // Console <- comparing (fails - last char in searched seq is 'e';
  78. // 'e' is last character in the key;
  79. // we still can advance by key length (7 chars) to skip it)
  80. // 4. ! <- pos following the last char in searched seq
  81. // Microsoft Management Console
  82. // Console <- comparing (fails)
  83. // 5. ! <- match point
  84. // Microsoft Management Console
  85. // Console <- comparing (fails)
  86. // 6. ! <- match point
  87. // Microsoft Management Console
  88. // Console <- comparing (succeeds)
  89. //------------------------------------------------
  90. static bool
  91. FindStringInData( LPCSTR &pstrSource, int nBytesPerChar, DWORD &dwCharsLeft, LPCSTR pstrKey)
  92. {
  93. // check parameters
  94. if( (!pstrSource) || (!pstrKey) )
  95. return false;
  96. typedef unsigned short KeyLen_t;
  97. static KeyLen_t nKeyDist[256]; // static - to keep stack small
  98. // calculate the key length
  99. DWORD dwKeyLen = strlen(pstrKey);
  100. // test for empty search key
  101. if (!dwKeyLen)
  102. return true; // we always match empty strings
  103. // test for longer search key than data provided
  104. if (dwKeyLen > dwCharsLeft)
  105. return false; // we'll never find longer substrings than the source
  106. // key length var size is not too big to minimize tho lookup table size
  107. KeyLen_t nKeyLen = (KeyLen_t)dwKeyLen;
  108. // recheck here if the key isn't too long
  109. if ((DWORD)nKeyLen != dwKeyLen) // key len does not fit to our variable -
  110. return false; // we do not deel with such a long keys
  111. // form the table holding minimal character distance from the end of pstrKey
  112. // It is used for increasing search speed:
  113. // When key does not match at current location, [instead of trying one location ahead,]
  114. // algorythm checks the last character in sequence tested with a key (data[keylen-1]).
  115. // now we check how far this character may be from the end of the key - we will have
  116. // distance of all key length in case character is not a part of the key.
  117. // we can safelly advance by that much. Sometimes we'll be positioning the key at whole
  118. // key_len offsets from previous test position, sometimes less - depending on data.
  119. // initialize the table. The distance is keylen value for all characters not existing in the key
  120. for (unsigned i = 0; i < sizeof(nKeyDist)/sizeof(nKeyDist[0]); ++i)
  121. nKeyDist[i] = nKeyLen;
  122. // now set minimal distance for characters in the key.
  123. // Note, that the last character is not included intensionally - to make
  124. // distance to it equal to whole key length
  125. for (i = 0; i < nKeyLen - 1; ++i)
  126. nKeyDist[pstrKey[i]] = nKeyLen - (KeyLen_t)i - 1;
  127. // we are done with initialization. Time for real work.
  128. LPCSTR p = pstrSource; // to speed it up: we use local variables
  129. DWORD dwLeft = dwCharsLeft;
  130. while ( 1 )
  131. {
  132. // set the pointers to start of inspected sequence
  133. LPCSTR ke = pstrKey; // pointer to evaluating key char
  134. LPCSTR pe = p; // pointer to evaluating source char
  135. // try to match all characters in the key
  136. KeyLen_t nToMatch = nKeyLen;
  137. while ( *pe == *ke )
  138. {
  139. --nToMatch;
  140. pe += nBytesPerChar;
  141. ++ke;
  142. // inspect if there still are some chars to match
  143. if (!nToMatch)
  144. {
  145. // we return the possitive answer here
  146. // change the reference parameters accordingly
  147. // (pointing right after the string found)
  148. pstrSource = pe;
  149. dwCharsLeft = dwLeft - nKeyLen;
  150. return true;
  151. }
  152. }
  153. // chLastChar is used as an index
  154. // need to cast the char to unsigned char - else it will
  155. // not work correctly for values over 127
  156. // NTRAID#NTBUG9-185761-2000/09/18 AUDRIUSZ
  157. BYTE chLastChar = p[(nKeyLen - 1) * nBytesPerChar]; // the last char from evaluated source range
  158. // the key couldn't be found at the position we inspected.
  159. // we can advance source pointer as far as we can match
  160. // the position of the last character to any entry in the key
  161. // or whole key length else.
  162. // We have a table built for that
  163. const KeyLen_t nToSkip = nKeyDist[chLastChar];
  164. if ((DWORD)nToSkip + (DWORD)nKeyLen >= dwLeft)
  165. return false; // gone too far... ( couldn't find the match )
  166. p += (nToSkip * nBytesPerChar);
  167. dwLeft -= nToSkip;
  168. }
  169. // we will not get here anyway...
  170. return false;
  171. }
  172. /***************************************************************************\
  173. *
  174. * METHOD: DecodeBase64Fragment
  175. *
  176. * PURPOSE: decodes base64 data fragment pointed by arguments
  177. *
  178. * PARAMETERS:
  179. * LPCSTR &pstrSource - [in/out] - data to decode / possition
  180. * of the first char following the decoded data
  181. * int nBytesPerChar - [in] - width of the character
  182. * ( only the first byte of each character will be examined )
  183. * DWORD &dwCharsLeft - [in/out] - init. data len / data left after skipping converted
  184. * HGLOBAL& hgResult - decoded data
  185. *
  186. * RETURNS:
  187. * HRESULT - result code
  188. *
  189. \***************************************************************************/
  190. static HRESULT
  191. DecodeBase64Fragment( LPCSTR &pstrSource, int nBytesPerChar, DWORD &dwCharsLeft, HGLOBAL& hgResult)
  192. {
  193. HRESULT hrStatus = S_OK;
  194. LPCSTR p = pstrSource;
  195. DWORD dwLeft = dwCharsLeft;
  196. const size_t ICON_ALLOCATION_LEN = 8*1024; // big enough to have 1 allocation in most cases
  197. LPBYTE pDynamicBuffer = NULL;
  198. LPBYTE pConversionBuffer = NULL;
  199. size_t nCharsInDynamicBuffer = 0;
  200. size_t nDynamicBufferCapacity = 0;
  201. HGLOBAL hGlobAlloc = NULL;
  202. ASSERT(hResult == NULL);
  203. static base64_table conv;
  204. // convert until done or end is found
  205. while (1)
  206. {
  207. // standard conversion. converts 4 chars (6bit each) to 3 bytes
  208. BYTE inp[4];
  209. memset(&inp, 0 ,sizeof(inp));
  210. // collect 4 characters for conversion, if possible.
  211. for (int nChars = 0; nChars < 4 && dwLeft && *p != '<' && *p != '='; --dwLeft)
  212. {
  213. BYTE bt = conv.map2six(*p);
  214. p += nBytesPerChar;
  215. if (bt != 0xff)
  216. inp[nChars++] = bt;
  217. }
  218. // if nothing to convert - we are done
  219. if (!nChars)
  220. break;
  221. // make sure we have enough storage for result
  222. if (nChars + nCharsInDynamicBuffer > nDynamicBufferCapacity)
  223. {
  224. // need to extend the dynamic buffer
  225. LPBYTE pnewBuffer = (LPBYTE)realloc(pDynamicBuffer, nDynamicBufferCapacity + ICON_ALLOCATION_LEN);
  226. if (!pnewBuffer)
  227. {
  228. hrStatus = E_OUTOFMEMORY;
  229. goto ON_ERROR;
  230. }
  231. // assign new pointer
  232. pDynamicBuffer = pnewBuffer;
  233. nDynamicBufferCapacity += ICON_ALLOCATION_LEN;
  234. pConversionBuffer = &pDynamicBuffer[nCharsInDynamicBuffer];
  235. }
  236. // decode and put the staff to the memory;
  237. int nCharsPut = conv.decode4(inp, nChars, pConversionBuffer);
  238. // update count & current pointer
  239. nCharsInDynamicBuffer += nCharsPut;
  240. pConversionBuffer += nCharsPut;
  241. }
  242. // allocate the buffer and store the result data
  243. // The same buffer is not reused for conversion, because
  244. // it's assumed to be saffer to load icon from stream, containing only
  245. // as much data as required ( we would have larger buffer, containing some
  246. // uninitialized data at the end if returning buffer used for conversion)
  247. hGlobAlloc = GlobalAlloc(GMEM_MOVEABLE, nCharsInDynamicBuffer);
  248. if (hGlobAlloc == NULL)
  249. {
  250. hrStatus = E_OUTOFMEMORY;
  251. goto ON_ERROR;
  252. }
  253. // if we have characters, copy them to result
  254. if (nCharsInDynamicBuffer)
  255. {
  256. LPVOID pResultStorage = GlobalLock(hGlobAlloc);
  257. if (pResultStorage == NULL)
  258. {
  259. hrStatus = E_OUTOFMEMORY;
  260. goto ON_ERROR;
  261. }
  262. memcpy(pResultStorage, pDynamicBuffer, nCharsInDynamicBuffer);
  263. GlobalUnlock(hGlobAlloc);
  264. }
  265. // assign the memory handle to caller
  266. hgResult = hGlobAlloc;
  267. hGlobAlloc = NULL; // assign null to avoid releasing it
  268. // adjust poiters to start from where we finished the next time
  269. pstrSource = p;
  270. dwCharsLeft = dwLeft;
  271. hrStatus = S_OK;
  272. ON_ERROR: // note: ok result falls thru as well
  273. if (hGlobAlloc)
  274. GlobalFree(hGlobAlloc);
  275. if (pDynamicBuffer)
  276. free(pDynamicBuffer);
  277. return hrStatus;
  278. }
  279. /***************************************************************************\
  280. *
  281. * METHOD: FindAndReadIconData
  282. *
  283. * PURPOSE: Function locates Icon data in the xml file data and loads it into HGLOBAL
  284. *
  285. * PARAMETERS:
  286. * LPCSTR &pstrSource - [in/out] - data to look thru / possition
  287. * of the first char following the decoded icon data
  288. * int nBytesPerChar - [in] - width of the character
  289. * ( only the first byte of each character will be examined )
  290. * DWORD &dwCharsLeft - [in/out] - init. data len / data left after skipping decoded
  291. * LPCSTR strIconName - [in] name of Icon to locate
  292. * - NOTE: it assumes icon data to be a base64-encoded stream, saved
  293. * as contents of XML element, having IconName as its attribute
  294. * HGLOBAL& hglobIcon - [out] - memory block containing icon data
  295. *
  296. * RETURNS:
  297. * HRESULT - result code
  298. *
  299. \***************************************************************************/
  300. static HRESULT FindAndReadIconData(LPCSTR &pstrSource, int nBytesPerChar, DWORD &dwCharsLeft, LPCSTR strIconName, HGLOBAL& hglobIcon)
  301. {
  302. ASSERT(hglobIcon == NULL); // we do not free data here, pass null handler!
  303. // make local vars for efficiency
  304. DWORD dwLen = dwCharsLeft;
  305. LPCSTR pstrData = pstrSource;
  306. // locate the string with the name of icon (assume it's unique enough)
  307. const bool bIconFound = FindStringInData( pstrData, nBytesPerChar, dwLen, strIconName);
  308. if (!bIconFound)
  309. return E_FAIL;
  310. // now locate the end of tag '>' ( start of the contents )
  311. const bool bStartFound = FindStringInData( pstrData, nBytesPerChar, dwLen, ">" );
  312. if (!bStartFound)
  313. return E_FAIL;
  314. HRESULT hr = DecodeBase64Fragment( pstrData, nBytesPerChar, dwLen, hglobIcon);
  315. if (FAILED(hr))
  316. return hr;
  317. // update pointers to start from where we finished the next time
  318. dwCharsLeft = dwLen;
  319. pstrSource = pstrData;
  320. return S_OK;
  321. }
  322. /***************************************************************************\
  323. *
  324. * METHOD: LoadIconFromHGlobal
  325. *
  326. * PURPOSE: Function extracts HICON from stream contained in HGLOBAL
  327. *
  328. * PARAMETERS:
  329. * HGLOBAL hData [in] - data to load from
  330. * HICON& hIcon [out] - read icon
  331. *
  332. * RETURNS:
  333. * HRESULT - result code
  334. *
  335. \***************************************************************************/
  336. static HRESULT LoadIconFromHGlobal(HGLOBAL hData, HICON& hIcon)
  337. {
  338. HRESULT hr = S_OK;
  339. // create the stream
  340. IStreamPtr spStream;
  341. hr = CreateStreamOnHGlobal(hData, FALSE/*do not release*/, &spStream);
  342. if (FAILED(hr))
  343. return hr;
  344. // read the icon as image list
  345. HIMAGELIST himl = ImageList_Read (spStream);
  346. if (!himl)
  347. return E_FAIL;
  348. // retrieve icon from image list
  349. hIcon = ImageList_GetIcon (himl, 0, ILD_NORMAL);
  350. // destroy image list (no longer need it)
  351. ImageList_Destroy (himl);
  352. return S_OK;
  353. }
  354. /***************************************************************************\
  355. *
  356. * METHOD: LoadIconFromXMLData
  357. *
  358. * PURPOSE: Loads icon from memory containing file data of XML document
  359. *
  360. * PARAMETERS:
  361. * LPCSTR pFileData - file data suspected to contain XML document
  362. * DWORD dwLen - the len of input data
  363. * CPersistableIcon &persistableIcon - Icon to initialize upon successful loading
  364. *
  365. * RETURNS:
  366. * HRESULT - result code (S_OK - icon loaded, error code else)
  367. *
  368. \***************************************************************************/
  369. static HRESULT LoadIconFromXMLData(LPCSTR pFileData, DWORD dwLen, CPersistableIcon &persistableIcon)
  370. {
  371. HRESULT hr = S_OK;
  372. int nBytesPerChar = 0;
  373. // check if we recognize the document contents
  374. hr = ValidateXMLDocument(pFileData,dwLen, &nBytesPerChar);
  375. if (hr != S_OK) // hr == S_FALSE means format is not recognized
  376. return E_FAIL;
  377. // Get required keywords.
  378. USES_CONVERSION;
  379. LPCSTR lpcstrLarge = T2CA(XML_ATTR_CONSOLE_ICON_LARGE);
  380. LPCSTR lpcstrSmall = T2CA(XML_ATTR_CONSOLE_ICON_SMALL);
  381. HICON hLargeIcon = NULL;
  382. HICON hSmallIcon = NULL;
  383. // try to read large icon first
  384. HGLOBAL hgLargeIcon = NULL;
  385. hr = FindAndReadIconData(pFileData, nBytesPerChar, dwLen, lpcstrLarge, hgLargeIcon );
  386. if (FAILED(hr))
  387. return hr;
  388. // try to read small icon ( look behind the large one - it should be there!)
  389. HGLOBAL hgSmallIcon = NULL;
  390. hr = FindAndReadIconData( pFileData, nBytesPerChar, dwLen, lpcstrSmall, hgSmallIcon );
  391. if (FAILED(hr))
  392. goto ON_ERROR;
  393. // do get the handles of the icons!
  394. hr = LoadIconFromHGlobal(hgLargeIcon, hLargeIcon);
  395. if (FAILED(hr))
  396. goto ON_ERROR;
  397. hr = LoadIconFromHGlobal(hgSmallIcon, hSmallIcon);
  398. if (FAILED(hr))
  399. goto ON_ERROR;
  400. persistableIcon.m_icon32.Attach (hLargeIcon);
  401. persistableIcon.m_icon16.Attach (hSmallIcon);
  402. // done!
  403. hr = S_OK;
  404. ON_ERROR:
  405. if (hLargeIcon && FAILED(hr))
  406. DestroyIcon(hLargeIcon);
  407. if (hSmallIcon && FAILED(hr))
  408. DestroyIcon(hSmallIcon);
  409. if (hgLargeIcon)
  410. GlobalFree(hgLargeIcon);
  411. return hr;
  412. }
  413. /***************************************************************************\
  414. *
  415. * METHOD: ExtractIconFromXMLFile
  416. *
  417. * PURPOSE: Loads icon from file containing XML document
  418. *
  419. * PARAMETERS:
  420. * LPCTSTR lpstrFileName - name of file to inspect
  421. * CPersistableIcon &persistableIcon - Icon to initialize upon successful loading
  422. *
  423. * RETURNS:
  424. * HRESULT - result code
  425. *
  426. \***************************************************************************/
  427. HRESULT ExtractIconFromXMLFile(LPCTSTR lpstrFileName, CPersistableIcon &persistableIcon)
  428. {
  429. HRESULT hrResult = S_OK;
  430. // open the file
  431. HANDLE hFile = CreateFile(lpstrFileName, GENERIC_READ, FILE_SHARE_READ,
  432. NULL, OPEN_EXISTING, 0, NULL);
  433. if (hFile == INVALID_HANDLE_VALUE)
  434. return hrResult = HRESULT_FROM_WIN32(GetLastError());
  435. // map data into virtual memory
  436. HANDLE hMapping = CreateFileMapping(hFile, NULL/*sec*/, PAGE_READONLY,
  437. 0/*sizeHi*/, 0/*sizeLo*/, NULL/*szname*/);
  438. if (hMapping == NULL)
  439. {
  440. hrResult = HRESULT_FROM_WIN32(GetLastError());
  441. CloseHandle(hFile);
  442. return hrResult;
  443. }
  444. // get pointer to physical memory
  445. LPVOID pData = MapViewOfFile(hMapping, FILE_MAP_READ, 0/*offHi*/, 0/*offLo*/, 0/*len*/);
  446. if (pData)
  447. {
  448. // we are sure here the sizeHi is zero. mapping should fail else
  449. DWORD dwLen = GetFileSize(hFile, NULL/*pSizeHi*/);
  450. // try to load icon from mapped data
  451. hrResult = LoadIconFromXMLData((LPCSTR)pData, dwLen, persistableIcon);
  452. // we do not need a view any more
  453. UnmapViewOfFile(pData);
  454. pData = NULL;
  455. // fall thru to release handles before return
  456. }
  457. else // getting the view failed
  458. {
  459. hrResult = HRESULT_FROM_WIN32(GetLastError());
  460. // fall thru to release handles before return
  461. }
  462. CloseHandle(hMapping);
  463. CloseHandle(hFile);
  464. return hrResult;
  465. }
  466. /***************************************************************************\
  467. *
  468. * METHOD: ValidateXMLDocument
  469. *
  470. * PURPOSE: Validates XML document loaded into memory
  471. * NOTE: it's rather __VERY__ weak inspection. it only checks if doc starts with '<'
  472. *
  473. * PARAMETERS:
  474. * LPCSTR &pFileData - [in/out] - data to look thru / start of xml documet contents
  475. * DWORD &dwLen - [in/out] - init. data len / data left after skipping header
  476. * int *piBytesPerEnglishChar - [out, optional] - bytes occupied by english character
  477. *
  478. * RETURNS:
  479. * HRESULT - (S_FALSE - data does not qualify for XML document)
  480. *
  481. \***************************************************************************/
  482. static HRESULT
  483. ValidateXMLDocument(LPCSTR &pFileData, DWORD &dwLen, int *piBytesPerEnglishChar /*= NULL*/)
  484. {
  485. // default to ansi when not sure
  486. int nBytesPerChar = 1;
  487. if (dwLen >= 2)
  488. {
  489. // raw UNICODE big endian ?
  490. if ((unsigned char)pFileData[1] == 0xff && (unsigned char)pFileData[0] == 0xfe)
  491. {
  492. // to maintain simplicity of the code, we will treat this like little endian.
  493. // we just position file pointer incorrectly.
  494. // since everything we are intersted:
  495. // - is in page 0 (xml tags and base 64)
  496. // - never is at the end of file ( closing tags expected )
  497. // - we do not care about the page of any data
  498. // :we can mix the page codes of the elements and pretend dealing w/ little endian
  499. pFileData += 3; // skip UNICODE signature and first page number
  500. dwLen -= 3;
  501. dwLen /= 2; // we count characters - seems like we have less of them
  502. nBytesPerChar = 2;
  503. }
  504. // raw UNICODE little endian ?
  505. else if ((unsigned char)pFileData[0] == 0xff && (unsigned char)pFileData[1] == 0xfe)
  506. {
  507. pFileData += 2; // skip UNICODE signature
  508. dwLen -= 2;
  509. dwLen /= 2; // we count characters - seems like we have less of them
  510. nBytesPerChar = 2;
  511. }
  512. // compressed UNICODE (UTF 8) ?
  513. else if (dwLen >= 2 && (unsigned char)pFileData[0] == 0xef
  514. && (unsigned char)pFileData[1] == 0xbb && (unsigned char)pFileData[2] == 0xbf)
  515. {
  516. //just skip signature and treat it as ANSI
  517. pFileData += 3; // skip UNICODE signature
  518. dwLen -= 3;
  519. nBytesPerChar = 1;
  520. }
  521. }
  522. // skip whitespaces
  523. char ch;
  524. while (dwLen && (((ch = *pFileData)==' ') || (ch=='\t') || (ch=='\n') || (ch=='\r')))
  525. {
  526. pFileData += nBytesPerChar;
  527. --dwLen;
  528. }
  529. // check if we have a valid xml file (it should open with '<')
  530. if (!dwLen || *pFileData != '<')
  531. return S_FALSE;
  532. if (piBytesPerEnglishChar)
  533. *piBytesPerEnglishChar = nBytesPerChar;
  534. return S_OK;
  535. }