windows-nt/Source/XPSP1/NT/shell/browseui/aclhist.cpp
2020-09-26 16:20:57 +08:00

617 lines
15 KiB
C++

/* Copyright 1996 Microsoft */
#include <priv.h>
#include "sccls.h"
#include "aclhist.h"
static const TCHAR c_szSlashSlash[] = TEXT("//");
static const TCHAR c_szEmpty[] = TEXT("");
static const TCHAR c_szFile[] = TEXT("file://");
#define SZ_REGKEY_URLPrefixesKeyA "Software\\Microsoft\\Windows\\CurrentVersion\\URL\\Prefixes"
const TCHAR c_szDefaultURLPrefixKey[] = TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\URL\\DefaultPrefix");
/* IUnknown methods */
HRESULT CACLHistory::QueryInterface(REFIID riid, void **ppvObj)
{
static const QITAB qit[] = {
QITABENT(CACLHistory, IEnumString),
QITABENT(CACLHistory, IEnumACString),
{ 0 },
};
return QISearch(this, qit, riid, ppvObj);
}
ULONG CACLHistory::AddRef(void)
{
_cRef++;
return _cRef;
}
ULONG CACLHistory::Release(void)
{
ASSERT(_cRef > 0);
_cRef--;
if (_cRef > 0)
{
return _cRef;
}
delete this;
return 0;
}
/* IEnumString methods */
STDAPI PrepareURLForDisplayUTF8W(LPCWSTR pwz, LPWSTR pwzOut, DWORD * pcbOut, BOOL fUTF8Enabled);
HRESULT CACLHistory::_Next(LPOLESTR* ppsz, ULONG cch, FILETIME* pftLastVisited)
{
ASSERT(ppsz);
HRESULT hr = S_FALSE;
if (_pwszAlternate)
{
//
// There is an alternate version of the string we produced last time.
// Hand them the alternate string now.
//
if (cch == 0)
{
// Return the allocated memory
*ppsz = _pwszAlternate;
}
else
{
// Copy into the caller's buffer
StrCpyN(*ppsz, _pwszAlternate, cch);
CoTaskMemFree(_pwszAlternate);
}
_pwszAlternate = NULL;
hr = S_OK;
}
else if (NULL != _pesu)
{
STATURL rsu[1] = { { SIZEOF(STATURL) } };
ULONG celtFetched;
while (SUCCEEDED(_pesu->Next(1, rsu, &celtFetched)) && celtFetched)
{
ASSERT(IS_VALID_STRING_PTRW(rsu[0].pwcsUrl, -1));
// We didn't ask for the title!
ASSERT(NULL == rsu[0].pwcsTitle);
//
// Ignore if a frame or an error URL
//
if (!(rsu[0].dwFlags & STATURLFLAG_ISTOPLEVEL) ||
IsErrorUrl(rsu[0].pwcsUrl))
{
CoTaskMemFree(rsu[0].pwcsUrl);
rsu[0].pwcsUrl = NULL;
continue;
}
// WARNING (IE #54924): It would look pretty to
// unescape the URL but that would incure data-loss
// so don't do it!!! This breaks more things that
// you could imagine. -BryanSt
//
// Unescape the URL (people don't like to type %F1, etc).
//
// Unescaping is definitely a problem for ftp, but it should be
// safe for http and https (stevepro).
hr = S_OK; // we're done already, unless we have to muck around with UTF8 decoding
if (StrChr(rsu[0].pwcsUrl, L'%'))
{
DWORD dwScheme = GetUrlScheme(rsu[0].pwcsUrl);
if ((dwScheme == URL_SCHEME_HTTP) || (dwScheme == URL_SCHEME_HTTPS))
{
WCHAR szBuf[MAX_URL_STRING];
DWORD cchBuf = ARRAYSIZE(szBuf);
HRESULT hr2 = PrepareURLForDisplayUTF8W(rsu[0].pwcsUrl, szBuf, &cchBuf, TRUE);
if (SUCCEEDED(hr2))
{
// normally StrCpyNW's cch limit should be the size of the destination
// buffer, but in this case, we know that the number of characters that
// were written into szBuf is <= the number of characters in
// rsu[0].pwcsUrl since if anything changes, it is the reduction of
// URL escaped sequences into single characters, and the reduction of
// UTF8 character sequences into single unicode characters.
ASSERT(cchBuf <= (DWORD)lstrlenW(rsu[0].pwcsUrl));
StrCpyNW(rsu[0].pwcsUrl, szBuf, cchBuf+1);
}
}
}
if (cch == 0)
{
// Return the allocated memory
*ppsz = rsu[0].pwcsUrl;
}
else
{
// Copy into the caller's buffer
StrCpyN(*ppsz, rsu[0].pwcsUrl, cch);
CoTaskMemFree(rsu[0].pwcsUrl);
}
// Save the time in case an alternate form is needed
_ftAlternate = rsu[0].ftLastVisited;
break;
}
}
// Provide alternate forms of the same url
if ((_dwOptions & ACEO_ALTERNATEFORMS) && hr == S_OK)
{
USES_CONVERSION;
_CreateAlternateItem(W2T(*ppsz));
}
if (pftLastVisited)
{
*pftLastVisited = _ftAlternate;
}
return hr;
}
HRESULT CACLHistory::Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched)
{
HRESULT hr = S_FALSE;
*pceltFetched = 0;
if (!celt)
{
return S_OK;
}
if (!rgelt)
{
return E_FAIL;
}
hr = _Next(&rgelt[0], 0, NULL);
if (S_OK == hr)
{
*pceltFetched = 1;
}
return hr;
}
HRESULT CACLHistory::Skip(ULONG celt)
{
return E_NOTIMPL;
}
HRESULT CACLHistory::Reset(void)
{
HRESULT hr = S_OK;
//
// Since Reset() is always called before Next() we will
// delay opening the History folder until that last
// moment.
//
if (!_puhs)
{
hr = CoCreateInstance(CLSID_CUrlHistory, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARG(IUrlHistoryStg, &_puhs));
}
if ((SUCCEEDED(hr)) && (_puhs) && (!_pesu))
{
hr = _puhs->EnumUrls(&_pesu);
}
if ((SUCCEEDED(hr)) && (_puhs) && (_pesu))
{
hr = _pesu->Reset();
// We only want top-level pages
_pesu->SetFilter(NULL, STATURL_QUERYFLAG_TOPLEVEL | STATURL_QUERYFLAG_NOTITLE);
}
if (_pwszAlternate)
{
CoTaskMemFree(_pwszAlternate);
_pwszAlternate = NULL;
}
return hr;
}
/****************************************************************\
FUNCTION: Clone
DESCRIPTION:
This function will clone the current enumerator.
WARNING:
This function will not implement the full functionality
of Clone(). It will not create an enumerator that is pointing
to the same location in the list as the original enumerator.
\****************************************************************/
HRESULT CACLHistory::Clone(IEnumString **ppenum)
{
HRESULT hr = E_OUTOFMEMORY;
*ppenum = NULL;
CACLHistory * p = new CACLHistory();
if (p)
{
hr = p->Reset();
if (FAILED(hr))
p->Release();
else
*ppenum = SAFECAST(p, IEnumString *);
}
return hr;
}
// *** IEnumACString ***
HRESULT CACLHistory::NextItem(LPOLESTR pszUrl, ULONG cchMax, ULONG* pulSortIndex)
{
if (NULL == pszUrl || cchMax == 0 || NULL == pulSortIndex)
{
return E_INVALIDARG;
}
*pulSortIndex = 0;
FILETIME ftLastVisited;
HRESULT hr = _Next(&pszUrl, cchMax, &ftLastVisited);
// See if we want the results sorted by most recently used first
if (S_OK == hr && (_dwOptions & ACEO_MOSTRECENTFIRST))
{
// Get the current system time
FILETIME ftTimeNow;
CoFileTimeNow(&ftTimeNow);
ULONGLONG t1=0,t2=0,t3;
// Put the current time into 64-bit
t1 = ((ULONGLONG)ftTimeNow.dwHighDateTime << 32);
t1 += ftTimeNow.dwLowDateTime;
// Ditto for the last visited time
t2 = ((ULONGLONG)ftLastVisited.dwHighDateTime << 32);
t2 += ftLastVisited.dwLowDateTime;
// Take the difference and convert into seconds
t3 = (t1-t2) / 10000000;
// If t3 overflows, then set the low byte to the highest possible value
if (t3 > (ULONGLONG)MAXULONG)
{
t3 = MAXULONG;
}
*pulSortIndex = (ULONG)t3;
}
return hr;
}
STDMETHODIMP CACLHistory::SetEnumOptions(DWORD dwOptions)
{
_dwOptions = dwOptions;
return S_OK;
}
STDMETHODIMP CACLHistory::GetEnumOptions(DWORD *pdwOptions)
{
HRESULT hr = E_INVALIDARG;
if (pdwOptions)
{
*pdwOptions = _dwOptions;
hr = S_OK;
}
return hr;
}
/* Constructor / Destructor / CreateInstance */
CACLHistory::CACLHistory()
{
DllAddRef();
ASSERT(_puhs == 0);
ASSERT(_pesu == 0);
_cRef = 1;
_dwOptions = ACEO_ALTERNATEFORMS;
}
CACLHistory::~CACLHistory()
{
if (_pesu)
{
_pesu->Release();
_pesu = NULL;
}
if (_puhs)
{
_puhs->Release();
_puhs = NULL;
}
if (_hdsaAlternateData)
{
DSA_DestroyCallback(_hdsaAlternateData, _FreeAlternateDataItem, 0);
_hdsaAlternateData = NULL;
}
DllRelease();
}
HRESULT CACLHistory_CreateInstance(IUnknown *punkOuter, IUnknown **ppunk, LPCOBJECTINFO poi)
{
// aggregation checking is handled in class factory
*ppunk = NULL;
CACLHistory * p = new CACLHistory();
if (p)
{
*ppunk = SAFECAST(p, IEnumString *);
return NOERROR;
}
return E_OUTOFMEMORY;
}
/* Private functions */
typedef struct _tagAlternateData
{
LPTSTR pszProtocol;
int cchProtocol;
LPTSTR pszDomain;
int cchDomain;
} ALTERNATEDATA;
//
// Add one protocol/domain combination to the HDSA.
// Information is stored in the registry as
// Protocol="ftp://" and Domain="ftp." but we want to
// store it as Protocol="ftp:" and Domain="//ftp."
// when fMoveSlashes is TRUE.
//
void CACLHistory::_AddAlternateDataItem(LPCTSTR pszProtocol, LPCTSTR pszDomain, BOOL fMoveSlashes)
{
ALTERNATEDATA ad;
ZeroMemory(&ad, SIZEOF(ad));
ad.cchProtocol = lstrlen(pszProtocol);
ad.cchDomain = lstrlen(pszDomain);
if (fMoveSlashes)
{
//
// Validate that there are slashes to move.
//
if (ad.cchProtocol > 2 &&
pszProtocol[ad.cchProtocol - 2] == TEXT('/') &&
pszProtocol[ad.cchProtocol - 1] == TEXT('/'))
{
ad.cchProtocol -= 2;
ad.cchDomain += 2;
}
else
{
fMoveSlashes = FALSE;
}
}
ad.pszProtocol = (LPTSTR)LocalAlloc(LPTR, (ad.cchProtocol + 1) * SIZEOF(TCHAR));
ad.pszDomain = (LPTSTR)LocalAlloc(LPTR, (ad.cchDomain + 1) * SIZEOF(TCHAR));
if (ad.pszProtocol && ad.pszDomain)
{
lstrcpyn(ad.pszProtocol, pszProtocol, ad.cchProtocol + 1);
if (fMoveSlashes)
{
lstrcpy(ad.pszDomain, c_szSlashSlash);
lstrcpy(ad.pszDomain + 2, pszDomain);
}
else
{
lstrcpy(ad.pszDomain, pszDomain);
}
DSA_AppendItem(_hdsaAlternateData, &ad);
}
else
{
_FreeAlternateDataItem(&ad, 0);
}
}
//
// This fills in the HDSA from the registry.
//
void CACLHistory::_CreateAlternateData(void)
{
HKEY hkey;
DWORD cbProtocol;
TCHAR szProtocol[MAX_PATH];
DWORD cchDomain;
TCHAR szDomain[MAX_PATH];
DWORD dwType;
ASSERT(_hdsaAlternateData == NULL);
_hdsaAlternateData = DSA_Create(SIZEOF(ALTERNATEDATA), 10);
if (!_hdsaAlternateData)
{
return;
}
//
// Add default protocol.
//
cbProtocol = SIZEOF(szProtocol);
if (SHGetValue(HKEY_LOCAL_MACHINE, c_szDefaultURLPrefixKey, NULL, NULL, (void *)szProtocol, (DWORD *)&cbProtocol) == ERROR_SUCCESS)
{
_AddAlternateDataItem(szProtocol, c_szEmpty, TRUE);
}
//
// Add "file://" prefix. Since "file://foo.txt" doesn't navigate to
// the same place as "//foo.txt" we have to pass in FALSE to fMoveSlashes.
//
_AddAlternateDataItem(c_szFile, c_szEmpty, FALSE);
//
// Add all registered prefixes.
//
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, SZ_REGKEY_URLPrefixesKeyA, 0, KEY_READ|KEY_WRITE, &hkey) == ERROR_SUCCESS)
{
cchDomain = ARRAYSIZE(szDomain);
cbProtocol = SIZEOF(szProtocol);
for (int i=0;
SHEnumValue(hkey, i, szDomain, &cchDomain, &dwType,
(PBYTE)szProtocol, &cbProtocol) == ERROR_SUCCESS;
i++)
{
_AddAlternateDataItem(szProtocol, szDomain, TRUE);
cchDomain = ARRAYSIZE(szDomain);
cbProtocol = SIZEOF(szProtocol);
}
RegCloseKey(hkey);
}
}
//
// Given a pszUrl, attempts to create an alternate URL
// and store it into _pwszAlternate.
//
// URL Alternate
// ================= ========================
// http://one.com //one.com
// //one.com one.com
// one.com (no alternate available)
// ftp://ftp.two.com //ftp.two.com
// //ftp.two.com ftp.two.com
// ftp.two.com (no alternate available)
// ftp://three.com (no alternate available)
// file://four.txt four.txt
// four.txt (no alternate available)
//
// In a sense, this is the opposite of IURLQualify().
//
void CACLHistory::_CreateAlternateItem(LPCTSTR pszUrl)
{
ASSERT(_pwszAlternate == NULL);
//
// If an URL begins with "//" we can always remove it.
//
if (pszUrl[0] == TEXT('/') && pszUrl[1] == TEXT('/'))
{
_SetAlternateItem(pszUrl + 2);
return;
}
//
// Create the HDSA if necessary.
//
if (!_hdsaAlternateData)
{
_CreateAlternateData();
if (!_hdsaAlternateData)
{
return;
}
}
//
// Look for matches in the HDSA.
//
// For instance, if pszProtocol="ftp:" and pszDomain="//ftp."
// and the given url is of the format "ftp://ftp.{other stuff}"
// then we strip off the pszProtocol and offer "//ftp.{other stuff}"
// as the alternate.
//
for (int i=0; i<DSA_GetItemCount(_hdsaAlternateData); i++)
{
ALTERNATEDATA ad;
if (DSA_GetItem(_hdsaAlternateData, i, &ad) != -1)
{
if ((StrCmpNI(ad.pszProtocol, pszUrl, ad.cchProtocol) == 0) &&
(StrCmpNI(ad.pszDomain, pszUrl + ad.cchProtocol, ad.cchDomain) == 0))
{
_SetAlternateItem(pszUrl + ad.cchProtocol);
return;
}
}
}
}
//
// Given an URL, set _pwszAlternate. This takes care
// of all ANSI/UNICODE issues and allocates memory for
// _pwszAlternate via CoTaskMemAlloc.
//
void CACLHistory::_SetAlternateItem(LPCTSTR pszUrl)
{
ASSERT(_pwszAlternate == NULL);
int cch;
#ifdef UNICODE
cch = lstrlen(pszUrl) + 1;
#else
cch = MultiByteToWideChar(CP_ACP, 0, pszUrl, -1, NULL, 0);
#endif
_pwszAlternate = (LPOLESTR)CoTaskMemAlloc(cch * SIZEOF(WCHAR));
if (_pwszAlternate)
{
#ifdef UNICODE
StrCpy(_pwszAlternate, pszUrl);
#else
MultiByteToWideChar(CP_ACP, 0, pszUrl, -1, _pwszAlternate, cch);
#endif
}
}
//
// Handy routine for calling directly or via DSA callback.
//
int CACLHistory::_FreeAlternateDataItem(void * p, void * d)
{
ALTERNATEDATA *pad = (ALTERNATEDATA *)p;
if (pad->pszProtocol)
{
LocalFree((HANDLE)pad->pszProtocol);
}
if(pad->pszDomain)
{
LocalFree((HANDLE)pad->pszDomain);
}
return 1;
}