Source code of Windows XP (NT5)
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.

616 lines
15 KiB

  1. /* Copyright 1996 Microsoft */
  2. #include <priv.h>
  3. #include "sccls.h"
  4. #include "aclhist.h"
  5. static const TCHAR c_szSlashSlash[] = TEXT("//");
  6. static const TCHAR c_szEmpty[] = TEXT("");
  7. static const TCHAR c_szFile[] = TEXT("file://");
  8. #define SZ_REGKEY_URLPrefixesKeyA "Software\\Microsoft\\Windows\\CurrentVersion\\URL\\Prefixes"
  9. const TCHAR c_szDefaultURLPrefixKey[] = TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\URL\\DefaultPrefix");
  10. /* IUnknown methods */
  11. HRESULT CACLHistory::QueryInterface(REFIID riid, void **ppvObj)
  12. {
  13. static const QITAB qit[] = {
  14. QITABENT(CACLHistory, IEnumString),
  15. QITABENT(CACLHistory, IEnumACString),
  16. { 0 },
  17. };
  18. return QISearch(this, qit, riid, ppvObj);
  19. }
  20. ULONG CACLHistory::AddRef(void)
  21. {
  22. _cRef++;
  23. return _cRef;
  24. }
  25. ULONG CACLHistory::Release(void)
  26. {
  27. ASSERT(_cRef > 0);
  28. _cRef--;
  29. if (_cRef > 0)
  30. {
  31. return _cRef;
  32. }
  33. delete this;
  34. return 0;
  35. }
  36. /* IEnumString methods */
  37. STDAPI PrepareURLForDisplayUTF8W(LPCWSTR pwz, LPWSTR pwzOut, DWORD * pcbOut, BOOL fUTF8Enabled);
  38. HRESULT CACLHistory::_Next(LPOLESTR* ppsz, ULONG cch, FILETIME* pftLastVisited)
  39. {
  40. ASSERT(ppsz);
  41. HRESULT hr = S_FALSE;
  42. if (_pwszAlternate)
  43. {
  44. //
  45. // There is an alternate version of the string we produced last time.
  46. // Hand them the alternate string now.
  47. //
  48. if (cch == 0)
  49. {
  50. // Return the allocated memory
  51. *ppsz = _pwszAlternate;
  52. }
  53. else
  54. {
  55. // Copy into the caller's buffer
  56. StrCpyN(*ppsz, _pwszAlternate, cch);
  57. CoTaskMemFree(_pwszAlternate);
  58. }
  59. _pwszAlternate = NULL;
  60. hr = S_OK;
  61. }
  62. else if (NULL != _pesu)
  63. {
  64. STATURL rsu[1] = { { SIZEOF(STATURL) } };
  65. ULONG celtFetched;
  66. while (SUCCEEDED(_pesu->Next(1, rsu, &celtFetched)) && celtFetched)
  67. {
  68. ASSERT(IS_VALID_STRING_PTRW(rsu[0].pwcsUrl, -1));
  69. // We didn't ask for the title!
  70. ASSERT(NULL == rsu[0].pwcsTitle);
  71. //
  72. // Ignore if a frame or an error URL
  73. //
  74. if (!(rsu[0].dwFlags & STATURLFLAG_ISTOPLEVEL) ||
  75. IsErrorUrl(rsu[0].pwcsUrl))
  76. {
  77. CoTaskMemFree(rsu[0].pwcsUrl);
  78. rsu[0].pwcsUrl = NULL;
  79. continue;
  80. }
  81. // WARNING (IE #54924): It would look pretty to
  82. // unescape the URL but that would incure data-loss
  83. // so don't do it!!! This breaks more things that
  84. // you could imagine. -BryanSt
  85. //
  86. // Unescape the URL (people don't like to type %F1, etc).
  87. //
  88. // Unescaping is definitely a problem for ftp, but it should be
  89. // safe for http and https (stevepro).
  90. hr = S_OK; // we're done already, unless we have to muck around with UTF8 decoding
  91. if (StrChr(rsu[0].pwcsUrl, L'%'))
  92. {
  93. DWORD dwScheme = GetUrlScheme(rsu[0].pwcsUrl);
  94. if ((dwScheme == URL_SCHEME_HTTP) || (dwScheme == URL_SCHEME_HTTPS))
  95. {
  96. WCHAR szBuf[MAX_URL_STRING];
  97. DWORD cchBuf = ARRAYSIZE(szBuf);
  98. HRESULT hr2 = PrepareURLForDisplayUTF8W(rsu[0].pwcsUrl, szBuf, &cchBuf, TRUE);
  99. if (SUCCEEDED(hr2))
  100. {
  101. // normally StrCpyNW's cch limit should be the size of the destination
  102. // buffer, but in this case, we know that the number of characters that
  103. // were written into szBuf is <= the number of characters in
  104. // rsu[0].pwcsUrl since if anything changes, it is the reduction of
  105. // URL escaped sequences into single characters, and the reduction of
  106. // UTF8 character sequences into single unicode characters.
  107. ASSERT(cchBuf <= (DWORD)lstrlenW(rsu[0].pwcsUrl));
  108. StrCpyNW(rsu[0].pwcsUrl, szBuf, cchBuf+1);
  109. }
  110. }
  111. }
  112. if (cch == 0)
  113. {
  114. // Return the allocated memory
  115. *ppsz = rsu[0].pwcsUrl;
  116. }
  117. else
  118. {
  119. // Copy into the caller's buffer
  120. StrCpyN(*ppsz, rsu[0].pwcsUrl, cch);
  121. CoTaskMemFree(rsu[0].pwcsUrl);
  122. }
  123. // Save the time in case an alternate form is needed
  124. _ftAlternate = rsu[0].ftLastVisited;
  125. break;
  126. }
  127. }
  128. // Provide alternate forms of the same url
  129. if ((_dwOptions & ACEO_ALTERNATEFORMS) && hr == S_OK)
  130. {
  131. USES_CONVERSION;
  132. _CreateAlternateItem(W2T(*ppsz));
  133. }
  134. if (pftLastVisited)
  135. {
  136. *pftLastVisited = _ftAlternate;
  137. }
  138. return hr;
  139. }
  140. HRESULT CACLHistory::Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched)
  141. {
  142. HRESULT hr = S_FALSE;
  143. *pceltFetched = 0;
  144. if (!celt)
  145. {
  146. return S_OK;
  147. }
  148. if (!rgelt)
  149. {
  150. return E_FAIL;
  151. }
  152. hr = _Next(&rgelt[0], 0, NULL);
  153. if (S_OK == hr)
  154. {
  155. *pceltFetched = 1;
  156. }
  157. return hr;
  158. }
  159. HRESULT CACLHistory::Skip(ULONG celt)
  160. {
  161. return E_NOTIMPL;
  162. }
  163. HRESULT CACLHistory::Reset(void)
  164. {
  165. HRESULT hr = S_OK;
  166. //
  167. // Since Reset() is always called before Next() we will
  168. // delay opening the History folder until that last
  169. // moment.
  170. //
  171. if (!_puhs)
  172. {
  173. hr = CoCreateInstance(CLSID_CUrlHistory, NULL, CLSCTX_INPROC_SERVER,
  174. IID_PPV_ARG(IUrlHistoryStg, &_puhs));
  175. }
  176. if ((SUCCEEDED(hr)) && (_puhs) && (!_pesu))
  177. {
  178. hr = _puhs->EnumUrls(&_pesu);
  179. }
  180. if ((SUCCEEDED(hr)) && (_puhs) && (_pesu))
  181. {
  182. hr = _pesu->Reset();
  183. // We only want top-level pages
  184. _pesu->SetFilter(NULL, STATURL_QUERYFLAG_TOPLEVEL | STATURL_QUERYFLAG_NOTITLE);
  185. }
  186. if (_pwszAlternate)
  187. {
  188. CoTaskMemFree(_pwszAlternate);
  189. _pwszAlternate = NULL;
  190. }
  191. return hr;
  192. }
  193. /****************************************************************\
  194. FUNCTION: Clone
  195. DESCRIPTION:
  196. This function will clone the current enumerator.
  197. WARNING:
  198. This function will not implement the full functionality
  199. of Clone(). It will not create an enumerator that is pointing
  200. to the same location in the list as the original enumerator.
  201. \****************************************************************/
  202. HRESULT CACLHistory::Clone(IEnumString **ppenum)
  203. {
  204. HRESULT hr = E_OUTOFMEMORY;
  205. *ppenum = NULL;
  206. CACLHistory * p = new CACLHistory();
  207. if (p)
  208. {
  209. hr = p->Reset();
  210. if (FAILED(hr))
  211. p->Release();
  212. else
  213. *ppenum = SAFECAST(p, IEnumString *);
  214. }
  215. return hr;
  216. }
  217. // *** IEnumACString ***
  218. HRESULT CACLHistory::NextItem(LPOLESTR pszUrl, ULONG cchMax, ULONG* pulSortIndex)
  219. {
  220. if (NULL == pszUrl || cchMax == 0 || NULL == pulSortIndex)
  221. {
  222. return E_INVALIDARG;
  223. }
  224. *pulSortIndex = 0;
  225. FILETIME ftLastVisited;
  226. HRESULT hr = _Next(&pszUrl, cchMax, &ftLastVisited);
  227. // See if we want the results sorted by most recently used first
  228. if (S_OK == hr && (_dwOptions & ACEO_MOSTRECENTFIRST))
  229. {
  230. // Get the current system time
  231. FILETIME ftTimeNow;
  232. CoFileTimeNow(&ftTimeNow);
  233. ULONGLONG t1=0,t2=0,t3;
  234. // Put the current time into 64-bit
  235. t1 = ((ULONGLONG)ftTimeNow.dwHighDateTime << 32);
  236. t1 += ftTimeNow.dwLowDateTime;
  237. // Ditto for the last visited time
  238. t2 = ((ULONGLONG)ftLastVisited.dwHighDateTime << 32);
  239. t2 += ftLastVisited.dwLowDateTime;
  240. // Take the difference and convert into seconds
  241. t3 = (t1-t2) / 10000000;
  242. // If t3 overflows, then set the low byte to the highest possible value
  243. if (t3 > (ULONGLONG)MAXULONG)
  244. {
  245. t3 = MAXULONG;
  246. }
  247. *pulSortIndex = (ULONG)t3;
  248. }
  249. return hr;
  250. }
  251. STDMETHODIMP CACLHistory::SetEnumOptions(DWORD dwOptions)
  252. {
  253. _dwOptions = dwOptions;
  254. return S_OK;
  255. }
  256. STDMETHODIMP CACLHistory::GetEnumOptions(DWORD *pdwOptions)
  257. {
  258. HRESULT hr = E_INVALIDARG;
  259. if (pdwOptions)
  260. {
  261. *pdwOptions = _dwOptions;
  262. hr = S_OK;
  263. }
  264. return hr;
  265. }
  266. /* Constructor / Destructor / CreateInstance */
  267. CACLHistory::CACLHistory()
  268. {
  269. DllAddRef();
  270. ASSERT(_puhs == 0);
  271. ASSERT(_pesu == 0);
  272. _cRef = 1;
  273. _dwOptions = ACEO_ALTERNATEFORMS;
  274. }
  275. CACLHistory::~CACLHistory()
  276. {
  277. if (_pesu)
  278. {
  279. _pesu->Release();
  280. _pesu = NULL;
  281. }
  282. if (_puhs)
  283. {
  284. _puhs->Release();
  285. _puhs = NULL;
  286. }
  287. if (_hdsaAlternateData)
  288. {
  289. DSA_DestroyCallback(_hdsaAlternateData, _FreeAlternateDataItem, 0);
  290. _hdsaAlternateData = NULL;
  291. }
  292. DllRelease();
  293. }
  294. HRESULT CACLHistory_CreateInstance(IUnknown *punkOuter, IUnknown **ppunk, LPCOBJECTINFO poi)
  295. {
  296. // aggregation checking is handled in class factory
  297. *ppunk = NULL;
  298. CACLHistory * p = new CACLHistory();
  299. if (p)
  300. {
  301. *ppunk = SAFECAST(p, IEnumString *);
  302. return NOERROR;
  303. }
  304. return E_OUTOFMEMORY;
  305. }
  306. /* Private functions */
  307. typedef struct _tagAlternateData
  308. {
  309. LPTSTR pszProtocol;
  310. int cchProtocol;
  311. LPTSTR pszDomain;
  312. int cchDomain;
  313. } ALTERNATEDATA;
  314. //
  315. // Add one protocol/domain combination to the HDSA.
  316. // Information is stored in the registry as
  317. // Protocol="ftp://" and Domain="ftp." but we want to
  318. // store it as Protocol="ftp:" and Domain="//ftp."
  319. // when fMoveSlashes is TRUE.
  320. //
  321. void CACLHistory::_AddAlternateDataItem(LPCTSTR pszProtocol, LPCTSTR pszDomain, BOOL fMoveSlashes)
  322. {
  323. ALTERNATEDATA ad;
  324. ZeroMemory(&ad, SIZEOF(ad));
  325. ad.cchProtocol = lstrlen(pszProtocol);
  326. ad.cchDomain = lstrlen(pszDomain);
  327. if (fMoveSlashes)
  328. {
  329. //
  330. // Validate that there are slashes to move.
  331. //
  332. if (ad.cchProtocol > 2 &&
  333. pszProtocol[ad.cchProtocol - 2] == TEXT('/') &&
  334. pszProtocol[ad.cchProtocol - 1] == TEXT('/'))
  335. {
  336. ad.cchProtocol -= 2;
  337. ad.cchDomain += 2;
  338. }
  339. else
  340. {
  341. fMoveSlashes = FALSE;
  342. }
  343. }
  344. ad.pszProtocol = (LPTSTR)LocalAlloc(LPTR, (ad.cchProtocol + 1) * SIZEOF(TCHAR));
  345. ad.pszDomain = (LPTSTR)LocalAlloc(LPTR, (ad.cchDomain + 1) * SIZEOF(TCHAR));
  346. if (ad.pszProtocol && ad.pszDomain)
  347. {
  348. lstrcpyn(ad.pszProtocol, pszProtocol, ad.cchProtocol + 1);
  349. if (fMoveSlashes)
  350. {
  351. lstrcpy(ad.pszDomain, c_szSlashSlash);
  352. lstrcpy(ad.pszDomain + 2, pszDomain);
  353. }
  354. else
  355. {
  356. lstrcpy(ad.pszDomain, pszDomain);
  357. }
  358. DSA_AppendItem(_hdsaAlternateData, &ad);
  359. }
  360. else
  361. {
  362. _FreeAlternateDataItem(&ad, 0);
  363. }
  364. }
  365. //
  366. // This fills in the HDSA from the registry.
  367. //
  368. void CACLHistory::_CreateAlternateData(void)
  369. {
  370. HKEY hkey;
  371. DWORD cbProtocol;
  372. TCHAR szProtocol[MAX_PATH];
  373. DWORD cchDomain;
  374. TCHAR szDomain[MAX_PATH];
  375. DWORD dwType;
  376. ASSERT(_hdsaAlternateData == NULL);
  377. _hdsaAlternateData = DSA_Create(SIZEOF(ALTERNATEDATA), 10);
  378. if (!_hdsaAlternateData)
  379. {
  380. return;
  381. }
  382. //
  383. // Add default protocol.
  384. //
  385. cbProtocol = SIZEOF(szProtocol);
  386. if (SHGetValue(HKEY_LOCAL_MACHINE, c_szDefaultURLPrefixKey, NULL, NULL, (void *)szProtocol, (DWORD *)&cbProtocol) == ERROR_SUCCESS)
  387. {
  388. _AddAlternateDataItem(szProtocol, c_szEmpty, TRUE);
  389. }
  390. //
  391. // Add "file://" prefix. Since "file://foo.txt" doesn't navigate to
  392. // the same place as "//foo.txt" we have to pass in FALSE to fMoveSlashes.
  393. //
  394. _AddAlternateDataItem(c_szFile, c_szEmpty, FALSE);
  395. //
  396. // Add all registered prefixes.
  397. //
  398. if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, SZ_REGKEY_URLPrefixesKeyA, 0, KEY_READ|KEY_WRITE, &hkey) == ERROR_SUCCESS)
  399. {
  400. cchDomain = ARRAYSIZE(szDomain);
  401. cbProtocol = SIZEOF(szProtocol);
  402. for (int i=0;
  403. SHEnumValue(hkey, i, szDomain, &cchDomain, &dwType,
  404. (PBYTE)szProtocol, &cbProtocol) == ERROR_SUCCESS;
  405. i++)
  406. {
  407. _AddAlternateDataItem(szProtocol, szDomain, TRUE);
  408. cchDomain = ARRAYSIZE(szDomain);
  409. cbProtocol = SIZEOF(szProtocol);
  410. }
  411. RegCloseKey(hkey);
  412. }
  413. }
  414. //
  415. // Given a pszUrl, attempts to create an alternate URL
  416. // and store it into _pwszAlternate.
  417. //
  418. // URL Alternate
  419. // ================= ========================
  420. // http://one.com //one.com
  421. // //one.com one.com
  422. // one.com (no alternate available)
  423. // ftp://ftp.two.com //ftp.two.com
  424. // //ftp.two.com ftp.two.com
  425. // ftp.two.com (no alternate available)
  426. // ftp://three.com (no alternate available)
  427. // file://four.txt four.txt
  428. // four.txt (no alternate available)
  429. //
  430. // In a sense, this is the opposite of IURLQualify().
  431. //
  432. void CACLHistory::_CreateAlternateItem(LPCTSTR pszUrl)
  433. {
  434. ASSERT(_pwszAlternate == NULL);
  435. //
  436. // If an URL begins with "//" we can always remove it.
  437. //
  438. if (pszUrl[0] == TEXT('/') && pszUrl[1] == TEXT('/'))
  439. {
  440. _SetAlternateItem(pszUrl + 2);
  441. return;
  442. }
  443. //
  444. // Create the HDSA if necessary.
  445. //
  446. if (!_hdsaAlternateData)
  447. {
  448. _CreateAlternateData();
  449. if (!_hdsaAlternateData)
  450. {
  451. return;
  452. }
  453. }
  454. //
  455. // Look for matches in the HDSA.
  456. //
  457. // For instance, if pszProtocol="ftp:" and pszDomain="//ftp."
  458. // and the given url is of the format "ftp://ftp.{other stuff}"
  459. // then we strip off the pszProtocol and offer "//ftp.{other stuff}"
  460. // as the alternate.
  461. //
  462. for (int i=0; i<DSA_GetItemCount(_hdsaAlternateData); i++)
  463. {
  464. ALTERNATEDATA ad;
  465. if (DSA_GetItem(_hdsaAlternateData, i, &ad) != -1)
  466. {
  467. if ((StrCmpNI(ad.pszProtocol, pszUrl, ad.cchProtocol) == 0) &&
  468. (StrCmpNI(ad.pszDomain, pszUrl + ad.cchProtocol, ad.cchDomain) == 0))
  469. {
  470. _SetAlternateItem(pszUrl + ad.cchProtocol);
  471. return;
  472. }
  473. }
  474. }
  475. }
  476. //
  477. // Given an URL, set _pwszAlternate. This takes care
  478. // of all ANSI/UNICODE issues and allocates memory for
  479. // _pwszAlternate via CoTaskMemAlloc.
  480. //
  481. void CACLHistory::_SetAlternateItem(LPCTSTR pszUrl)
  482. {
  483. ASSERT(_pwszAlternate == NULL);
  484. int cch;
  485. #ifdef UNICODE
  486. cch = lstrlen(pszUrl) + 1;
  487. #else
  488. cch = MultiByteToWideChar(CP_ACP, 0, pszUrl, -1, NULL, 0);
  489. #endif
  490. _pwszAlternate = (LPOLESTR)CoTaskMemAlloc(cch * SIZEOF(WCHAR));
  491. if (_pwszAlternate)
  492. {
  493. #ifdef UNICODE
  494. StrCpy(_pwszAlternate, pszUrl);
  495. #else
  496. MultiByteToWideChar(CP_ACP, 0, pszUrl, -1, _pwszAlternate, cch);
  497. #endif
  498. }
  499. }
  500. //
  501. // Handy routine for calling directly or via DSA callback.
  502. //
  503. int CACLHistory::_FreeAlternateDataItem(void * p, void * d)
  504. {
  505. ALTERNATEDATA *pad = (ALTERNATEDATA *)p;
  506. if (pad->pszProtocol)
  507. {
  508. LocalFree((HANDLE)pad->pszProtocol);
  509. }
  510. if(pad->pszDomain)
  511. {
  512. LocalFree((HANDLE)pad->pszDomain);
  513. }
  514. return 1;
  515. }