WindowsXP-SP1/shell/shdocvw/iforms.cpp

5327 lines
151 KiB
C++

// iforms.cpp : Implementation of CIntelliForms
#include "priv.h"
#include <iehelpid.h>
#include <pstore.h>
#include "hlframe.h"
#include "iformsp.h"
#include "shldisp.h"
#include "opsprof.h"
#include "resource.h"
#include <mluisupp.h>
// {E161255A-37C3-11d2-BCAA-00C04FD929DB}
static const GUID c_PStoreType =
{ 0xe161255a, 0x37c3, 0x11d2, { 0xbc, 0xaa, 0x0, 0xc0, 0x4f, 0xd9, 0x29, 0xdb } };
const TCHAR c_szIntelliForms[] = TEXT("Internet Explorer");
#define TF_IFORMS TF_CUSTOM2
// ALLOW_SHELLUIOC_HOST code will allow us to host intelliforms
// from the Shell UI OC (shuioc.cpp). This is used for the
// HTML Find dialog
#define ALLOW_SHELLUIOC_HOST
CIntelliForms *GetIntelliFormsFromDoc(IHTMLDocument2 *pDoc2);
inline void MyToLower(LPWSTR pwszStr)
{
if (g_fRunningOnNT)
{
CharLowerBuffW(pwszStr, lstrlenW(pwszStr));
}
else
{
// Ideally we would use the code page contained in the string instead of
// the system code page.
CHAR chBuf[MAX_PATH];
SHUnicodeToAnsi(pwszStr, chBuf, ARRAYSIZE(chBuf));
CharLowerBuffA(chBuf, lstrlenA(chBuf));
SHAnsiToUnicode(chBuf, pwszStr, lstrlenW(pwszStr)+1);
}
}
//=================== Exported functions =====================
// Exported for inetCPL
HRESULT ClearAutoSuggestForForms(DWORD dwClear)
{
CIntelliForms *pObj = new CIntelliForms();
if (pObj)
{
HRESULT hr;
hr = pObj->ClearStore(dwClear);
pObj->Release();
return hr;
}
return E_OUTOFMEMORY;
}
HRESULT SetIdAutoSuggestForForms(const GUID *pguidId, void *pIntelliForms)
{
CIntelliForms *pThis = (CIntelliForms *)pIntelliForms;
if (pThis)
{
if (GUID_NULL == *pguidId)
{
pThis->m_guidUserId = c_PStoreType;
}
else
{
pThis->m_guidUserId = *pguidId;
}
return S_OK;
}
return E_FAIL;
}
// called from iedisp.cpp
void AttachIntelliForms(void *pvOmWindow, HWND hwnd, IHTMLDocument2 *pDoc2, void **ppIntelliForms)
{
static DWORD s_dwAdminRestricted = 0xFE;
CIEFrameAuto::COmWindow *pOmWindow = (CIEFrameAuto::COmWindow *)pvOmWindow;
ASSERT(ppIntelliForms && *ppIntelliForms==NULL);
if (s_dwAdminRestricted == 0xFE)
{
s_dwAdminRestricted = CIntelliForms::IsAdminRestricted(c_szRegValFormSuggestRestrict) &&
CIntelliForms::IsAdminRestricted(c_szRegValSavePasswords);
}
if (s_dwAdminRestricted)
{
return;
}
// If we're not hosted by internet explorer, we don't want to enable Intelliforms
// unless dochost explicitly overrides this
if (!IsInternetExplorerApp() &&
!(pOmWindow && (DOCHOSTUIFLAG_ENABLE_FORMS_AUTOCOMPLETE & pOmWindow->IEFrameAuto()->GetDocHostFlags())))
{
return;
}
if (!hwnd && pOmWindow)
{
pOmWindow->IEFrameAuto()->get_HWND((LONG_PTR *)&hwnd);
}
if (!hwnd || !pDoc2 || !ppIntelliForms || (*ppIntelliForms != NULL))
{
return;
}
#ifndef ALLOW_SHELLUIOC_HOST
if (!pOmWindow)
{
return;
}
#else
if (!pOmWindow)
{
// Script is asking to attach to this document
// Deny their request if another CIntelliForms is already attached
if (NULL != GetIntelliFormsFromDoc(pDoc2))
{
return;
}
}
#endif
CIntelliForms *pForms = new CIntelliForms();
if (pForms)
{
if (SUCCEEDED(pForms->Init(pOmWindow, pDoc2, hwnd)))
{
*ppIntelliForms = pForms;
}
else
{
pForms->Release();
}
}
}
void ReleaseIntelliForms(void *pIntelliForms)
{
CIntelliForms *pForms = (CIntelliForms *) pIntelliForms;
if (pForms)
{
pForms->UnInit();
pForms->Release();
}
}
HRESULT IntelliFormsActiveElementChanged(void *pIntelliForms, IHTMLElement * pHTMLElement)
{
CIntelliForms *pForms = (CIntelliForms *) pIntelliForms;
if (pForms)
return pForms->ActiveElementChanged(pHTMLElement);
return E_FAIL;
}
INT_PTR CALLBACK AskUserDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
HRESULT IncrementAskCount();
HRESULT IntelliFormsDoAskUser(HWND hwndBrowser, void *pv)
{
// Make sure that we haven't asked them yet
if (S_OK == IncrementAskCount())
{
// Modal dialog to ask the user our little question
SHFusionDialogBoxParam(MLGetHinst(), MAKEINTRESOURCE(IDD_AUTOSUGGEST_ASK_USER),
hwndBrowser, AskUserDlgProc, NULL);
}
return S_OK;
}
// Linked list of active CIntelliform objects to translate from
// IHTMLDocument2->CIntelliforms when script calls window.external.saveforms
// Protected by g_csDll
CIntelliForms *g_pIntelliFormsFirst=NULL;
// Translate this pDoc2 to an existing instance of CIntelliForms
// Will return NULL if no CIntelliForms attached to this doc
// NO REFCOUNT IS ADDED TO THE RETURN
CIntelliForms *GetIntelliFormsFromDoc(IHTMLDocument2 *pDoc2)
{
if (!pDoc2)
{
return NULL;
}
ENTERCRITICAL;
CIntelliForms *pNext = g_pIntelliFormsFirst;
IUnknown *punkDoc;
CIntelliForms *pIForms=NULL;
pDoc2->QueryInterface(IID_IUnknown, (void **)&punkDoc);
if (punkDoc)
{
while (pNext)
{
if (pNext->GetDocument() == punkDoc)
{
pIForms = pNext;
break;
}
pNext=pNext->GetNext();
}
punkDoc->Release();
}
LEAVECRITICAL;
return pIForms;
}
// called from shuioc.cpp
HRESULT IntelliFormsSaveForm(IHTMLDocument2 *pDoc2, VARIANT *pvarForm)
{
HRESULT hrRet = S_FALSE;
IHTMLFormElement *pForm=NULL;
CIntelliForms *pIForms=NULL;
if (pvarForm->vt == VT_DISPATCH)
{
pvarForm->pdispVal->QueryInterface(IID_IHTMLFormElement, (void **)&pForm);
}
if (pForm)
{
pIForms = GetIntelliFormsFromDoc(pDoc2);
if (pIForms)
{
// Should validate that pIForms was created on this thread
hrRet = pIForms->ScriptSubmit(pForm);
}
pForm->Release();
}
return hrRet;
}
const TCHAR c_szYes[] = TEXT("yes");
const TCHAR c_szNo[] = TEXT("no");
INT_PTR AutoSuggestDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);
#ifdef CHECKBOX_HELP
const DWORD c_aIFormsHelpIds[] = {
IDC_AUTOSUGGEST_NEVER, IDH_INTELLIFORM_PW_PROMPT,
0, 0
};
#endif
const WCHAR c_wszVCardPrefix[] = L"vCard.";
BOOL CIntelliForms::CAutoSuggest::s_fRegisteredWndClass = FALSE;
// Must be in same order as EVENT enum type
// All events we need to sink anywhere
CEventSinkCallback::EventSinkEntry CEventSinkCallback::EventsToSink[] =
{
{ EVENT_KEYDOWN, L"onkeydown", L"keydown" },
{ EVENT_KEYPRESS, L"onkeypress", L"keypress" },
{ EVENT_MOUSEDOWN, L"onmousedown", L"mousedown"},
{ EVENT_DBLCLICK, L"ondblclick", L"dblclick" },
{ EVENT_FOCUS, L"onfocus", L"focus" },
{ EVENT_BLUR, L"onblur", L"blur" },
{ EVENT_SUBMIT, L"onsubmit", L"submit" },
{ EVENT_SCROLL, L"onscroll", L"scroll" },
{ EVENT_COMPOSITION,NULL, L"composition"},
{ EVENT_NOTIFY, NULL, L"notify" },
};
// Fake edit window class
const WCHAR c_szEditWndClass[] = TEXT("IntelliFormClass");
// Minimum dropdown width
const int MINIMUM_WIDTH=100;
// Submit number to ask user to enable us
const int ASK_USER_ON_SUBMIT_N = 2;
void GetStuffFromEle(IUnknown *punkEle, IHTMLWindow2 **ppWin2, IHTMLDocument2 **ppDoc2)
{
if (ppWin2)
*ppWin2=NULL;
if (ppDoc2)
*ppDoc2=NULL;
IHTMLElement *pEle=NULL;
punkEle->QueryInterface(IID_IHTMLElement, (void **)&pEle);
if (pEle)
{
IDispatch *pDisp=NULL;
pEle->get_document(&pDisp);
if (pDisp)
{
IHTMLDocument2 *pDoc2 = NULL;
pDisp->QueryInterface(IID_IHTMLDocument2, (void **)&pDoc2);
if (pDoc2)
{
if (ppWin2)
{
pDoc2->get_parentWindow(ppWin2);
}
if (ppDoc2)
{
*ppDoc2 = pDoc2;
}
else
{
pDoc2->Release();
}
}
pDisp->Release();
}
pEle->Release();
}
}
void Win3FromDoc2(IHTMLDocument2 *pDoc2, IHTMLWindow3 **ppWin3)
{
*ppWin3=NULL;
IHTMLWindow2 *pWin2=NULL;
if (SUCCEEDED(pDoc2->get_parentWindow(&pWin2)) && pWin2)
{
pWin2->QueryInterface(IID_IHTMLWindow3, (void **)ppWin3);
pWin2->Release();
}
}
// Increment the count of whether we've asked the user to enable us or not. We won't
// ask them on the first form submit since installing ie5.
HRESULT IncrementAskCount()
{
DWORD dwData, dwSize, dwType;
dwSize = sizeof(dwData);
// c_szRegValAskUser contains the number of form submits
// 0 means we've already asked user whether to enable us
// 1 means we've already had one form submit, and should ask the user this time
// value not present means we haven't had any form submits
if ((ERROR_SUCCESS == SHGetValue(HKEY_CURRENT_USER,
c_szRegKeyIntelliForms, c_szRegValAskUser, &dwType, &dwData, &dwSize)) &&
dwType == REG_DWORD)
{
if (dwData == 0)
{
// Shouldn't get this far
TraceMsg(TF_IFORMS|TF_WARNING, "IntelliFormsDoAskUser: Already asked user");
return E_FAIL; // Already asked user
}
}
else
{
dwData = 0;
}
if (dwData+1 < ASK_USER_ON_SUBMIT_N)
{
dwData ++;
SHSetValue(HKEY_CURRENT_USER, c_szRegKeyIntelliForms, c_szRegValAskUser,
REG_DWORD, &dwData, sizeof(dwData));
TraceMsg(TF_IFORMS, "IntelliFormsDoAskUser incrementing submit count. Not asking user.");
return E_FAIL; // Don't ask the user
}
return S_OK; // Let's ask the user
}
/////////////////////////////////////////////////////////////////////////////
// CIntelliForms
CIntelliForms::CIntelliForms()
{
TraceMsg(TF_IFORMS, "CIntelliForms::CIntelliForms");
m_cRef = 1;
m_iRestoredIndex = -1;
m_fRestricted = IsAdminRestricted(c_szRegValFormSuggestRestrict);
m_fRestrictedPW = IsAdminRestricted(c_szRegValSavePasswords);
m_guidUserId = c_PStoreType;
// Add us to global linked list
ENTERCRITICAL;
m_pNext = g_pIntelliFormsFirst;
g_pIntelliFormsFirst = this;
LEAVECRITICAL;
}
CIntelliForms::~CIntelliForms()
{
// Remove us from global linked list
ENTERCRITICAL;
CIntelliForms *pLast=NULL, *pNext = g_pIntelliFormsFirst;
while (pNext && pNext != this)
{
pLast = pNext;
pNext=pNext->m_pNext;
}
ASSERT(pNext == this);
if (pNext)
{
if (pLast)
{
pLast->m_pNext = m_pNext;
}
else
{
g_pIntelliFormsFirst = m_pNext;
}
}
LEAVECRITICAL;
TraceMsg(TF_IFORMS, "CIntelliForms::~CIntelliForms");
}
// Called when document is ready to attach to
// We don't support re-initting
HRESULT CIntelliForms::Init(CIEFrameAuto::COmWindow *pOmWindow, IHTMLDocument2 *pDoc2, HWND hwndBrowser)
{
HRESULT hr;
ASSERT(pDoc2 && hwndBrowser);
#ifndef ALLOW_SHELLUIOC_HOST
if (pOmWindow == NULL)
{
return E_INVALIDARG;
}
#endif
// Connect to get active element changed notifications
m_pOmWindow = pOmWindow;
if (pOmWindow)
{
pOmWindow->AddRef();
}
m_pDoc2 = pDoc2;
pDoc2->AddRef();
pDoc2->QueryInterface(IID_IUnknown, (void **)&m_punkDoc2);
m_hwndBrowser = hwndBrowser;
m_iRestoredIndex = -1;
hr = S_OK;
#ifdef ALLOW_SHELLUIOC_HOST
if (!pOmWindow && (hr == S_OK))
{
// Check for the current active element since the page is requesting
// us to attach to an existing document
IHTMLElement *pHTMLElement = NULL;
m_pDoc2->get_activeElement(&pHTMLElement);
ActiveElementChanged(pHTMLElement);
if (pHTMLElement)
pHTMLElement->Release();
}
#endif
GetUrl(); // Init Url member variables so we don't get the url on the
// wrong thread in the FillEnumerator call
TraceMsg(TF_IFORMS, "CIntelliForms::Init hr=%08x", hr);
return hr;
}
HRESULT CIntelliForms::UnInit()
{
if (m_fInModalDialog)
{
// Lifetime management. If UnInit is called during modal dialog, we keep ourself
// alive. Use Enter/LeaveModalDialog to ensure correct use
ASSERT(m_fUninitCalled == FALSE); // Should only be called once...
m_fUninitCalled = TRUE;
return S_FALSE;
}
// Destroy this now, before we free other member variables, to ensure CAutoSuggest doesn't
// try to access us on a second thread.
if (m_pAutoSuggest)
{
m_pAutoSuggest->SetParent(NULL);
m_pAutoSuggest->DetachFromInput();
delete m_pAutoSuggest;
m_pAutoSuggest = NULL;
}
if (m_hdpaForms && m_pSink)
{
IHTMLElement2 *pEle2;
EVENTS events[] = { EVENT_SUBMIT };
for (int i=DPA_GetPtrCount(m_hdpaForms)-1; i>=0; i--)
{
((IHTMLFormElement *)(DPA_FastGetPtr(m_hdpaForms, i)))->QueryInterface(IID_IHTMLElement2, (void **)&pEle2);
m_pSink->UnSinkEvents(pEle2, ARRAYSIZE(events), events);
pEle2->Release();
}
}
SysFreeString(m_bstrFullUrl);
m_bstrFullUrl = NULL;
SysFreeString(m_bstrUrl);
m_bstrUrl = NULL;
if (m_pwszUrlHash)
{
LocalFree((void *)m_pwszUrlHash);
m_pwszUrlHash = NULL;
}
// Unhook regular event sink
if (m_pSink)
{
#ifndef ALLOW_SHELLUIOC_HOST
ASSERT(m_pOmWindow);
#endif
if (m_pOmWindow)
{
IHTMLWindow3 *pWin3=NULL;
Win3FromDoc2(m_pDoc2, &pWin3);
if (pWin3)
{
EVENTS events[] = { EVENT_SCROLL };
m_pSink->UnSinkEvents(pWin3, ARRAYSIZE(events), events);
pWin3->Release();
}
}
m_pSink->SetParent(NULL);
m_pSink->Release();
m_pSink=NULL;
}
// Unhook designer event sink
if (m_pEditSink)
{
m_pEditSink->Attach(NULL);
m_pEditSink->SetParent(NULL);
m_pEditSink->Release();
m_pEditSink=NULL;
}
// SAFERELEASE (and ATOMICRELEASE) macro in shdocvw is actually function which requires IUnknown
ATOMICRELEASET(m_pOmWindow, CIEFrameAuto::COmWindow);
SAFERELEASE(m_pDoc2);
SAFERELEASE(m_punkDoc2);
FreeElementList();
FreeFormList();
if (m_pslPasswords)
{
delete m_pslPasswords;
m_pslPasswords = NULL;
}
ReleasePStore();
TraceMsg(TF_IFORMS, "CIntelliForms::UnInit");
return S_OK;
}
STDMETHODIMP CIntelliForms::QueryInterface(REFIID riid, void **ppv)
{
*ppv = NULL;
if ((IID_IPropertyNotifySink == riid) ||
(IID_IUnknown == riid))
{
*ppv = (IPropertyNotifySink *)this;
}
if (NULL != *ppv)
{
((IUnknown *)*ppv)->AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) CIntelliForms::AddRef(void)
{
return ++m_cRef;
}
STDMETHODIMP_(ULONG) CIntelliForms::Release(void)
{
if (--m_cRef == 0)
{
delete this;
return 0;
}
return m_cRef;
}
HRESULT CIntelliForms::ActiveElementChanged(IHTMLElement * pHTMLElement)
{
ASSERT(m_pDoc2);
// Detach the AutoSuggest object and destroy it
if (m_pAutoSuggest)
{
m_pAutoSuggest->DetachFromInput();
delete m_pAutoSuggest;
m_pAutoSuggest=NULL;
}
if (m_pDoc2)
{
IHTMLElement *pEle=pHTMLElement;
if (pEle)
{
BOOL fPassword=FALSE;
IHTMLInputTextElement *pTextEle = NULL;
if (SUCCEEDED(ShouldAttachToElement(pEle, TRUE, NULL, &pTextEle, NULL, &fPassword)))
{
BOOL fEnabledInCPL = IsEnabledInCPL();
BOOL fEnabledPW = IsEnabledRestorePW();
// We need to watch user activity if...
if (fEnabledInCPL || // Intelliforms is enabled
fEnabledPW || // Or Restore Passwords is enabled
!AskedUserToEnable()) // Or we may ask them to enable us
{
m_pAutoSuggest = new CAutoSuggest(this, fEnabledInCPL, fEnabledPW);
if (m_pAutoSuggest)
{
if (!m_pSink)
{
m_pSink = new CEventSink(this);
if (m_pSink)
{
#ifndef ALLOW_SHELLUIOC_HOST
// Don't sink scroll event if hosted by ShellUIOC
// or jscript.dll asserts on unload
ASSERT(m_pOmWindow);
#endif
if (m_pOmWindow)
{
IHTMLWindow3 *pWin3=NULL;
Win3FromDoc2(m_pDoc2, &pWin3);
if (pWin3)
{
EVENTS events[] = { EVENT_SCROLL };
m_pSink->SinkEvents(pWin3, ARRAYSIZE(events), events);
pWin3->Release();
}
}
}
}
// Hook up designer sink for IME event
if (!m_pEditSink)
{
m_pEditSink = new CEditEventSink(this);
if (m_pEditSink)
{
m_pEditSink->Attach(pEle);
}
}
if (!m_pSink || FAILED(m_pAutoSuggest->AttachToInput(pTextEle)))
{
delete m_pAutoSuggest;
m_pAutoSuggest = NULL;
}
}
}
pTextEle->Release();
}
else
{
ASSERT(!pTextEle);
if (fPassword)
{
m_fHitPWField = TRUE;
}
}
//
// Don't release pEle
}
}
return S_OK;
}
// Helper functions
BOOL CIntelliForms::AskedUserToEnable()
{
DWORD dwType, dwSize;
DWORD dwVal;
DWORD dwRet;
dwSize = sizeof(dwVal);
dwRet = SHGetValue(HKEY_CURRENT_USER, c_szRegKeyIntelliForms, c_szRegValAskUser,
&dwType, &dwVal, &dwSize);
if ((dwRet == ERROR_SUCCESS) && (dwType == REG_DWORD))
{
return (dwVal == 0) ? TRUE : FALSE;
}
return FALSE;
}
BOOL CIntelliForms::IsEnabledInRegistry(LPCTSTR pszKey, LPCTSTR pszValue, BOOL fDefault)
{
DWORD dwType, dwSize;
TCHAR szEnabled[16];
DWORD dwRet;
dwSize = sizeof(szEnabled);
dwRet = SHGetValue(HKEY_CURRENT_USER, pszKey, pszValue, &dwType, szEnabled, &dwSize);
if (dwRet == ERROR_INSUFFICIENT_BUFFER)
{
// Invalid value in registry.
ASSERT(dwRet == ERROR_SUCCESS);
return FALSE;
}
if (dwRet == ERROR_SUCCESS)
{
if ((dwType == REG_SZ) &&
(!StrCmp(szEnabled, TEXT("yes"))))
{
// Enabled
return TRUE;
}
else
{
// Disabled
return FALSE;
}
}
// Value not found
return fDefault;
}
BOOL CIntelliForms::IsAdminRestricted(LPCTSTR pszRegVal)
{
DWORD lSize;
DWORD lValue;
lValue = 0; // clear it
lSize = sizeof(lValue);
if (ERROR_SUCCESS !=
SHGetValue(HKEY_CURRENT_USER, c_szRegKeyRestrict, pszRegVal, NULL, (LPBYTE)&lValue, &lSize ))
{
return FALSE;
}
ASSERT(lSize == sizeof(lValue));
return (0 != lValue) ? TRUE : FALSE;
}
BOOL CIntelliForms::IsEnabledForPage()
{
if (!m_fCheckedIfEnabled)
{
m_fCheckedIfEnabled = TRUE;
// We will have our Url in m_bstrFullUrl, only if it is https: protocol
if (m_bstrFullUrl)
{
ASSERT(!StrCmpNIW(m_bstrFullUrl, L"https:", 5));
m_fEnabledForPage = TRUE;
// See if this page is in the internet cache. If not, we won't intelliform
// for this page either.
if (!GetUrlCacheEntryInfoW(m_bstrFullUrl, NULL, NULL) && (GetLastError() != ERROR_INSUFFICIENT_BUFFER))
{
// Failed - it's not in the cache
m_fEnabledForPage = FALSE;
}
}
else
{
// Url is not https: so always enable Intelliforms
m_fEnabledForPage = TRUE;
}
}
return m_fEnabledForPage;
}
HRESULT CIntelliForms::GetBodyEle(IHTMLElement2 **ppEle2)
{
if (!m_pDoc2 || !ppEle2)
{
return E_INVALIDARG;
}
*ppEle2=NULL;
IHTMLElement *pBodyEle=NULL;
m_pDoc2->get_body(&pBodyEle);
if (pBodyEle)
{
pBodyEle->QueryInterface(IID_IHTMLElement2, (void **)ppEle2);
pBodyEle->Release();
}
return (*ppEle2) ? S_OK : E_FAIL;
}
// static
BOOL CIntelliForms::IsElementEnabled(IHTMLElement *pEle)
{
BOOL fEnabled=TRUE;
BSTR bstrAttribute;
VARIANT varVal;
varVal.vt = VT_EMPTY;
// First check "AutoComplete=OFF"
bstrAttribute=SysAllocString(L"AutoComplete");
if (bstrAttribute &&
SUCCEEDED(pEle->getAttribute(bstrAttribute, 0, &varVal)))
{
if (varVal.vt == VT_BSTR)
{
if (!StrCmpIW(varVal.bstrVal, L"off"))
{
// We are disabled.
fEnabled=FALSE;
}
}
VariantClear(&varVal);
}
SysFreeString(bstrAttribute);
// Then check "READONLY" attribute
if (fEnabled)
{
IHTMLInputElement *pInputEle=NULL;
pEle->QueryInterface(IID_IHTMLInputElement, (void **)&pInputEle);
if (pInputEle)
{
VARIANT_BOOL vbReadOnly=VARIANT_FALSE;
pInputEle->get_readOnly(&vbReadOnly);
if (vbReadOnly)
{
// We are read only.
fEnabled=FALSE;
}
pInputEle->Release();
}
}
return fEnabled;
}
// static
HRESULT CIntelliForms::ShouldAttachToElement(IUnknown *punkEle,
BOOL fCheckForm,
IHTMLElement2 **ppEle2,
IHTMLInputTextElement **ppITE,
IHTMLFormElement **ppFormEle,
BOOL *pfPassword)
{
IHTMLInputTextElement *pITE = NULL;
if (ppEle2)
{
*ppEle2 = NULL;
}
if (ppITE)
{
*ppITE = NULL;
}
if (ppFormEle)
{
*ppFormEle = NULL;
}
punkEle->QueryInterface(IID_IHTMLInputTextElement, (void **)&pITE);
if (NULL == pITE)
{
// Not an input text element. Do not attach.
return E_FAIL;
}
HRESULT hr = E_FAIL;
IHTMLElement2 *pEle2 = NULL;
IHTMLElement *pEle = NULL;
IHTMLFormElement *pFormEle = NULL;
punkEle->QueryInterface(IID_IHTMLElement2, (void **)&pEle2);
punkEle->QueryInterface(IID_IHTMLElement, (void **)&pEle);
if (pEle2 && pEle)
{
// type=text is all that's allowed
BSTR bstrType=NULL;
if (SUCCEEDED(pITE->get_type(&bstrType)) && bstrType)
{
if (!StrCmpICW(bstrType, L"text"))
{
// FormSuggest=off attribute turns us off for this element
if (IsElementEnabled(pEle))
{
IHTMLElement *pFormHTMLEle=NULL;
if (fCheckForm || ppFormEle)
{
pITE->get_form(&pFormEle);
if (pFormEle)
{
pFormEle->QueryInterface(IID_IHTMLElement, (void **)&pFormHTMLEle);
}
else
{
// This may be valid if element is not in form
TraceMsg(TF_IFORMS, "Iforms: pITE->get_form() returned NULL");
}
}
// FormSuggest=off for form turns us off for this form
if (pFormEle &&
(!fCheckForm || (pFormHTMLEle && IsElementEnabled(pFormHTMLEle))))
{
hr = S_OK;
if (ppEle2)
{
*ppEle2 = pEle2;
pEle2->AddRef();
}
if (ppFormEle)
{
*ppFormEle = pFormEle;
pFormEle->AddRef();
}
if (ppITE)
{
*ppITE = pITE;
pITE->AddRef();
}
}
SAFERELEASE(pFormHTMLEle);
SAFERELEASE(pFormEle);
}
}
else
{
if (pfPassword && !StrCmpICW(bstrType, L"password") && IsElementEnabled(pEle))
{
TraceMsg(TF_IFORMS, "IForms: Password field detected.");
*pfPassword = TRUE;
}
}
SysFreeString(bstrType);
}
else
{
TraceMsg(TF_IFORMS, "IntelliForms disabled for single element via attribute");
}
}
SAFERELEASE(pITE);
SAFERELEASE(pEle2);
SAFERELEASE(pEle);
return hr;
}
// Get the URL that we're located at, with query string/anchor stripped.
LPCWSTR CIntelliForms::GetUrl()
{
if (m_bstrUrl)
{
return m_bstrUrl;
}
if (m_pOmWindow)
{
m_pOmWindow->IEFrameAuto()->get_LocationURL(&m_bstrUrl);
}
#ifdef ALLOW_SHELLUIOC_HOST
else
{
IHTMLLocation *pHTMLLocation=NULL;
m_pDoc2->get_location(&pHTMLLocation);
if (NULL != pHTMLLocation)
{
pHTMLLocation->get_href(&m_bstrUrl);
pHTMLLocation->Release();
}
}
#endif
if (m_bstrUrl)
{
PARSEDURLW puW = {0};
puW.cbSize = sizeof(puW);
// Save the full url for a security check, if we are https protocol
if (SUCCEEDED(ParseURLW(m_bstrUrl, &puW)))
{
if (puW.nScheme == URL_SCHEME_HTTPS)
{
m_bstrFullUrl = SysAllocString(m_bstrUrl);
if (!m_bstrFullUrl)
{
SysFreeString(m_bstrUrl);
m_bstrUrl=NULL;
}
}
}
}
if (m_bstrUrl)
{
// Strip off any query string or anchor
LPWSTR lpUrl = m_bstrUrl;
while (*lpUrl)
{
if ((*lpUrl == L'?') || (*lpUrl == L'#'))
{
*lpUrl = L'\0';
break;
}
lpUrl ++;
}
return m_bstrUrl;
}
TraceMsg(TF_WARNING|TF_IFORMS, "CIntelliForms::GetUrl() failing!");
return L""; // We can assume non-NULL pointer
}
// hook our "Submit" event sink to this form
HRESULT CIntelliForms::AttachToForm(IHTMLFormElement *pFormEle)
{
ASSERT(m_pSink);
if (m_pSink)
{
IHTMLElement2 *pEle2 = NULL;
pFormEle->QueryInterface(IID_IHTMLElement2, (void **)&pEle2);
if (pEle2)
{
// Sink event for the form
EVENTS events[] = { EVENT_SUBMIT };
m_pSink->SinkEvents(pEle2, ARRAYSIZE(events), events);
}
SAFERELEASE(pEle2);
return S_OK;
}
return E_OUTOFMEMORY;
}
// Returns TRUE if nothing but spaces in string
inline BOOL IsEmptyString(LPCWSTR lpwstr)
{
while (*lpwstr && (*lpwstr == L' ')) lpwstr++;
return (*lpwstr == 0);
}
// called for each element in the form we are submitting
HRESULT CIntelliForms::SubmitElement(IHTMLInputTextElement *pITE, FILETIME ftSubmit, BOOL fEnabledInCPL)
{
if (m_fRestricted) return E_FAIL;
HRESULT hrRet = S_OK;
BSTR bstrName;
CIntelliForms::GetName(pITE, &bstrName);
if (bstrName && bstrName[0])
{
BSTR bstrValue=NULL;
pITE->get_value(&bstrValue);
if (bstrValue && bstrValue[0] && !IsEmptyString(bstrValue))
{
if (fEnabledInCPL)
{
TraceMsg(TF_IFORMS, "IForms: Saving field \"%ws\" as \"%ws\"", bstrName, bstrValue);
CStringList *psl;
if (FAILED(ReadFromStore(bstrName, &psl)))
{
CStringList_New(&psl);
}
if (psl)
{
HRESULT hr;
if (SUCCEEDED(hr = psl->AddString(bstrValue, ftSubmit)))
{
if ((S_OK == hr) ||
(psl->NumStrings() > CStringList::MAX_STRINGS / 4))
{
// We added a non-duplicate string, or we updated the
// last submit time of an existing string
WriteToStore(bstrName, psl);
}
}
delete psl;
}
}
else
{
hrRet = S_FALSE; // Tell caller that we didn't save because we were disabled
}
}
SysFreeString(bstrValue);
}
SysFreeString(bstrName);
return hrRet;
}
HRESULT CIntelliForms::HandleFormSubmit(IHTMLFormElement *pForm)
{
IUnknown *punkForm=NULL;
if (!pForm)
{
// We currently require a form element even from script
return E_INVALIDARG;
}
if (!m_hdpaElements || !m_hdpaForms)
{
return S_OK;
}
// Make sure we're enabled
BOOL fEnabledInCPL = IsEnabledInCPL();
if (fEnabledInCPL || IsEnabledRestorePW() || !AskedUserToEnable())
{
pForm->QueryInterface(IID_IUnknown, (void **)&punkForm);
if (punkForm)
{
IHTMLFormElement *pThisFormEle;
IUnknown *punkThisForm;
FILETIME ftSubmit;
int iCount=0;
BOOL fShouldAskUser=FALSE;
IHTMLInputTextElement *pFirstEle=NULL;
GetSystemTimeAsFileTime(&ftSubmit);
// Go through list of 'changed' elements and save their values
// make sure we loop backwards since we nuke elements as we find them
for (int i=DPA_GetPtrCount(m_hdpaElements)-1; i>=0; i--)
{
IHTMLInputTextElement *pITE = ((IHTMLInputTextElement *)(DPA_FastGetPtr(m_hdpaElements, i)));
if (SUCCEEDED(pITE->get_form(&pThisFormEle)) && pThisFormEle)
{
if (SUCCEEDED(pThisFormEle->QueryInterface(IID_IUnknown, (void **)&punkThisForm)))
{
if (punkThisForm == punkForm)
{
// Verify that we're still allowed to save this element
if (SUCCEEDED(ShouldAttachToElement(pITE, TRUE, NULL, NULL, NULL, NULL)))
{
iCount ++;
if (!pFirstEle)
{
pFirstEle = pITE;
pFirstEle->AddRef();
}
// Don't save non-password stuff for non-cached pages
if (IsEnabledForPage())
{
// Won't actually save the value if fEnabledInCPL is FALSE
if (S_FALSE == SubmitElement(pITE, ftSubmit, fEnabledInCPL))
{
// We would have saved this if we were enabled
fShouldAskUser = TRUE;
}
}
// Remove this element from the DPA to prevent any possibility of
// saving before more user input takes place
pITE->Release();
DPA_DeletePtr(m_hdpaElements, i);
}
}
else
{
TraceMsg(TF_IFORMS, "IForms: User input in different form than was submitted...?");
}
punkThisForm->Release();
}
pThisFormEle->Release();
}
else
{
// It shouldn't be in our DPA if it isn't in a form...
TraceMsg(TF_WARNING|TF_IFORMS, "Iforms: pITE->get_form() returned NULL!");
}
}
if (0 == DPA_GetPtrCount(m_hdpaElements))
{
DPA_Destroy(m_hdpaElements);
m_hdpaElements=NULL;
}
if (m_fHitPWField || (m_iRestoredIndex != -1))
{
// ?? why not check iCount==1 here?
if (pFirstEle)
{
// May have restored PW and may have changed or entered it
SavePassword(pForm, ftSubmit, pFirstEle);
// WARNING - after returning from "SavePassword" our object may be invalid
// if we got released/detached during modal dialog
}
}
else if (fShouldAskUser)
{
// Possibly ask user if they want to enable intelliforms, only if
// this isn't a login
if (m_pOmWindow)
{
m_pOmWindow->IntelliFormsAskUser(NULL);
}
fShouldAskUser = FALSE;
}
if (fShouldAskUser)
{
// If we should ask the user but we're not going to (login form),
// increment our count anyway so that we ask them as soon as we can
IncrementAskCount();
}
punkForm->Release();
SAFERELEASE(pFirstEle);
}
}
return S_OK;
}
HRESULT CIntelliForms::HandleEvent(IHTMLElement *pEle, EVENTS Event, IHTMLEventObj *pEventObj)
{
TraceMsg(TF_IFORMS, "CIntelliForms::HandleEvent Event=%ws", EventsToSink[Event].pwszEventName);
if (Event == EVENT_SUBMIT)
{
// Save strings for modified text inputs when appropriate
IHTMLFormElement *pFormEle = NULL;
if (pEle)
{
pEle->QueryInterface(IID_IHTMLFormElement, (void **)&pFormEle);
if (pFormEle)
{
HandleFormSubmit(pFormEle);
// Warning - "this" may be detached/destroyed at this point
pFormEle->Release();
}
}
}
else
{
ASSERT(Event == EVENT_SCROLL);
if (m_pAutoSuggest)
m_pAutoSuggest->UpdateDropdownPosition();
}
return S_OK;
}
HRESULT CIntelliForms::PreHandleEvent(DISPID inEvtDispId, IHTMLEventObj* pIEventObj)
{
if ((inEvtDispId == 0) &&
(m_pAutoSuggest != NULL) &&
(m_pAutoSuggest->AttachedElement() != NULL))
{
BSTR bstrType = NULL;
CEventSinkCallback::EVENTS Event = EVENT_BOGUS;
pIEventObj->get_type(&bstrType);
if (bstrType)
{
#if 0
// Spew wParam and lParam
IHTMLEventObj3 *pObj3 = NULL;
pIEventObj->QueryInterface(IID_PPV_ARG(IHTMLEventObj3, &pObj3));
if (pObj3)
{
long lLong=0;
long wWord=0;
pObj3->get_imeCompositionChange(&lLong);
pObj3->get_imeNotifyCommand(&wWord);
TraceMsg(TF_ALWAYS, "PreHandleEvent: %ws - wWord=0x%04x lLong=0x%08x", bstrType, wWord, lLong);
pObj3->Release();
}
#endif
if (!StrCmp(bstrType, L"composition"))
{
Event = EVENT_COMPOSITION;
}
else if (!StrCmp(bstrType, L"notify"))
{
Event = EVENT_NOTIFY;
}
if (Event != EVENT_BOGUS)
{
// Trident doesn't set srcElement on eventobj, so just use the one
// we're attached to
IHTMLElement *pEle;
m_pAutoSuggest->AttachedElement()->QueryInterface(IID_IHTMLElement, (void **)&pEle);
if (pEle)
{
m_pAutoSuggest->HandleEvent(pEle, Event, pIEventObj);
pEle->Release();
}
}
SysFreeString(bstrType);
}
}
return S_FALSE; // S_FALSE so that Trident will still process this
}
// Our passwords are stored in username/value pairs
// Search every other string for the username
HRESULT CIntelliForms::FindPasswordEntry(LPCWSTR pwszValue, int *piIndex)
{
ASSERT(m_pslPasswords);
ASSERT(!(m_pslPasswords->NumStrings() & 1)); // Should be even number
int i;
for (i=0; i<m_pslPasswords->NumStrings(); i += 2)
{
if (!StrCmpIW(pwszValue, m_pslPasswords->GetString(i)))
{
// Found it
*piIndex = i+1;
return S_OK;
}
}
return E_FAIL;
}
// Convert url to string based on shlwapi UrlHash return
LPCWSTR CIntelliForms::GetUrlHash()
{
BYTE bBuf[15];
if (m_pwszUrlHash)
{
return m_pwszUrlHash;
}
LPCWSTR pwszUrl = GetUrl();
if (!pwszUrl || !*pwszUrl)
{
return NULL;
}
if (SUCCEEDED(UrlHashW(pwszUrl, bBuf, ARRAYSIZE(bBuf))))
{
// Translate this array of bytes into 7-bit chars
m_pwszUrlHash = (LPWSTR)LocalAlloc(LMEM_FIXED, sizeof(WCHAR)*(ARRAYSIZE(bBuf)+1));
if (m_pwszUrlHash)
{
for (int i=0; i<ARRAYSIZE(bBuf); i++)
{
// Translate each char into 32-96 range
((LPWSTR)m_pwszUrlHash)[i] = (WCHAR)((bBuf[i] & 0x3F) + 0x20);
}
((LPWSTR)m_pwszUrlHash)[i] = L'\0';
}
return m_pwszUrlHash;
}
return NULL;
}
// Tells us if passwords are present for this url
BOOL CIntelliForms::ArePasswordsSaved()
{
if (!m_fRestrictedPW)
{
DWORD dwVal, dwSize=sizeof(dwVal);
LPCWSTR pwsz = GetUrlHash();
if (pwsz && (ERROR_SUCCESS == SHGetValueW(HKEY_CURRENT_USER, c_wszRegKeyIntelliFormsSPW, pwsz, NULL, &dwVal, &dwSize)))
{
return TRUE;
}
}
return FALSE;
}
// Will return password list in m_pslPasswords, if passwords are saved
BOOL CIntelliForms::LoadPasswords()
{
if (!m_fCheckedPW)
{
m_fCheckedPW = TRUE;
// Check if passwords are present without hitting pstore
if (ArePasswordsSaved())
{
// We should have passwords for this url. Hit PStore.
ReadFromStore(GetUrl(), &m_pslPasswords, TRUE);
m_iRestoredIndex = -1;
}
}
else if (m_pslPasswords)
{
// If we already have passwords, double check the registry in case the user
// nuked saved stuff via inetcpl
if (!ArePasswordsSaved())
{
delete m_pslPasswords;
m_pslPasswords=NULL;
m_iRestoredIndex = -1;
}
}
return (m_pslPasswords != NULL);
}
void CIntelliForms::SavePasswords()
{
if (m_pslPasswords && m_bstrUrl)
{
WriteToStore(m_bstrUrl, m_pslPasswords);
SetPasswordsAreSaved(TRUE);
}
}
// Mark that we have passwords saved for this url
void CIntelliForms::SetPasswordsAreSaved(BOOL fSaved)
{
LPCWSTR pwsz = GetUrlHash();
if (pwsz)
{
if (fSaved)
{
DWORD dwSize = sizeof(DWORD);
DWORD dw = 0;
SHSetValueW(HKEY_CURRENT_USER, c_wszRegKeyIntelliFormsSPW, pwsz, REG_DWORD, &dw, sizeof(dw));
}
else
{
SHDeleteValueW(HKEY_CURRENT_USER, c_wszRegKeyIntelliFormsSPW, pwsz);
}
}
}
// enumerates form & gets password fields
class CDetectLoginForm
{
public:
CDetectLoginForm() { m_pNameEle=m_pPasswordEle=m_pPasswordEle2=NULL; }
~CDetectLoginForm() { SAFERELEASE(m_pNameEle); SAFERELEASE(m_pPasswordEle); }
HRESULT ParseForm(IHTMLFormElement *pFormEle, BOOL fRestoring);
IHTMLInputTextElement *GetNameEle() { return m_pNameEle; }
IHTMLInputTextElement *GetPasswordEle() { return m_pPasswordEle; }
protected:
IHTMLInputTextElement *m_pNameEle;
IHTMLInputTextElement *m_pPasswordEle;
IHTMLInputTextElement *m_pPasswordEle2;
static HRESULT s_PasswordCB(IDispatch *pDispEle, DWORD_PTR dwCBData);
};
// if SUCCEEDED(hr), GetNameEle and GetPasswordEle are guaranteed non-NULL
HRESULT CDetectLoginForm::ParseForm(IHTMLFormElement *pFormEle, BOOL fRestoring)
{
if (m_pPasswordEle || m_pNameEle || m_pPasswordEle2)
{
return E_FAIL;
}
CIntelliForms::CEnumCollection<IHTMLFormElement>::EnumCollection(pFormEle, s_PasswordCB, (DWORD_PTR)this);
// For forms with two password fields (possibly used for login *and* new accounts)
// we clear the second field on PW restore and require it to be blank for saving.
// Ideally, we would detect this as a password change situation as well.
if (m_pPasswordEle2)
{
if (fRestoring)
{
BSTR bstrEmpty=SysAllocString(L"");
if (bstrEmpty)
{
m_pPasswordEle2->put_value(bstrEmpty);
SysFreeString(bstrEmpty);
}
}
else
{
BSTR bstrVal=NULL;
m_pPasswordEle2->get_value(&bstrVal);
if (bstrVal && bstrVal[0])
{
// Failure! Second password field isn't empty.
SAFERELEASE(m_pNameEle);
SAFERELEASE(m_pPasswordEle);
}
SysFreeString(bstrVal);
}
SAFERELEASE(m_pPasswordEle2); // Always release this
}
if (m_pPasswordEle && m_pNameEle)
{
return S_OK;
}
SAFERELEASE(m_pNameEle);
SAFERELEASE(m_pPasswordEle);
ASSERT(!m_pPasswordEle2);
return E_FAIL;
}
// Password callback for CEnumCollection to find username and password fields
// in a login form
HRESULT CDetectLoginForm::s_PasswordCB(IDispatch *pDispEle, DWORD_PTR dwCBData)
{
CDetectLoginForm *pThis = (CDetectLoginForm *)dwCBData;
HRESULT hr=S_OK;
IHTMLInputTextElement *pTextEle=NULL;
pDispEle->QueryInterface(IID_IHTMLInputTextElement, (void **)&pTextEle);
if (pTextEle)
{
BSTR bstrType;
pTextEle->get_type(&bstrType);
if (bstrType)
{
if (!StrCmpICW(bstrType, L"text"))
{
// Assume this is the 'name' field
if (pThis->m_pNameEle)
{
// Whoops, we've already got a name field. Can't have two...
hr = E_ABORT;
}
else
{
pThis->m_pNameEle = pTextEle;
pTextEle->AddRef();
}
}
else if (!StrCmpICW(bstrType, L"password"))
{
// Assume this is the 'password' field
if (pThis->m_pPasswordEle)
{
// Whoops, we've already got a password field. Can't have two...
// ...oh wait, yes we can...
if (pThis->m_pPasswordEle2)
{
// ...but we definitely can't have three!!!
hr = E_ABORT;
}
else
{
pThis->m_pPasswordEle2 = pTextEle;
pTextEle->AddRef();
}
}
else
{
pThis->m_pPasswordEle = pTextEle;
pTextEle->AddRef();
}
}
SysFreeString(bstrType);
}
pTextEle->Release();
}
if (hr == E_ABORT)
{
SAFERELEASE(pThis->m_pNameEle);
SAFERELEASE(pThis->m_pPasswordEle);
SAFERELEASE(pThis->m_pPasswordEle2);
}
return hr;
}
// Fill in passwords for this username, if one is available
HRESULT CIntelliForms::AutoFillPassword(IHTMLInputTextElement *pTextEle, LPCWSTR pwszUsername)
{
BSTR bstrUrl = NULL;
if (!pTextEle || !pwszUsername)
return E_INVALIDARG;
if (!IsEnabledRestorePW() || !LoadPasswords())
{
// We have no passwords for this url
return S_FALSE;
}
int iIndex;
if (SUCCEEDED(FindPasswordEntry(pwszUsername, &iIndex)))
{
// Returns index of password in m_pslPasswords
ASSERT(iIndex>=0 && iIndex<m_pslPasswords->NumStrings() && (iIndex&1));
FILETIME ft;
// StringTime==0 indicates user said "no" to saving password
if (SUCCEEDED(m_pslPasswords->GetStringTime(iIndex, &ft)) && (FILETIME_TO_INT(ft) != 0))
{
TraceMsg(TF_IFORMS, "IntelliForms found saved password");
// We have a password saved for this specific username. Fill it in.
CDetectLoginForm LoginForm;
IHTMLFormElement *pFormEle=NULL;
HRESULT hr = E_FAIL;
pTextEle->get_form(&pFormEle);
if (pFormEle)
{
// See if this is a valid form: One plain text input, One password input. Find the fields.
hr = LoginForm.ParseForm(pFormEle, TRUE);
pFormEle->Release();
}
else
{
// Shouldn't get this far if we don't have a form for this element
TraceMsg(TF_WARNING|TF_IFORMS, "Iforms: pITE->get_form() returned NULL!");
}
if (SUCCEEDED(hr))
{
BSTR bstrPW=NULL;
m_pslPasswords->GetBSTR(iIndex, &bstrPW);
if (bstrPW)
{
LoginForm.GetPasswordEle()->put_value(bstrPW);
SysFreeString(bstrPW);
m_iRestoredIndex = iIndex;
// We restored this password. sink the SUBMIT for this form (if we haven't yet)
UserInput(pTextEle);
}
}
}
else
{
// User previously said 'no' to remembering passwords
m_iRestoredIndex = -1;
}
}
return S_OK;
}
HRESULT CIntelliForms::DeletePassword(LPCWSTR pwszUsername)
{
// If we have a password, ask them if they want to delete it.
if (LoadPasswords())
{
int iIndex;
if (SUCCEEDED(FindPasswordEntry(pwszUsername, &iIndex)))
{
// If they previously said "no", delete without asking - they don't actually
// have a password saved
// Otherwise, ask and delete only if they say "yes"
FILETIME ft;
if (FAILED(m_pslPasswords->GetStringTime(iIndex, &ft)) ||
(0 == FILETIME_TO_INT(ft)) ||
(IDYES == DialogBoxParam(MLGetHinst(), MAKEINTRESOURCE(IDD_AUTOSUGGEST_DELETEPASSWORD),
m_hwndBrowser, AutoSuggestDlgProc, IDD_AUTOSUGGEST_DELETEPASSWORD)))
{
// Delete username then password from string list
if (SUCCEEDED(m_pslPasswords->DeleteString(iIndex-1)) &&
SUCCEEDED(m_pslPasswords->DeleteString(iIndex-1)))
{
TraceMsg(TF_IFORMS, "Deleting password for user \"%ws\"", pwszUsername);
ASSERT(!(m_pslPasswords->NumStrings() & 1));
if (m_iRestoredIndex == iIndex)
{
m_iRestoredIndex = -1;
}
else if (m_iRestoredIndex > iIndex)
{
m_iRestoredIndex -= 2;
}
if (m_pslPasswords->NumStrings() == 0)
{
// No more strings for this url. Nuke it.
DeleteFromStore(GetUrl());
SetPasswordsAreSaved(FALSE);
delete m_pslPasswords;
m_pslPasswords = NULL;
ASSERT(m_iRestoredIndex == -1);
}
else
{
SavePasswords();
}
}
}
}
}
return S_OK;
}
HRESULT CIntelliForms::SavePassword(IHTMLFormElement *pFormEle, FILETIME ftSubmit, IHTMLInputTextElement *pFirstEle)
{
if (m_fRestrictedPW ||
!IsEnabledRestorePW())
{
return S_FALSE;
}
BOOL fAskUser = TRUE;
// First let's check for previously saved entries for this username
if (LoadPasswords())
{
int iIndex;
BSTR bstrUserName=NULL;
pFirstEle->get_value(&bstrUserName);
if (bstrUserName)
{
if (SUCCEEDED(FindPasswordEntry(bstrUserName, &iIndex)))
{
FILETIME ft;
if (SUCCEEDED(m_pslPasswords->GetStringTime(iIndex, &ft)))
{
if (FILETIME_TO_INT(ft) == 0)
{
// StringTime==0 means user previously said "no".
TraceMsg(TF_IFORMS, "IForms not asking about saving password");
fAskUser = FALSE;
}
else if (m_iRestoredIndex != iIndex)
{
// User previously said "yes" - but we didn't restore it for some reason
// Can happen with "back" button then submit
TraceMsg(TF_WARNING|TF_IFORMS, "IForms - user saved password and we didn't restore it");
// Write regkey in case that was the problem - we'll work next time
SetPasswordsAreSaved(TRUE);
m_iRestoredIndex = iIndex;
}
}
}
else
{
m_iRestoredIndex = -1;
}
SysFreeString(bstrUserName);
}
}
// Then lets ask the user if they'd like to save the password for this username
if (fAskUser)
{
CDetectLoginForm LoginForm;
// See if this is a valid form: One plain text input, One password input. Find the fields.
if (SUCCEEDED(LoginForm.ParseForm(pFormEle, FALSE)))
{
TraceMsg(TF_IFORMS, "IForms Successfully detected 'save password' form");
BSTR bstrUsername=NULL;
BSTR bstrPassword=NULL;
LoginForm.GetNameEle()->get_value(&bstrUsername);
LoginForm.GetPasswordEle()->get_value(&bstrPassword);
if (bstrUsername && bstrPassword)
{
if (m_iRestoredIndex != -1)
{
// We have a previously saved password. See if our current entry is the same.
if (!StrCmpW(bstrPassword, m_pslPasswords->GetString(m_iRestoredIndex)))
{
// They're the same... nothing to do...
TraceMsg(TF_IFORMS, "IForms - user entered PW same as saved PW - nothing to do");
// Check to see that the username case is the same, just to be sure
if (StrCmpW(bstrUsername, m_pslPasswords->GetString(m_iRestoredIndex-1)))
{
TraceMsg(TF_IFORMS, "IForms - except change the username's case");
if (SUCCEEDED(m_pslPasswords->ReplaceString(m_iRestoredIndex-1, bstrUsername)))
{
SavePasswords();
}
else
{
// Something went horribly wrong!
delete m_pslPasswords;
m_pslPasswords=NULL;
}
}
}
else
{
// Ask the user if we want to change the saved password
INT_PTR iMB;
EnterModalDialog();
iMB = DialogBoxParam(MLGetHinst(), MAKEINTRESOURCE(IDD_AUTOSUGGEST_CHANGEPASSWORD),
m_hwndBrowser, AutoSuggestDlgProc, IDD_AUTOSUGGEST_CHANGEPASSWORD);
if (IDYES == iMB)
{
// Delete the old one and add the new one. Update filetimes.
if (SUCCEEDED(m_pslPasswords->ReplaceString(m_iRestoredIndex, bstrPassword)))
{
m_pslPasswords->SetStringTime(m_iRestoredIndex, ftSubmit);
SavePasswords();
TraceMsg(TF_IFORMS, "IForms successfully saved changed password");
}
else
{
TraceMsg(TF_IFORMS|TF_WARNING, "IForms couldn't change password!");
delete m_pslPasswords;
m_pslPasswords = NULL;
}
}
LeaveModalDialog();
}
}
else
{
// We don't have a previously saved password for this user. See if they want to save it.
// If the password is empty, don't bother asking or saving
if (IsEnabledAskPW() && bstrPassword[0])
{
EnterModalDialog();
INT_PTR iMB = DialogBoxParam(MLGetHinst(), MAKEINTRESOURCE(IDD_AUTOSUGGEST_SAVEPASSWORD),
m_hwndBrowser, AutoSuggestDlgProc, IDD_AUTOSUGGEST_SAVEPASSWORD);
// If we can't load passwords, then create a new list
if (!LoadPasswords())
{
CStringList_New(&m_pslPasswords);
if (m_pslPasswords)
m_pslPasswords->SetListData(LIST_DATA_PASSWORD);
}
if (m_pslPasswords)
{
if ((IDCANCEL == iMB) || ((IDNO == iMB) && (!IsEnabledAskPW())))
{
// If they hit the close box or said "no" and checked "don't ask",
// don't even save the username; we may ask them again next time
}
else
{
if (IDYES != iMB)
{
// User said "no" but we save the username (no password) and
// set filetime to 0 which means they said "no"
bstrPassword[0] = L'\0';
ftSubmit.dwLowDateTime = ftSubmit.dwHighDateTime = 0;
}
else
{
TraceMsg(TF_IFORMS, "IForms saving password for user %ws", bstrUsername);
}
m_pslPasswords->SetAutoScavenge(FALSE);
// Save the username and password, or just the username if they said "no"
if (SUCCEEDED(m_pslPasswords->AppendString(bstrUsername, ftSubmit)) &&
SUCCEEDED(m_pslPasswords->AppendString(bstrPassword, ftSubmit)))
{
SavePasswords();
}
else
{
TraceMsg(TF_WARNING, "IForms couldn't save username/password");
delete m_pslPasswords;
m_pslPasswords=NULL;
}
}
}
LeaveModalDialog();
}
}
}
SysFreeString(bstrUsername);
SysFreeString(bstrPassword);
} // if (SUCCEEDED(ParseForm()))
}
return S_OK;
}
// Returns reference to password string list if present. Return value must be used
// immediately and not destroyed. Used only by CEnumString.
HRESULT CIntelliForms::GetPasswordStringList(CStringList **ppslPasswords)
{
if (LoadPasswords())
{
*ppslPasswords = m_pslPasswords;
return S_OK;
}
*ppslPasswords = NULL;
return E_FAIL;
}
HRESULT CIntelliForms::CreatePStore()
{
if (!m_pPStore)
{
if (!m_hinstPStore)
{
m_hinstPStore = LoadLibrary(TEXT("PSTOREC.DLL"));
}
if (m_hinstPStore)
{
HRESULT (* pfn)(IPStore **, PST_PROVIDERID *, void *, DWORD) = NULL;
*(FARPROC *)&pfn = GetProcAddress(m_hinstPStore, "PStoreCreateInstance");
if (pfn)
{
pfn(&m_pPStore, NULL, NULL, 0);
}
}
}
return m_pPStore ? S_OK : E_FAIL;
}
void CIntelliForms::ReleasePStore()
{
SAFERELEASE(m_pPStore);
if (m_hinstPStore)
{
FreeLibrary(m_hinstPStore);
m_hinstPStore = NULL;
}
m_fPStoreTypeInit=FALSE;
}
HRESULT CIntelliForms::CreatePStoreAndType()
{
HRESULT hr;
hr = CreatePStore();
if (SUCCEEDED(hr) && !m_fPStoreTypeInit)
{
PST_TYPEINFO typeInfo;
typeInfo.cbSize = sizeof(typeInfo);
typeInfo.szDisplayName = (LPTSTR)c_szIntelliForms;
hr = m_pPStore->CreateType(PST_KEY_CURRENT_USER, &c_PStoreType, &typeInfo, 0);
if (hr == PST_E_TYPE_EXISTS)
{
hr = S_OK;
}
if (SUCCEEDED(hr))
{
hr = m_pPStore->CreateSubtype(PST_KEY_CURRENT_USER, &c_PStoreType, &m_guidUserId, &typeInfo, NULL, 0);
if (hr == PST_E_TYPE_EXISTS)
{
hr = S_OK;
}
}
if (SUCCEEDED(hr))
{
m_fPStoreTypeInit = TRUE;
}
}
return hr;
}
const WCHAR c_szBlob1Value[] = L"StringIndex";
const WCHAR c_szBlob2Value[] = L"StringData";
HRESULT CIntelliForms::WriteToStore(LPCWSTR pwszName, CStringList *psl)
{
HRESULT hr = E_FAIL;
TraceMsg(TF_IFORMS, "+WriteToStore");
if (SUCCEEDED(CreatePStoreAndType()))
{
LPBYTE pBlob1, pBlob2;
DWORD cbBlob1, cbBlob2;
if (SUCCEEDED(psl->WriteToBlobs(&pBlob1, &cbBlob1, &pBlob2, &cbBlob2)))
{
PST_PROMPTINFO promptInfo;
promptInfo.cbSize = sizeof(promptInfo);
promptInfo.dwPromptFlags = 0;
promptInfo.hwndApp = NULL;
promptInfo.szPrompt = NULL;
LPWSTR pwszValue;
int iValLen = lstrlenW(c_szBlob1Value) + lstrlenW(pwszName) + 10;
pwszValue = (LPWSTR) LocalAlloc(LMEM_FIXED, iValLen * sizeof(WCHAR));
if (pwszValue)
{
// Write Index
wnsprintfW(pwszValue, iValLen, L"%s:%s", pwszName, c_szBlob1Value);
hr = m_pPStore->WriteItem(PST_KEY_CURRENT_USER, &c_PStoreType, &m_guidUserId, pwszValue,
cbBlob1,
pBlob1,
&promptInfo, PST_CF_NONE, 0);
if (FAILED(hr))
{
TraceMsg(TF_WARNING | TF_IFORMS, "Failure writing Blob1 (Index). hr=%x", hr);
}
else
{
// Wrote Index successfully. Write data.
wnsprintfW(pwszValue, iValLen, L"%s:%s", pwszName, c_szBlob2Value);
hr = m_pPStore->WriteItem(PST_KEY_CURRENT_USER, &c_PStoreType, &m_guidUserId, pwszValue,
cbBlob2,
pBlob2,
&promptInfo, PST_CF_NONE, 0);
if (FAILED(hr))
{
// IE6#16676: This call failed on 64-bit Windows. Added a warning trace here to facilitate
// future debugging.
TraceMsg(TF_WARNING | TF_IFORMS, "Failure writing Blob2 (Data). hr=%x", hr);
}
}
// If *either* WriteItem failed, we really need to delete both the Index and the Data.
//
if (FAILED(hr))
{
// Delete bogus Blobs
// Delete Index Blob
wnsprintfW(pwszValue, iValLen, L"%s:%s", pwszName, c_szBlob1Value);
if (FAILED(m_pPStore->DeleteItem(PST_KEY_CURRENT_USER, &c_PStoreType, &m_guidUserId, pwszValue, &promptInfo, 0)))
{
TraceMsg(TF_ERROR | TF_IFORMS, "Failure deleting Blob1 (Index). hr=%x", hr);
}
// Delete Data Blob
wnsprintfW(pwszValue, iValLen, L"%s:%s", pwszName, c_szBlob2Value);
if (FAILED(m_pPStore->DeleteItem(PST_KEY_CURRENT_USER, &c_PStoreType, &m_guidUserId, pwszValue, &promptInfo, 0)))
{
TraceMsg(TF_ERROR | TF_IFORMS, "Failure deleting Blob2 (Data). hr=%x", hr);
}
}
LocalFree(pwszValue);
pwszValue = NULL;
}
if (pBlob1)
{
LocalFree(pBlob1);
pBlob1 = NULL;
}
if (pBlob2)
{
LocalFree(pBlob2);
pBlob2 = NULL;
}
}
}
TraceMsg(TF_IFORMS, "-WriteToStore");
return hr;
}
HRESULT CIntelliForms::ReadFromStore(LPCWSTR pwszName, CStringList **ppsl, BOOL fPasswordList/*=FALSE*/)
{
HRESULT hr = E_FAIL;
TraceMsg(TF_IFORMS, "+ReadFromStore");
*ppsl=NULL;
if (SUCCEEDED(CreatePStore()))
{
PST_PROMPTINFO promptInfo;
promptInfo.cbSize = sizeof(promptInfo);
promptInfo.dwPromptFlags = 0;
promptInfo.hwndApp = NULL;
promptInfo.szPrompt = NULL;
LPWSTR pwszValue;
int iValLen = lstrlenW(c_szBlob1Value) + lstrlenW(pwszName) + 10;
pwszValue = (LPWSTR) LocalAlloc(LMEM_FIXED, iValLen * sizeof(WCHAR));
if (pwszValue)
{
DWORD dwBlob1Size, dwBlob2Size;
LPBYTE pBlob1=NULL, pBlob2=NULL;
wnsprintfW(pwszValue, iValLen, L"%s:%s", pwszName, c_szBlob1Value);
hr = m_pPStore->ReadItem(PST_KEY_CURRENT_USER, &c_PStoreType, &m_guidUserId, pwszValue,
&dwBlob1Size,
&pBlob1,
&promptInfo, 0);
if (SUCCEEDED(hr))
{
wnsprintfW(pwszValue, iValLen, L"%s:%s", pwszName, c_szBlob2Value);
hr = m_pPStore->ReadItem(PST_KEY_CURRENT_USER, &c_PStoreType, &m_guidUserId, pwszValue,
&dwBlob2Size,
&pBlob2,
&promptInfo, 0);
if (SUCCEEDED(hr))
{
// bogus... have to reallocate here... bogus... bogus...
LPBYTE pBlob1b, pBlob2b;
pBlob1b=(LPBYTE)LocalAlloc(LMEM_FIXED, dwBlob1Size);
pBlob2b=(LPBYTE)LocalAlloc(LMEM_FIXED, dwBlob2Size);
if (pBlob1b && pBlob2b)
{
memcpy(pBlob1b, pBlob1, dwBlob1Size);
memcpy(pBlob2b, pBlob2, dwBlob2Size);
CStringList_New(ppsl);
if (*ppsl)
{
hr = (*ppsl)->ReadFromBlobs(&pBlob1b, dwBlob1Size, &pBlob2b, dwBlob2Size);
if (SUCCEEDED(hr))
{
INT64 i;
if (FAILED((*ppsl)->GetListData(&i)) ||
((fPasswordList && !(i & LIST_DATA_PASSWORD)) ||
(!fPasswordList && (i & LIST_DATA_PASSWORD))))
{
TraceMsg(TF_WARNING|TF_IFORMS, "IForms: Password/nonpassword lists mixed up");
hr = E_FAIL; // don't allow malicious site to access PW data
}
}
if (FAILED(hr))
{
delete *ppsl;
*ppsl=NULL;
}
}
}
else
{
if (pBlob1b)
{
LocalFree(pBlob1b);
pBlob1b = NULL;
}
if (pBlob2b)
{
LocalFree(pBlob2b);
pBlob2b = NULL;
}
}
}
else
{
TraceMsg(TF_IFORMS, "Failed reading Blob2. hr=%x", hr);
}
}
else
{
TraceMsg(TF_IFORMS, "Failed reading Blob1. hr=%x", hr);
}
LocalFree(pwszValue);
pwszValue = NULL;
if (pBlob1)
{
CoTaskMemFree(pBlob1);
pBlob1 = NULL;
}
if (pBlob2)
{
CoTaskMemFree(pBlob2);
pBlob2 = NULL;
}
}
}
TraceMsg(TF_IFORMS, "-ReadFromStore");
return hr;
}
HRESULT CIntelliForms::DeleteFromStore(LPCWSTR pwszName)
{
HRESULT hr=E_FAIL;
if (SUCCEEDED(CreatePStore()))
{
HRESULT hr1, hr2;
LPWSTR pwszValue;
int iValLen = lstrlenW(c_szBlob1Value) + lstrlenW(pwszName) + 10;
pwszValue = (LPWSTR) LocalAlloc(LMEM_FIXED, iValLen * sizeof(WCHAR));
if (pwszValue)
{
PST_PROMPTINFO promptInfo;
promptInfo.cbSize = sizeof(promptInfo);
promptInfo.dwPromptFlags = 0;
promptInfo.hwndApp = NULL;
promptInfo.szPrompt = NULL;
wnsprintfW(pwszValue, iValLen, L"%s:%s", pwszName, c_szBlob1Value);
hr1 = m_pPStore->DeleteItem(PST_KEY_CURRENT_USER, &c_PStoreType, &m_guidUserId, pwszValue, &promptInfo, 0);
wnsprintfW(pwszValue, iValLen, L"%s:%s", pwszName, c_szBlob2Value);
hr2 = m_pPStore->DeleteItem(PST_KEY_CURRENT_USER, &c_PStoreType, &m_guidUserId, pwszValue, &promptInfo, 0);
if (SUCCEEDED(hr1) && SUCCEEDED(hr2))
{
hr = S_OK;
}
LocalFree(pwszValue);
pwszValue = NULL;
}
}
return hr;
}
const int c_iEnumSize=256;
HRESULT CIntelliForms::ClearStore(DWORD dwClear)
{
BOOL fReleasePStore = (m_pPStore == NULL);
ASSERT(dwClear <= 2);
if (dwClear > 2)
{
return E_INVALIDARG;
}
if (SUCCEEDED(CreatePStoreAndType()))
{
IEnumPStoreItems *pEnumItems;
ULONG cFetched=0;
do
{
if (SUCCEEDED(m_pPStore->EnumItems(PST_KEY_CURRENT_USER, &c_PStoreType, &m_guidUserId, 0, &pEnumItems)))
{
LPWSTR pwszName[c_iEnumSize];
PST_PROMPTINFO promptInfo;
promptInfo.cbSize = sizeof(promptInfo);
promptInfo.dwPromptFlags = 0;
promptInfo.hwndApp = NULL;
promptInfo.szPrompt = NULL;
// Enumerator doesn't keep its state - deleting items while we enumerate makes us
// miss some. It does support celt>1... but returns failure codes when it succeeds.
cFetched = 0;
pEnumItems->Next(c_iEnumSize, pwszName, &cFetched);
if (cFetched)
{
for (ULONG i=0; i<cFetched; i++)
{
ASSERT(pwszName[i]);
if (pwszName[i])
{
BOOL fDelete = TRUE;
// Hack to work around PStore string-case bug: first take their
// enum value literally, then convert to lowercase and do it
// again; IE5.0 #71001
for (int iHack=0; iHack<2; iHack++)
{
if (iHack == 1)
{
// Convert the pwszName[i] to lowercase... only before
// the colon...
WCHAR *pwch = StrRChrW(pwszName[i], NULL, L':');
if (pwch)
{
*pwch = L'\0';
MyToLower(pwszName[i]);
*pwch = L':';
}
else
break;
}
if (dwClear != IECMDID_ARG_CLEAR_FORMS_ALL)
{
fDelete = FALSE;
// See if this is a password item or not
// This is pretty annoying. Since our string lists are split
// into two blobs, we need to find out which one this is and
// load the index for it.
WCHAR *pwch = StrRChrW(pwszName[i], NULL, L':');
if (pwch)
{
LPWSTR pwszIndexName=NULL;
if (!StrCmpCW(pwch+1, c_szBlob2Value))
{
int iSize = sizeof(WCHAR) * (lstrlenW(pwszName[i])+10);
pwszIndexName = (LPWSTR) LocalAlloc(LMEM_FIXED, iSize);
if (pwszIndexName)
{
*pwch = L'\0';
wnsprintfW(pwszIndexName, iSize, L"%s:%s", pwszName[i], c_szBlob1Value);
*pwch = L':';
}
}
DWORD dwBlob1Size;
LPBYTE pBlob1=NULL;
INT64 iFlags;
if (SUCCEEDED(m_pPStore->ReadItem(
PST_KEY_CURRENT_USER,
&c_PStoreType, &m_guidUserId,
(pwszIndexName) ? pwszIndexName : pwszName[i],
&dwBlob1Size,
&pBlob1,
&promptInfo, 0)) && pBlob1)
{
if (SUCCEEDED(CStringList::GetFlagsFromIndex(pBlob1, &iFlags)))
{
if (((iFlags & LIST_DATA_PASSWORD) && (dwClear == IECMDID_ARG_CLEAR_FORMS_PASSWORDS_ONLY)) ||
(!(iFlags & LIST_DATA_PASSWORD) && (dwClear == IECMDID_ARG_CLEAR_FORMS_ALL_BUT_PASSWORDS)))
{
// Delete this item
fDelete = TRUE;
}
}
CoTaskMemFree(pBlob1);
}
else
{
// The index is already deleted
fDelete = TRUE;
}
if (pwszIndexName)
{
LocalFree(pwszIndexName);
pwszIndexName = NULL;
}
}
} // if (dwClear != CLEAR_INTELLIFORMS_ALL)
if (fDelete)
{
m_pPStore->DeleteItem(PST_KEY_CURRENT_USER, &c_PStoreType, &m_guidUserId, pwszName[i], &promptInfo, 0);
}
} // for (iHack)
CoTaskMemFree(pwszName[i]);
} // if (pwszName[i])
}
}
pEnumItems->Release();
}
}
while (cFetched == c_iEnumSize); // In case we didn't cover everything in one pass
if (dwClear == IECMDID_ARG_CLEAR_FORMS_ALL)
{
m_pPStore->DeleteSubtype(PST_KEY_CURRENT_USER, &c_PStoreType, &m_guidUserId, 0);
m_pPStore->DeleteType(PST_KEY_CURRENT_USER, &c_PStoreType, 0);
}
if ((dwClear == IECMDID_ARG_CLEAR_FORMS_ALL) ||
(dwClear == IECMDID_ARG_CLEAR_FORMS_PASSWORDS_ONLY))
{
// Delete the urlhash key storing which urls we have passwords saved for
SHDeleteKey(HKEY_CURRENT_USER, c_szRegKeyIntelliForms);
}
TraceMsg(TF_IFORMS, "IForms: ClearStore cleared at least %d entries", cFetched);
}
if (fReleasePStore)
{
ReleasePStore();
}
return S_OK;
}
// static: Get the name from an input element - uses VCARD_NAME attribute if present.
HRESULT CIntelliForms::GetName(IHTMLInputTextElement *pTextEle, BSTR *pbstrName)
{
IHTMLElement *pEle=NULL;
*pbstrName = NULL;
pTextEle->QueryInterface(IID_IHTMLElement, (void **)&pEle);
if (pEle)
{
BSTR bstrAttr = SysAllocString(L"VCARD_NAME");
if (bstrAttr)
{
VARIANT var;
var.vt = VT_EMPTY;
pEle->getAttribute(bstrAttr, 0, &var);
if (var.vt == VT_BSTR && var.bstrVal)
{
*pbstrName = var.bstrVal;
}
else
{
VariantClear(&var);
}
SysFreeString(bstrAttr);
}
pEle->Release();
}
if (!*pbstrName)
{
pTextEle->get_name(pbstrName);
}
// Convert the name to lowercase
if (*pbstrName)
{
// Call "MyToLower" instead
if (g_fRunningOnNT)
{
CharLowerBuffW(*pbstrName, lstrlenW(*pbstrName));
}
else
{
// Ideally we would use the code page contained in the string instead of
// the system code page.
CHAR chBuf[MAX_PATH];
SHUnicodeToAnsi(*pbstrName, chBuf, ARRAYSIZE(chBuf));
CharLowerBuffA(chBuf, lstrlenA(chBuf));
SHAnsiToUnicode(chBuf, *pbstrName, SysStringLen(*pbstrName)+1);
}
}
return (*pbstrName) ? S_OK : E_FAIL;
}
// Called when script calls window.external.AutoCompleteSaveForm
HRESULT CIntelliForms::ScriptSubmit(IHTMLFormElement *pForm)
{
HRESULT hr = E_FAIL;
if (pForm)
{
hr = HandleFormSubmit(pForm);
}
return SUCCEEDED(hr) ? S_OK : S_FALSE;
}
// Called when user changes a text field. Mark it "dirty" and sink submit event for form
HRESULT CIntelliForms::UserInput(IHTMLInputTextElement *pTextEle)
{
AddToElementList(pTextEle);
IHTMLFormElement *pForm=NULL;
pTextEle->get_form(&pForm);
if (pForm)
{
if (S_OK == AddToFormList(pForm))
{
AttachToForm(pForm);
}
pForm->Release();
}
else
{
TraceMsg(TF_WARNING|TF_IFORMS, "Iforms: pITE->get_form() returned NULL!");
}
return S_OK;
}
HRESULT CIntelliForms::AddToElementList(IHTMLInputTextElement *pITE)
{
if (m_hdpaElements)
{
if (SUCCEEDED(FindInElementList(pITE)))
{
return S_FALSE;
}
}
else
{
m_hdpaElements = DPA_Create(4);
}
if (m_hdpaElements)
{
TraceMsg(TF_IFORMS, "CIntelliForms::AddToElementList adding");
if (DPA_AppendPtr(m_hdpaElements, pITE) >= 0)
{
pITE->AddRef();
return S_OK;
}
}
return E_OUTOFMEMORY;
}
HRESULT CIntelliForms::FindInElementList(IHTMLInputTextElement *pITE)
{
IUnknown *punk;
HRESULT hr = E_FAIL;
pITE->QueryInterface(IID_IUnknown, (void **)&punk);
if (m_hdpaElements)
{
for (int i=DPA_GetPtrCount(m_hdpaElements)-1; i>=0; i--)
{
IUnknown *punk2;
((IUnknown *)DPA_FastGetPtr(m_hdpaElements, i))->QueryInterface(IID_IUnknown, (void **)&punk2);
if (punk == punk2)
{
punk2->Release();
break;
}
punk2->Release();
}
if (i >= 0)
{
hr = S_OK;
}
}
punk->Release();
return hr;
}
void CIntelliForms::FreeElementList()
{
if (m_hdpaElements)
{
for (int i=DPA_GetPtrCount(m_hdpaElements)-1; i>=0; i--)
{
((IUnknown *)(DPA_FastGetPtr(m_hdpaElements, i)))->Release();
}
DPA_Destroy(m_hdpaElements);
m_hdpaElements=NULL;
}
}
HRESULT CIntelliForms::AddToFormList(IHTMLFormElement *pFormEle)
{
if (m_hdpaForms)
{
if (SUCCEEDED(FindInFormList(pFormEle)))
{
return S_FALSE;
}
}
else
{
m_hdpaForms = DPA_Create(2);
}
if (m_hdpaForms)
{
if (DPA_AppendPtr(m_hdpaForms, pFormEle) >= 0)
{
TraceMsg(TF_IFORMS, "CIntelliForms::AddToFormList adding");
pFormEle->AddRef();
return S_OK;
}
}
return E_OUTOFMEMORY;
}
HRESULT CIntelliForms::FindInFormList(IHTMLFormElement *pFormEle)
{
IUnknown *punk;
HRESULT hr = E_FAIL;
pFormEle->QueryInterface(IID_IUnknown, (void **)&punk);
if (m_hdpaForms)
{
for (int i=DPA_GetPtrCount(m_hdpaForms)-1; i>=0; i--)
{
IUnknown *punk2;
((IUnknown *)DPA_FastGetPtr(m_hdpaForms, i))->QueryInterface(IID_IUnknown, (void **)&punk2);
if (punk == punk2)
{
punk2->Release();
break;
}
punk2->Release();
}
if (i >= 0)
{
hr = S_OK;
}
}
punk->Release();
return hr;
}
void CIntelliForms::FreeFormList()
{
if (m_hdpaForms)
{
for (int i=DPA_GetPtrCount(m_hdpaForms)-1; i>=0; i--)
{
((IUnknown *)(DPA_FastGetPtr(m_hdpaForms, i)))->Release();
}
DPA_Destroy(m_hdpaForms);
m_hdpaForms = NULL;
}
}
//=========================================================================
//
// Event sinking class
//
// We simply implement IDispatch and make a call into our parent when
// we receive a sinked event.
//
//=========================================================================
CIntelliForms::CEventSink::CEventSink(CEventSinkCallback *pParent)
{
TraceMsg(TF_IFORMS, "CIntelliForms::CEventSink::CEventSink");
DllAddRef();
m_cRef = 1;
m_pParent = pParent;
}
CIntelliForms::CEventSink::~CEventSink()
{
TraceMsg(TF_IFORMS, "CIntelliForms::CEventSink::~CEventSink");
ASSERT( m_cRef == 0 );
DllRelease();
}
STDMETHODIMP CIntelliForms::CEventSink::QueryInterface(REFIID riid, void **ppv)
{
*ppv = NULL;
if ((IID_IDispatch == riid) ||
(IID_IUnknown == riid))
{
*ppv = (IDispatch *)this;
}
if (NULL != *ppv)
{
((IUnknown *)*ppv)->AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) CIntelliForms::CEventSink::AddRef(void)
{
return ++m_cRef;
}
STDMETHODIMP_(ULONG) CIntelliForms::CEventSink::Release(void)
{
if (--m_cRef == 0)
{
delete this;
return 0;
}
return m_cRef;
}
HRESULT CIntelliForms::CEventSink::SinkEvents(IHTMLElement2 *pEle2, int iNum, EVENTS *pEvents)
{
VARIANT_BOOL bSuccess = VARIANT_TRUE;
for (int i=0; i<iNum; i++)
{
BSTR bstrEvent = SysAllocString(CEventSinkCallback::EventsToSink[(int)(pEvents[i])].pwszEventSubscribe);
if (bstrEvent)
{
pEle2->attachEvent(bstrEvent, (IDispatch *)this, &bSuccess);
SysFreeString(bstrEvent);
}
else
{
bSuccess = VARIANT_FALSE;
}
if (!bSuccess)
break;
}
return (bSuccess) ? S_OK : E_FAIL;
}
HRESULT CIntelliForms::CEventSink::SinkEvents(IHTMLWindow3 *pWin3, int iNum, EVENTS *pEvents)
{
VARIANT_BOOL bSuccess = VARIANT_TRUE;
for (int i=0; i<iNum; i++)
{
BSTR bstrEvent = SysAllocString(CEventSinkCallback::EventsToSink[(int)(pEvents[i])].pwszEventSubscribe);
if (bstrEvent)
{
pWin3->attachEvent(bstrEvent, (IDispatch *)this, &bSuccess);
SysFreeString(bstrEvent);
}
else
{
bSuccess = VARIANT_FALSE;
}
if (!bSuccess)
break;
}
return (bSuccess) ? S_OK : E_FAIL;
}
HRESULT CIntelliForms::CEventSink::UnSinkEvents(IHTMLElement2 *pEle2, int iNum, EVENTS *pEvents)
{
for (int i=0; i<iNum; i++)
{
BSTR bstrEvent = SysAllocString(CEventSinkCallback::EventsToSink[(int)(pEvents[i])].pwszEventSubscribe);
if (bstrEvent)
{
pEle2->detachEvent(bstrEvent, (IDispatch *)this);
SysFreeString(bstrEvent);
}
}
return S_OK;
}
HRESULT CIntelliForms::CEventSink::UnSinkEvents(IHTMLWindow3 *pWin3, int iNum, EVENTS *pEvents)
{
for (int i=0; i<iNum; i++)
{
BSTR bstrEvent = SysAllocString(CEventSinkCallback::EventsToSink[(int)(pEvents[i])].pwszEventSubscribe);
if (bstrEvent)
{
pWin3->detachEvent(bstrEvent, (IDispatch *)this);
SysFreeString(bstrEvent);
}
}
return S_OK;
}
// IDispatch
STDMETHODIMP CIntelliForms::CEventSink::GetTypeInfoCount(UINT* /*pctinfo*/)
{
return E_NOTIMPL;
}
STDMETHODIMP CIntelliForms::CEventSink::GetTypeInfo(/* [in] */ UINT /*iTInfo*/,
/* [in] */ LCID /*lcid*/,
/* [out] */ ITypeInfo** /*ppTInfo*/)
{
return E_NOTIMPL;
}
STDMETHODIMP CIntelliForms::CEventSink::GetIDsOfNames(
REFIID riid,
OLECHAR** rgszNames,
UINT cNames,
LCID lcid,
DISPID* rgDispId)
{
return E_NOTIMPL;
}
STDMETHODIMP CIntelliForms::CEventSink::Invoke(
DISPID dispIdMember,
REFIID, LCID,
WORD wFlags,
DISPPARAMS* pDispParams,
VARIANT* pVarResult,
EXCEPINFO*,
UINT* puArgErr)
{
if (m_pParent && pDispParams && pDispParams->cArgs>=1)
{
if (pDispParams->rgvarg[0].vt == VT_DISPATCH)
{
IHTMLEventObj *pObj=NULL;
if (SUCCEEDED(pDispParams->rgvarg[0].pdispVal->QueryInterface(IID_IHTMLEventObj, (void **)&pObj) && pObj))
{
EVENTS Event=EVENT_BOGUS;
BSTR bstrEvent=NULL;
pObj->get_type(&bstrEvent);
if (bstrEvent)
{
for (int i=0; i<ARRAYSIZE(CEventSinkCallback::EventsToSink); i++)
{
if (!StrCmpCW(bstrEvent, CEventSinkCallback::EventsToSink[i].pwszEventName))
{
Event = (EVENTS) i;
break;
}
}
SysFreeString(bstrEvent);
}
if (Event != EVENT_BOGUS)
{
IHTMLElement *pEle=NULL;
pObj->get_srcElement(&pEle);
// EVENT_SCROLL comes from our window so we won't have an
// element for it
if (pEle || (Event == EVENT_SCROLL))
{
// Call the event handler here
m_pParent->HandleEvent(pEle, Event, pObj);
if (pEle)
{
pEle->Release();
}
}
}
pObj->Release();
}
}
}
return S_OK;
}
//=========================================================================
//
// Event sinking class
//
// We implement IHTMLEditDesigner and make a call into our parent when
// we receive any event.
//
//=========================================================================
CIntelliForms::CEditEventSink::CEditEventSink(CEditEventSinkCallback *pParent)
{
TraceMsg(TF_IFORMS, "CIntelliForms::CEditEventSink::CEditEventSink");
DllAddRef();
m_cRef = 1;
m_pParent = pParent;
}
CIntelliForms::CEditEventSink::~CEditEventSink()
{
TraceMsg(TF_IFORMS, "CIntelliForms::CEditEventSink::~CEditEventSink");
ASSERT(m_cRef == 0);
ASSERT(!m_pEditServices);
DllRelease();
}
STDMETHODIMP CIntelliForms::CEditEventSink::QueryInterface(REFIID riid, void **ppv)
{
if ((IID_IHTMLEditDesigner == riid) ||
(IID_IUnknown == riid))
{
*ppv = SAFECAST(this, IHTMLEditDesigner *);
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
STDMETHODIMP_(ULONG) CIntelliForms::CEditEventSink::AddRef(void)
{
return ++m_cRef;
}
STDMETHODIMP_(ULONG) CIntelliForms::CEditEventSink::Release(void)
{
if (--m_cRef == 0)
{
delete this;
return 0;
}
return m_cRef;
}
HRESULT CIntelliForms::CEditEventSink::Attach(IUnknown *punkElement)
{
HRESULT hr = S_OK;
// Detach from any existing element
if (m_pEditServices)
{
m_pEditServices->RemoveDesigner(this);
m_pEditServices->Release();
m_pEditServices = NULL;
}
// Attach to any new element
if (punkElement)
{
hr = E_FAIL;
IHTMLDocument2 *pDoc2 = NULL;
GetStuffFromEle(punkElement, NULL, &pDoc2);
if (pDoc2)
{
IServiceProvider *pSP = NULL;
pDoc2->QueryInterface(IID_IServiceProvider, (void **)&pSP);
if (pSP)
{
pSP->QueryService(SID_SHTMLEditServices, IID_IHTMLEditServices, (void **)&m_pEditServices);
pSP->Release();
}
if (m_pEditServices)
{
hr = m_pEditServices->AddDesigner(this);
}
pDoc2->Release();
}
}
return hr;
}
HRESULT CIntelliForms::CEditEventSink::PreHandleEvent(DISPID inEvtDispId, IHTMLEventObj *pIEventObj)
{
if (m_pParent)
{
return m_pParent->PreHandleEvent(inEvtDispId, pIEventObj);
}
return S_FALSE;
}
HRESULT CIntelliForms::CEditEventSink::PostHandleEvent(DISPID inEvtDispId, IHTMLEventObj *pIEventObj)
{
return S_FALSE;
}
HRESULT CIntelliForms::CEditEventSink::TranslateAccelerator(DISPID inEvtDispId, IHTMLEventObj *pIEventObj)
{
return S_FALSE;
}
//=========================================================================
//
// AutoSuggest class
//
// Handles connecting and disconnecting the AutoComplete object, as well
// as translating between Trident OM and Edit window messages
//=========================================================================
CIntelliForms::CAutoSuggest::CAutoSuggest(CIntelliForms *pParent, BOOL fEnabled, BOOL fEnabledPW)
{
TraceMsg(TF_IFORMS, "CIntelliForms::CAutoSuggest::CAutoSuggest");
m_pParent = pParent;
m_fEnabled = fEnabled;
m_fEnabledPW = fEnabledPW;
ASSERT(m_pEventSink == NULL);
ASSERT(m_pAutoComplete == NULL);
ASSERT(m_hwndEdit == NULL);
ASSERT(m_pTextEle == NULL);
//
// bug 81414 : To avoid clashing with app messages used by the edit window, we
// use registered messages.
//
m_uMsgItemActivate = RegisterWindowMessageA("AC_ItemActivate");
if (m_uMsgItemActivate == 0)
{
m_uMsgItemActivate = WM_APP + 301;
}
// Register our window class if necessary
if (!s_fRegisteredWndClass)
{
s_fRegisteredWndClass = TRUE;
WNDCLASSEXW wndclass =
{
sizeof(WNDCLASSEX),
0,
CIntelliForms::CAutoSuggest::WndProc,
0,
sizeof(DWORD_PTR),
g_hinst,
NULL,
NULL,
NULL,
NULL,
c_szEditWndClass
};
if (!RegisterClassEx(&wndclass))
{
TraceMsg(TF_IFORMS, "Intelliforms failed to register wnd class!");
}
}
}
CIntelliForms::CAutoSuggest::~CAutoSuggest()
{
TraceMsg(TF_IFORMS, "CIntelliForms::CAutoSuggest::~CAutoSuggest");
CleanUp();
}
HRESULT CIntelliForms::CAutoSuggest::CleanUp()
{
SetParent(NULL);
DetachFromInput();
return S_OK;
}
// List of all events we sink for an individual INPUT tag
// post IE5.5 we can use CEditEventSink instead of CEventSink for all of these events.
CEventSinkCallback::EVENTS CIntelliForms::CAutoSuggest::s_EventsToSink[] =
{
EVENT_KEYPRESS,
EVENT_KEYDOWN,
EVENT_MOUSEDOWN,
EVENT_DBLCLICK,
EVENT_FOCUS,
EVENT_BLUR,
};
HRESULT CIntelliForms::CAutoSuggest::AttachToInput(IHTMLInputTextElement *pTextEle)
{
HRESULT hr;
TraceMsg(TF_IFORMS, "CIntelliForms::CAutoSuggest::AttachToInput");
if (!pTextEle)
return E_INVALIDARG;
hr = DetachFromInput();
if (SUCCEEDED(hr))
{
m_pTextEle = pTextEle;
pTextEle->AddRef();
if (!m_pEventSink)
{
m_pEventSink = new CEventSink(this);
if (!m_pEventSink)
{
hr = E_OUTOFMEMORY;
}
}
if (SUCCEEDED(hr))
{
// Hook up our event sink
IHTMLElement2 *pEle2=NULL;
hr = pTextEle->QueryInterface(IID_IHTMLElement2, (void **)&pEle2);
if (pEle2)
{
hr = m_pEventSink->SinkEvents(pEle2, ARRAYSIZE(s_EventsToSink), s_EventsToSink);
pEle2->Release();
}
}
}
if (FAILED(hr))
{
TraceMsg(TF_IFORMS, "IForms: AttachToInput failed");
DetachFromInput();
}
return hr;
}
HRESULT CIntelliForms::CAutoSuggest::DetachFromInput()
{
if (!m_pTextEle)
{
return S_FALSE;
}
TraceMsg(TF_IFORMS, "CIntelliForms::CAutoSuggest::DetachFromInput");
// Auto Fill Password here, since we get ACTIVEELEMENT change before blur event
BSTR bstrUsername=NULL;
m_pTextEle->get_value(&bstrUsername);
if (bstrUsername)
{
CheckAutoFillPassword(bstrUsername);
SysFreeString(bstrUsername);
}
if (m_bstrLastUsername)
{
SysFreeString(m_bstrLastUsername);
m_bstrLastUsername=NULL;
}
if (m_hwndEdit)
{
// This is for subclass wndproc
SendMessage(m_hwndEdit, WM_KILLFOCUS, 0, 0);
}
if (m_pEnumString)
{
m_pEnumString->UnInit();
m_pEnumString->Release();
m_pEnumString = NULL;
}
if (m_pEventSink)
{
IHTMLElement2 *pEle2=NULL;
m_pTextEle->QueryInterface(IID_IHTMLElement2, (void **)&pEle2);
if (pEle2)
{
m_pEventSink->UnSinkEvents(pEle2, ARRAYSIZE(s_EventsToSink), s_EventsToSink);
pEle2->Release();
}
m_pEventSink->SetParent(NULL);
m_pEventSink->Release();
m_pEventSink=NULL;
}
SAFERELEASE(m_pAutoComplete);
SAFERELEASE(m_pAutoCompleteDD);
if (m_hwndEdit)
{
DestroyWindow(m_hwndEdit);
m_hwndEdit = NULL;
}
SAFERELEASE(m_pTextEle);
m_fInitAutoComplete = FALSE;
return S_OK;
}
// Creates autocomplete and string enumerator.
HRESULT CIntelliForms::CAutoSuggest::CreateAutoComplete()
{
if (m_fInitAutoComplete)
{
return (m_pAutoCompleteDD != NULL) ? S_OK : E_FAIL;
}
HRESULT hr = S_OK;
ASSERT(!m_hwndEdit && !m_pEnumString && !m_pAutoComplete && !m_pAutoCompleteDD);
// Create the edit window
#ifndef UNIX
m_hwndEdit = CreateWindowEx(0, c_szEditWndClass, TEXT("IntelliFormProxy"), WS_POPUP,
#else
m_hwndEdit = CreateWindowEx(WS_EX_MW_UNMANAGED_WINDOW, c_szEditWndClass, TEXT("IntelliFormProxy"), WS_POPUP,
#endif
300, 200, 200, 50, m_pParent->m_hwndBrowser, NULL, g_hinst, this);
if (!m_hwndEdit)
{
hr = E_OUTOFMEMORY;
}
if (SUCCEEDED(hr))
{
// Create our enumerator
m_pEnumString = new CEnumString();
if (m_pEnumString)
{
m_pEnumString->Init(m_pTextEle, m_pParent);
// Create the AutoComplete Object
if (!m_pAutoComplete)
{
hr = CoCreateInstance(CLSID_AutoComplete, NULL, CLSCTX_INPROC_SERVER, IID_IAutoComplete2, (void **)&m_pAutoComplete);
if (m_pAutoComplete)
{
m_pAutoComplete->QueryInterface(IID_IAutoCompleteDropDown, (void **)&m_pAutoCompleteDD);
if (!m_pAutoCompleteDD)
{
SAFERELEASE(m_pAutoComplete);
}
}
}
if (m_pAutoComplete)
{
hr = m_pAutoComplete->Init(m_hwndEdit, (IUnknown *) m_pEnumString, NULL, NULL);
DWORD dwOptions = ACO_AUTOSUGGEST | ACO_UPDOWNKEYDROPSLIST;
// Add the RTLREADING option to the dropdown, if the element is RTL
BSTR bstrDir = NULL;
IHTMLElement2 *pEle2=NULL;
m_pTextEle->QueryInterface(IID_IHTMLElement2, (void **)&pEle2);
if (pEle2)
{
pEle2->get_dir(&bstrDir);
pEle2->Release();
}
if (bstrDir)
{
if (!StrCmpIW(bstrDir, L"RTL"))
{
dwOptions |= ACO_RTLREADING;
}
SysFreeString(bstrDir);
}
m_pAutoComplete->SetOptions(dwOptions);
}
}
}
m_fInitAutoComplete = TRUE;
ASSERT_MSG(SUCCEEDED(hr), "IForms: CreateAutoComplete failed");
return hr;
}
void CIntelliForms::CAutoSuggest::CheckAutoFillPassword(LPCWSTR pwszUsername)
{
// We don't autofill their password unless we know they've hit a key
if (m_pParent && m_fEnabledPW && m_fAllowAutoFillPW)
{
if (m_bstrLastUsername && !StrCmpCW(pwszUsername, m_bstrLastUsername))
{
return;
}
SysFreeString(m_bstrLastUsername);
m_bstrLastUsername = SysAllocString(pwszUsername);
m_pParent->AutoFillPassword(m_pTextEle, pwszUsername);
}
}
HRESULT GetScreenCoordinates(IUnknown *punkEle, HWND hwnd, long *plLeft, long *plTop, long *plWidth, long *plHeight)
{
long lScreenLeft=0, lScreenTop=0;
HRESULT hr = E_FAIL;
*plLeft = *plTop = *plWidth = *plHeight = 0;
IHTMLElement2 *pEle2;
if (SUCCEEDED(punkEle->QueryInterface(IID_IHTMLElement2, (void **)&pEle2)) && pEle2)
{
IHTMLRect *pRect=NULL;
if (SUCCEEDED(pEle2->getBoundingClientRect(&pRect)) && pRect)
{
IHTMLWindow2 *pWin2;
long lLeft, lRight, lTop, lBottom;
pRect->get_left(&lLeft);
pRect->get_right(&lRight);
pRect->get_top(&lTop);
pRect->get_bottom(&lBottom);
lBottom -= 2; // put dropdown on top of edit box
if (lBottom < lTop)
{
lBottom = lTop;
}
if (lTop >= 0 && lLeft >= 0)
{
GetStuffFromEle(punkEle, &pWin2, NULL);
if (pWin2)
{
IHTMLWindow3 *pWin3;
if (SUCCEEDED(pWin2->QueryInterface(IID_IHTMLWindow3, (void **)&pWin3)) && pWin3)
{
IHTMLScreen *pScreen = NULL;
RECT rcBrowserWnd;
pWin3->get_screenLeft(&lScreenLeft);
pWin3->get_screenTop(&lScreenTop);
// GetClientRect & the screen_* APIs return document coordinates.
// We're position using device coordinates.
// Use document (currently 96DPI) and device resolutions & transform
pWin2->get_screen(&pScreen);
if (pScreen)
{
IHTMLScreen2 * pScreen2 = NULL;
if (SUCCEEDED(pScreen->QueryInterface(IID_IHTMLScreen2, (void **)&pScreen2)))
{
if (pScreen2)
{
long xDeviceDPI, yDeviceDPI, xLogicalDPI, yLogicalDPI;
pScreen2->get_deviceXDPI(&xDeviceDPI);
pScreen2->get_deviceYDPI(&yDeviceDPI);
pScreen2->get_logicalXDPI(&xLogicalDPI);
pScreen2->get_logicalYDPI(&yLogicalDPI);
lBottom = (lBottom * yDeviceDPI) / yLogicalDPI;
lTop = (lTop * yDeviceDPI) / yLogicalDPI;
lScreenTop = (lScreenTop * yDeviceDPI) / yLogicalDPI;
lLeft = (lLeft * xDeviceDPI) / xLogicalDPI;
lRight = (lRight * xDeviceDPI) / xLogicalDPI;
lScreenLeft = (lScreenLeft * xDeviceDPI) / xLogicalDPI;
pScreen2->Release();
}
}
pScreen->Release();
}
if (GetWindowRect(hwnd, &rcBrowserWnd))
{
// Clip the right edge to the window
if (lRight+lScreenLeft > rcBrowserWnd.right)
{
lRight = rcBrowserWnd.right - lScreenLeft;
}
*plLeft = lScreenLeft + lLeft;
*plWidth = lRight-lLeft;
*plTop = lScreenTop + lTop;
*plHeight = lBottom-lTop;
hr = S_OK;
if (*plWidth < MINIMUM_WIDTH)
{
// Primitive minimum width for now
*plWidth = MINIMUM_WIDTH;
}
}
pWin3->Release();
}
pWin2->Release();
}
}
pRect->Release();
}
pEle2->Release();
}
return hr;
}
HRESULT CIntelliForms::CAutoSuggest::UpdateDropdownPosition()
{
if (m_pTextEle && m_pParent && m_hwndEdit)
{
long lLeft, lTop, lWidth, lHeight;
if (SUCCEEDED(GetScreenCoordinates(m_pTextEle, m_pParent->m_hwndBrowser, &lLeft, &lTop, &lWidth, &lHeight)))
{
MoveWindow(m_hwndEdit, lLeft, lTop, lWidth, lHeight, FALSE);
}
else
{
// Send "escape" key to autocomplete so that it hides the dropdown.
// This will happen if dropdown moves outside of parent window, for example.
SendMessage(m_hwndEdit, IF_CHAR, (WPARAM) VK_ESCAPE, 0);
}
}
return S_OK;
}
HRESULT CIntelliForms::CAutoSuggest::HandleEvent(IHTMLElement *pEle, EVENTS Event, IHTMLEventObj *pEventObj)
{
TraceMsg(TF_IFORMS, "CIntelliForms::CAutoSuggest::HandleEvent Event=%ws", EventsToSink[Event].pwszEventName);
ASSERT(SHIsSameObject(pEle, m_pTextEle));
long lKey = 0;
BOOL fIsComposition = FALSE;
if (!m_pParent)
{
TraceMsg(TF_WARNING|TF_IFORMS, "IForms autosuggest receiving events while invalid");
return E_FAIL;
}
if (Event == EVENT_KEYPRESS || Event == EVENT_KEYDOWN)
{
pEventObj->get_keyCode(&lKey);
}
if (Event == EVENT_COMPOSITION)
{
fIsComposition = TRUE;
Event = EVENT_KEYPRESS; // Pretend to be a "keypress" for various processing below
}
if (Event == EVENT_NOTIFY)
{
// Send WM_IME_NOTIFY to AutoComplete so it can hide the dropdown
// if necessary
IHTMLEventObj3 *pObj3 = NULL;
pEventObj->QueryInterface(IID_PPV_ARG(IHTMLEventObj3, &pObj3));
if (pObj3)
{
LONG_PTR wParam = 0;
pObj3->get_imeNotifyCommand(&wParam);
SendMessage(m_hwndEdit, WM_IME_NOTIFY, (WPARAM)wParam, 0);
pObj3->Release();
}
return S_OK;
}
if (!m_fEnabled && !m_fEnabledPW)
{
// If the dropdown isn't enabled, our only purpose is to tell Intelliforms when
// user activity occurs for the first-time enable dialog box.
if (Event == EVENT_KEYPRESS && lKey != VK_TAB)
{
// Add this element to the master list so we save it when we submit
// and sink the submit event for this element's form
MarkDirty();
}
return S_OK;
}
if (Event == EVENT_KEYDOWN || Event == EVENT_KEYPRESS ||
Event == EVENT_MOUSEDOWN || Event == EVENT_DBLCLICK)
{
m_fAllowAutoFillPW = TRUE;
// Create our autocomplete object if it hasn't happened yet.
// If it's "tab" we don't create it; we're leaving the field
if (lKey != VK_TAB)
{
if (FAILED(CreateAutoComplete()))
return E_FAIL;
}
else
{
// Add this element to the master list so we save it when we submit
// and sink the submit event for this element's form
MarkDirty();
}
ASSERT((m_pEnumString && m_hwndEdit) || (lKey==VK_TAB));
}
// If AutoComplete hasn't been initialized there's nothing for us to do
if (!m_pAutoCompleteDD || !m_hwndEdit)
{
return E_FAIL;
}
// Update the position of our hidden edit box
long lLeft, lTop, lWidth, lHeight;
// call UpdateDropdownPosition instead
if (SUCCEEDED(GetScreenCoordinates(pEle, m_pParent->m_hwndBrowser, &lLeft, &lTop, &lWidth, &lHeight)))
{
MoveWindow(m_hwndEdit, lLeft, lTop, lWidth, lHeight, FALSE);
}
switch (Event)
{
case EVENT_FOCUS :
SendMessage(m_hwndEdit, WM_SETFOCUS, 0, 0);
break;
case EVENT_BLUR:
{
if (m_hwndEdit)
{
SendMessage(m_hwndEdit, WM_KILLFOCUS, 0, 0);
}
// ensure that script hasn't changed value of edit field?
BSTR bstrUsername=NULL;
m_pTextEle->get_value(&bstrUsername);
if (bstrUsername)
{
CheckAutoFillPassword(bstrUsername);
SysFreeString(bstrUsername);
}
}
break;
case EVENT_MOUSEDOWN:
case EVENT_DBLCLICK:
{
// If the dropdown is invisible, give AutoComplete a downarrow
long lButton=0;
pEventObj->get_button(&lButton);
if ((Event == EVENT_DBLCLICK) ||
(lButton & 1)) // Left button down?
{
DWORD dwFlags;
if (SUCCEEDED(m_pAutoCompleteDD->GetDropDownStatus(&dwFlags, NULL)) &&
!(dwFlags & ACDD_VISIBLE))
{
TraceMsg(TF_IFORMS, "IForms sending downarrow because of mouse click");
PostMessage(m_hwndEdit, IF_KEYDOWN, (WPARAM)VK_DOWN, 0);
m_fEscapeHit = FALSE;
}
}
}
break;
case EVENT_KEYPRESS:
{
// Add this element to the master list so we save it when we submit
// and sink the submit event for this element's form
MarkDirty();
// Ignore ctrl-enter (quickcomplete) (may be unnecessary)
if (lKey == VK_RETURN)
{
VARIANT_BOOL bCtrl;
if (SUCCEEDED(pEventObj->get_ctrlKey(&bCtrl)) && bCtrl)
{
lKey = 0;
}
}
if (lKey != 0)
{
if (lKey == m_lCancelKeyPress)
{
// tell MSHTML to ignore this keystroke (may be tab, enter, escape)
TraceMsg(TF_IFORMS, "Intelliforms cancelling default action for EVENT_KEYPRESS=%d", lKey);
VARIANT v;
v.vt = VT_BOOL;
v.boolVal = VARIANT_FALSE;
pEventObj->put_returnValue(v);
if(!(lKey == VK_DOWN || lKey == VK_UP))
pEventObj->put_cancelBubble(VARIANT_TRUE);
}
m_lCancelKeyPress = 0;
// Tell AutoComplete about this keystroke
if (!m_fEscapeHit)
{
PostMessage(m_hwndEdit, IF_CHAR, (WPARAM)lKey, 0);
}
}
if (fIsComposition)
{
// Tell AutoComplete about the new string. This must be a Post so that
// Trident handles the event before we send the WM_CHAR to browseui.
PostMessage(m_hwndEdit, IF_IME_COMPOSITION, 0, 0);
}
}
break;
case EVENT_KEYDOWN:
{
long lKey;
BOOL fCancelEvent=FALSE, // Cancel default MSHTML action?
fForwardKeystroke=TRUE; // Forward keystroke to AutoComplete?
pEventObj->get_keyCode(&lKey);
if (m_fEscapeHit)
{
// They dismissed the dropdown; don't bring it back unless they ask for it
if (lKey == VK_DOWN)
{
m_fEscapeHit = FALSE;
}
else
{
fForwardKeystroke = FALSE;
}
}
if (lKey != 0)
{
if ((lKey == VK_RETURN) || (lKey == VK_TAB))
{
fForwardKeystroke=FALSE;
LPWSTR pwszString=NULL;
if (SUCCEEDED(m_pAutoCompleteDD->GetDropDownStatus(NULL, &pwszString)) && pwszString)
{
// User is inside dropdown
fForwardKeystroke=TRUE;
// Set this value into our edit field
SetText(pwszString);
// We will fill in their password if they asked for it in m_uMsgItemActivate
if (lKey == VK_RETURN)
{
// Avoid submitting this form
fCancelEvent = TRUE;
}
CoTaskMemFree(pwszString);
}
else if (lKey == VK_RETURN)
{
// User's gonna submit. Give 'em their password first.
// ensure that script hasn't changed value of edit field?
BSTR bstrUsername=NULL;
m_pTextEle->get_value(&bstrUsername);
if (bstrUsername)
{
CheckAutoFillPassword(bstrUsername);
SysFreeString(bstrUsername);
}
}
}
else if (lKey == VK_DELETE)
{
LPWSTR pwszString=NULL;
if (SUCCEEDED(m_pAutoCompleteDD->GetDropDownStatus(NULL, &pwszString)) && pwszString)
{
// User is inside dropdown
fForwardKeystroke=FALSE;
// Delete this value from our string lists
CStringList *psl=NULL;
BSTR bstrName;
CIntelliForms::GetName(m_pTextEle, &bstrName);
if (bstrName)
{
int iIndex;
if (SUCCEEDED(m_pParent->ReadFromStore(bstrName, &psl)) &&
SUCCEEDED(psl->FindString(pwszString, -1, &iIndex, FALSE)))
{
TraceMsg(TF_IFORMS, "IForms: Deleting string \"%ws\"", pwszString);
psl->DeleteString(iIndex);
// We deleted string.
if (psl->NumStrings() > 0)
{
m_pParent->WriteToStore(bstrName, psl);
}
else
{
m_pParent->DeleteFromStore(bstrName);
}
}
}
SysFreeString(bstrName);
if (psl) delete psl;
// avoid deleting a character from the edit window; user was inside dropdown
fCancelEvent = TRUE;
// Check this url to see if we should maybe delete a password entry
m_pParent->DeletePassword(pwszString);
// Get AutoComplete to fill in the dropdown again
m_pEnumString->ResetEnum();
m_pAutoCompleteDD->ResetEnumerator();
CoTaskMemFree(pwszString);
}
}
if (lKey == VK_ESCAPE)
{
DWORD dwFlags;
if (SUCCEEDED(m_pAutoCompleteDD->GetDropDownStatus(&dwFlags, NULL)) &&
(dwFlags & ACDD_VISIBLE))
{
fCancelEvent = TRUE;
m_fEscapeHit = TRUE;
}
}
if (lKey == VK_DOWN || lKey == VK_UP)
{
// Cancel the MSHTML events. This will cause MSHTML to return
// S_OK instead of S_FALSE from its TranslateAccelerator, and we
// won't get multiple keystrokes in different panes
fCancelEvent = TRUE;
}
if (fForwardKeystroke)
{
PostMessage(m_hwndEdit, IF_KEYDOWN, lKey, 0);
if (lKey == VK_BACK)
{
// Never get OnKeyPress for this guy
PostMessage(m_hwndEdit, IF_CHAR, lKey, 0);
}
}
if (fCancelEvent)
{
TraceMsg(TF_IFORMS, "Intelliforms cancelling default action for EVENT_KEYDOWN=%d", lKey);
m_lCancelKeyPress = lKey; // Cancel the EVENT_KEYPRESS when it comes
VARIANT v;
v.vt = VT_BOOL;
v.boolVal = VARIANT_FALSE;
pEventObj->put_returnValue(v);
if(!(lKey == VK_DOWN || lKey == VK_UP))
pEventObj->put_cancelBubble(VARIANT_TRUE);
}
else
{
m_lCancelKeyPress = 0;
}
}
}
break;
}
return S_OK;
}
HRESULT CIntelliForms::CAutoSuggest::GetText(int cchTextMax, LPWSTR pszTextOut, LRESULT *lcchCopied)
{
*pszTextOut = TEXT('\0');
*lcchCopied = 0;
if (m_pTextEle)
{
BSTR bstr=NULL;
m_pTextEle->get_value(&bstr);
if (bstr)
{
StrCpyN(pszTextOut, bstr, cchTextMax);
*lcchCopied = lstrlenW(pszTextOut); // needed for NT
SysFreeString(bstr);
}
}
return (*pszTextOut) ? S_OK : E_FAIL;
}
HRESULT CIntelliForms::CAutoSuggest::GetTextLength(int *pcch)
{
*pcch = 0;
if (m_pTextEle)
{
BSTR bstr=NULL;
m_pTextEle->get_value(&bstr);
if (bstr)
{
*pcch = SysStringLen(bstr);
SysFreeString(bstr);
}
}
return S_OK;
}
HRESULT CIntelliForms::CAutoSuggest::SetText(LPCWSTR pszTextIn)
{
if (m_pTextEle && pszTextIn)
{
BSTR bstr=SysAllocString(pszTextIn);
if (bstr)
{
// Even though we know we already have this string in our dropdown, mark
// it as dirty so that we sink submit event; can be necessary in saved
// password situation.
MarkDirty();
// Make sure we don't put a string longer than the max length in this field
long lMaxLen=-1;
m_pTextEle->get_maxLength(&lMaxLen);
if ((lMaxLen >= 0) && (lstrlenW(bstr) > lMaxLen))
{
bstr[lMaxLen] = L'\0';
}
m_pTextEle->put_value(bstr);
SysFreeString(bstr);
}
}
TraceMsg(TF_IFORMS, "CIntelliForms::CAutoSuggest::SetText \"%ws\"", pszTextIn);
return S_OK;
}
#define MY_GWL_THISPTR 0
LRESULT CALLBACK CIntelliForms::CAutoSuggest::WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
CIntelliForms::CAutoSuggest *pThis = (CIntelliForms::CAutoSuggest *)GetWindowLongPtr(hwnd, MY_GWL_THISPTR);
switch (uMsg)
{
case WM_CREATE:
{
LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
if (!pcs || !(pcs->lpCreateParams))
{
return -1;
}
SetWindowLongPtr(hwnd, MY_GWL_THISPTR, (LONG_PTR) pcs->lpCreateParams);
return 0;
}
case WM_GETTEXT:
if (pThis)
{
LRESULT lcchCopied=0;
if (g_fRunningOnNT)
{
pThis->GetText((int)wParam, (LPWSTR) lParam, &lcchCopied);
}
else
{
// We are actually an ANSI window. Convert.
LPWSTR pwszOutBuf = (LPWSTR) LocalAlloc(LPTR, (wParam+1)*sizeof(WCHAR));
if (pwszOutBuf)
{
pThis->GetText((int)wParam, pwszOutBuf, &lcchCopied);
SHUnicodeToAnsi(pwszOutBuf, (LPSTR) lParam, (int)(wParam+1));
LocalFree((HLOCAL)pwszOutBuf);
pwszOutBuf = NULL;
}
}
return lcchCopied;
}
return 0;
case WM_GETTEXTLENGTH:
if (pThis)
{
int iLen;
pThis->GetTextLength(&iLen);
return iLen;
}
return 0;
case EM_GETSEL:
// Must return zeroes here or autocomp will use uninitialized
// values and crash
if (wParam) (*(DWORD *)wParam) = 0;
if (lParam) (*(DWORD *)lParam) = 0;
break;
case IF_IME_COMPOSITION:
// Forward a WM_CHAR. Autocomplete will notice that the rest of the string
// has changed if necessary (it does a GetText)
SendMessage(hwnd, WM_CHAR, 32, 0);
break;
case IF_CHAR:
SendMessage(hwnd, WM_CHAR, wParam, lParam);
break;
case IF_KEYDOWN:
SendMessage(hwnd, WM_KEYDOWN, wParam, lParam);
break;
case WM_KEYDOWN:
case WM_CHAR:
return 0; // eat it (see notes at top of file)
default:
// Check registered message
if (pThis && uMsg == pThis->m_uMsgItemActivate)
{
TraceMsg(TF_IFORMS, "IForms: Received AM_ITEMACTIVATE(WM_APP+2)");
pThis->SetText((LPCWSTR)lParam);
pThis->CheckAutoFillPassword((LPCWSTR)lParam);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 1;
}
CIntelliForms::CAutoSuggest::CEnumString::CEnumString()
{
// TraceMsg(TF_IFORMS, "CIntelliForms::CAutoSuggest::CEnumString::CEnumString");
DllAddRef();
InitializeCriticalSection(&m_crit);
m_cRef = 1;
}
CIntelliForms::CAutoSuggest::CEnumString::~CEnumString()
{
// TraceMsg(TF_IFORMS, "CIntelliForms::CAutoSuggest::CEnumString::~CEnumString");
if (m_pslMain)
{
delete m_pslMain;
}
SysFreeString(m_bstrName);
if (m_pszOpsValue)
{
CoTaskMemFree(m_pszOpsValue);
}
DeleteCriticalSection(&m_crit);
DllRelease();
}
HRESULT CIntelliForms::CAutoSuggest::CEnumString::Init(IHTMLInputTextElement *pInputEle, CIntelliForms *pIntelliForms)
{
if (m_fInit || // Can only init once
!pInputEle || !pIntelliForms) // Need both pointers
{
return E_FAIL;
}
m_fInit=TRUE;
m_pIntelliForms = pIntelliForms;
// Take care of things that must be done on the main thread. Autocomplete will
// call us on a secondary thread to do the enumeration.
CIntelliForms::GetName(pInputEle, &m_bstrName);
if (m_bstrName && m_bstrName[0])
{
// See if this specifies the "vcard." format
if (IsEnabledInCPL() &&
!StrCmpNICW(m_bstrName, c_wszVCardPrefix, ARRAYSIZE(c_wszVCardPrefix)-1))
{
// It does. Retrieve string from the profile assistant store.
IHTMLWindow2 *pWin2 = NULL;
IServiceProvider *pQS = NULL;
// QS up to get the shdocvw IHTMLWindow2 instead of NF trident's
pInputEle->QueryInterface(IID_IServiceProvider, (void **)&pQS);
if (pQS)
{
pQS->QueryService(IID_IHTMLWindow2, IID_IHTMLWindow2, (void **)&pWin2);
pQS->Release();
}
if (pWin2)
{
IOmNavigator *pNav=NULL;
pWin2->get_navigator(&pNav);
if (pNav)
{
IHTMLOpsProfile *pProfile=NULL;
pNav->get_userProfile(&pProfile);
if (pProfile)
{
IOpsProfileSimple *pSimple=NULL;
pProfile->QueryInterface(IID_IOpsProfileSimple, (void **)&pSimple);
if (pSimple)
{
pSimple->ReadProperties(1, &m_bstrName, &m_pszOpsValue);
pSimple->Release();
}
pProfile->Release();
}
pNav->Release();
}
pWin2->Release();
}
}
}
return S_OK;
}
void CIntelliForms::CAutoSuggest::CEnumString::UnInit()
{
EnterCriticalSection(&m_crit);
m_pIntelliForms = NULL;
LeaveCriticalSection(&m_crit);
}
HRESULT CIntelliForms::CAutoSuggest::CEnumString::ResetEnum()
{
EnterCriticalSection(&m_crit);
if (m_pslMain)
{
delete m_pslMain;
m_pslMain = NULL;
}
m_fFilledStrings = FALSE;
LeaveCriticalSection(&m_crit);
return S_OK;
}
STDMETHODIMP CIntelliForms::CAutoSuggest::CEnumString::QueryInterface(REFIID riid, void **ppv)
{
*ppv = NULL;
if ((IID_IEnumString == riid) ||
(IID_IUnknown == riid))
{
*ppv = (IEnumString *)this;
}
if (NULL != *ppv)
{
((IUnknown *)*ppv)->AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) CIntelliForms::CAutoSuggest::CEnumString::AddRef(void)
{
return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_(ULONG) CIntelliForms::CAutoSuggest::CEnumString::Release(void)
{
if (InterlockedDecrement(&m_cRef) != 0)
{
return 1;
}
delete this;
return 0;
}
STDMETHODIMP CIntelliForms::CAutoSuggest::CEnumString::Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched)
{
EnterCriticalSection(&m_crit);
if (!m_fFilledStrings)
{
FillEnumerator();
}
if (m_pslMain)
{
int iNewPtr = m_iPtr + celt;
if (iNewPtr > m_pslMain->NumStrings())
{
iNewPtr = m_pslMain->NumStrings();
}
*pceltFetched = iNewPtr - m_iPtr;
LPOLESTR lpstr;
for (; m_iPtr < iNewPtr; m_iPtr ++)
{
m_pslMain->GetTaskAllocString(m_iPtr, &lpstr);
if (!lpstr) break;
*(rgelt ++) = lpstr;
}
if (m_iPtr < iNewPtr)
{
*pceltFetched += (m_iPtr - iNewPtr);
}
}
LeaveCriticalSection(&m_crit);
if (!m_pslMain)
{
return E_FAIL;
}
return (*pceltFetched) ? S_OK : S_FALSE;
}
STDMETHODIMP CIntelliForms::CAutoSuggest::CEnumString::Reset()
{
EnterCriticalSection(&m_crit);
m_iPtr = 0;
LeaveCriticalSection(&m_crit);
return S_OK;
}
HRESULT CIntelliForms::CAutoSuggest::CEnumString::FillEnumerator()
{
// Already in critical section
ASSERT(!m_pslMain);
if (m_fFilledStrings)
{
return S_FALSE;
}
if (!m_bstrName || !m_bstrName[0] || !m_pIntelliForms)
{
return E_FAIL;
}
m_fFilledStrings = TRUE;
m_iPtr = 0;
// Fill the enumerator based on our name
TraceMsg(TF_IFORMS, "IForms: Intelliforms filling enumerator");
// Open any previously saved strings
if (!m_pIntelliForms->IsRestricted() &&
IsEnabledInCPL() &&
m_pIntelliForms->IsEnabledForPage())
{
m_pIntelliForms->ReadFromStore(m_bstrName, &m_pslMain);
// Add in profile assistant value, if any
if (m_pszOpsValue && m_pszOpsValue[0])
{
if (!m_pslMain)
{
CStringList_New(&m_pslMain);
}
else
{
// don't risk a scavenge (perf)
m_pslMain->SetMaxStrings(CStringList::MAX_STRINGS+4);
}
if (m_pslMain)
{
m_pslMain->AddString(m_pszOpsValue);
}
}
}
// Next fill with any usernames that have saved passwords
CStringList *pslPasswords;
if (!m_pIntelliForms->IsRestrictedPW() &&
CIntelliForms::IsEnabledRestorePW() &&
SUCCEEDED(m_pIntelliForms->GetPasswordStringList(&pslPasswords)))
{
ASSERT(!(pslPasswords->NumStrings() & 1));
FILETIME ft;
if (pslPasswords->NumStrings() > 0)
{
if (!m_pslMain)
{
CStringList_New(&m_pslMain);
}
else
{
// avoid expensive scavenging while adding usernames to string list
m_pslMain->SetMaxStrings(m_pslMain->GetMaxStrings() + pslPasswords->NumStrings()/2);
}
if (m_pslMain)
{
for (int i=0; i<pslPasswords->NumStrings(); i+=2)
{
if (SUCCEEDED(pslPasswords->GetStringTime(i, &ft)) &&
FILETIME_TO_INT(ft) != 0)
{
// We have a saved password for this username. Add username to enumerator.
m_pslMain->AddString(pslPasswords->GetString(i));
}
}
}
}
// do not delete pslPasswords
}
return (m_pslMain) ? ((m_pslMain->NumStrings()) ? S_OK : S_FALSE) : E_FAIL;
}
// Static helper. Pretty basic.
HRESULT CStringList_New(CStringList **ppNew, BOOL fAutoDelete/*=TRUE*/)
{
*ppNew = new CStringList();
if (*ppNew)
{
(*ppNew)->SetAutoScavenge(fAutoDelete);
}
return (*ppNew) ? S_OK : E_OUTOFMEMORY;
}
CStringList::CStringList()
{
TraceMsg(TF_IFORMS, "IForms: CStringList::CStringList");
m_fAutoScavenge = TRUE;
m_dwMaxStrings = MAX_STRINGS;
}
CStringList::~CStringList()
{
TraceMsg(TF_IFORMS, "IForms: CStringList::~CStringList");
CleanUp();
}
void CStringList::CleanUp()
{
if (m_psiIndex)
{
LocalFree(m_psiIndex);
m_psiIndex = NULL;
}
if (m_pBuffer)
{
LocalFree(m_pBuffer);
m_pBuffer = NULL;
}
m_dwIndexSize = 0;
m_dwBufEnd = m_dwBufSize = 0;
}
HRESULT CStringList::WriteToBlobs(LPBYTE *ppBlob1, DWORD *pcbBlob1, LPBYTE *ppBlob2, DWORD *pcbBlob2)
{
HRESULT hr = E_FAIL;
TraceMsg(TF_IFORMS, "+WriteToBlobs");
if (SUCCEEDED(Validate()))
{
DWORD dwIndexSize;
dwIndexSize = INDEX_SIZE(m_psiIndex->dwNumStrings);
ASSERT(dwIndexSize <= m_dwIndexSize);
*ppBlob1 = (LPBYTE) LocalAlloc(LMEM_FIXED, dwIndexSize);
if (*ppBlob1)
{
*ppBlob2 = (LPBYTE) LocalAlloc(LMEM_FIXED, m_dwBufEnd);
if (*ppBlob2)
{
memcpy(*ppBlob1, m_psiIndex, dwIndexSize);
*pcbBlob1=dwIndexSize;
memcpy(*ppBlob2, m_pBuffer, m_dwBufEnd);
*pcbBlob2=m_dwBufEnd;
hr = S_OK;
}
}
}
else
{
// Validate failed.
TraceMsg(TF_ERROR | TF_IFORMS, "Validate FAILED in WriteToBlobs");
*ppBlob1=NULL;
*ppBlob2=NULL;
}
if (FAILED(hr))
{
if (*ppBlob1)
{
LocalFree(*ppBlob1);
*ppBlob1=NULL;
}
if (*ppBlob2)
{
LocalFree(*ppBlob2);
*ppBlob2=NULL;
}
*pcbBlob1=0;
*pcbBlob2=0;
}
TraceMsg(TF_IFORMS, "-WriteToBlobs");
return hr;
}
// Take the blobs and use as our buffer
HRESULT CStringList::ReadFromBlobs(LPBYTE *ppBlob1, DWORD cbBlob1, LPBYTE *ppBlob2, DWORD cbBlob2)
{
HRESULT hr = E_FAIL;
TraceMsg(TF_IFORMS, "+ReadFromBlobs");
if (m_psiIndex)
{
TraceMsg(TF_IFORMS, "IForms: CStringList::ReadFromRegistry called with initialized instance.");
CleanUp();
}
// Allocate our buffers.
m_psiIndex = (StringIndex *) (*ppBlob1);
m_pBuffer = (LPBYTE) (*ppBlob2);
*ppBlob1 = NULL;
*ppBlob2 = NULL;
if (!m_psiIndex || !m_pBuffer || !cbBlob1 || !cbBlob2)
{
// Nothing to do
CleanUp();
return S_FALSE;
}
// Validate our string index.
if ((m_psiIndex->dwSignature == INDEX_SIGNATURE) &&
(m_psiIndex->cbSize == STRINGINDEX_CBSIZE) &&
(m_psiIndex->dwNumStrings <= MAX_STRINGS))
{
m_dwBufEnd = m_dwBufSize = cbBlob2;
m_dwIndexSize = cbBlob1;
if (SUCCEEDED(Validate()))
{
// Everything worked. Amazing.
hr = S_OK;
}
}
if (FAILED(hr))
{
// Release buffers if necessary.
CleanUp();
}
TraceMsg(TF_IFORMS, "-ReadFromBlobs");
return hr;
}
// static
HRESULT CStringList::GetFlagsFromIndex(LPBYTE pBlob1, INT64 *piFlags)
{
StringIndex *psiIndex = (StringIndex *)pBlob1;
if ((psiIndex->dwSignature == INDEX_SIGNATURE) &&
(psiIndex->cbSize == STRINGINDEX_CBSIZE))
{
*piFlags = psiIndex->iData;
return S_OK;
}
return E_FAIL;
}
HRESULT CStringList::Validate()
{
TraceMsg(TF_IFORMS, "+CStringList::Validate");
if (!m_psiIndex || !m_pBuffer)
{
return E_FAIL;
}
for (DWORD dw=0; dw < m_psiIndex->dwNumStrings; dw++)
{
DWORD dwPtr = m_psiIndex->StringEntry[dw].dwStringPtr;
DWORD dwSize = (GetStringLen(dw)+1) * sizeof(WCHAR);
if (dwPtr + dwSize > m_dwBufSize)
{
return E_FAIL;
}
}
TraceMsg(TF_IFORMS, "-CStringList::Validate");
return S_OK;
}
HRESULT CStringList::Init(DWORD dwBufSize /* =0 */)
{
DWORD dwMaxStrings=0;
DWORD dwIndexSize=0;
if (m_psiIndex)
{
TraceMsg(TF_IFORMS, "IForms: CStringList::Init called when already initialized");
CleanUp();
}
if (dwBufSize == 0)
{
dwBufSize = INIT_BUF_SIZE;
}
dwMaxStrings = dwBufSize >> 5; // this is relatively arbitrary but doesn't matter much
if (dwMaxStrings == 0)
dwMaxStrings = 1;
dwIndexSize = INDEX_SIZE(dwMaxStrings);
m_pBuffer = (LPBYTE)LocalAlloc(LMEM_FIXED, dwBufSize);
m_psiIndex = (StringIndex *)LocalAlloc(LMEM_FIXED, dwIndexSize);
if ((NULL == m_psiIndex) ||
(NULL == m_pBuffer))
{
TraceMsg(TF_IFORMS, "IForms: CStringList::Init memory allocation failed");
CleanUp();
return E_OUTOFMEMORY;
}
*((WCHAR *)m_pBuffer) = L'\0';
m_dwBufSize = dwBufSize;
m_dwBufEnd = 0;
m_psiIndex->dwSignature = INDEX_SIGNATURE;
m_psiIndex->cbSize = STRINGINDEX_CBSIZE;
m_psiIndex->dwNumStrings = 0;
m_psiIndex->iData = 0;
m_dwIndexSize = dwIndexSize;
TraceMsg(TF_IFORMS, "IForms: CStringList::Init succeeded");
return S_OK;
}
HRESULT CStringList::GetBSTR(int iIndex, BSTR *pbstrRet)
{
LPCWSTR lpwstr = GetString(iIndex);
if (!lpwstr)
{
*pbstrRet = NULL;
return E_INVALIDARG;
}
*pbstrRet = SysAllocString(lpwstr);
return (*pbstrRet) ? S_OK : E_OUTOFMEMORY;
}
HRESULT CStringList::GetTaskAllocString(int iIndex, LPOLESTR *pRet)
{
LPCWSTR lpwstr = GetString(iIndex);
if (!lpwstr)
{
*pRet = NULL;
return E_INVALIDARG;
}
DWORD dwSize = (GetStringLen(iIndex)+1) * sizeof(WCHAR);
*pRet = (LPOLESTR)CoTaskMemAlloc(dwSize);
if (!*pRet)
{
return E_OUTOFMEMORY;
}
memcpy(*pRet, lpwstr, dwSize);
return S_OK;
}
HRESULT CStringList::FindString(LPCWSTR lpwstr, int iLen, int *piNum, BOOL fCaseSensitive)
{
if (!m_psiIndex) return E_FAIL;
DWORD dw;
if (!lpwstr)
{
return E_INVALIDARG;
}
if (iLen <= 0)
{
iLen = lstrlenW(lpwstr);
}
if (piNum)
{
*piNum = -1;
}
for (dw=0; dw<m_psiIndex->dwNumStrings; dw++)
{
if (m_psiIndex->StringEntry[dw].dwStringLen == (DWORD)iLen)
{
if ((fCaseSensitive && (!StrCmpW(GetString(dw), lpwstr))) ||
(!fCaseSensitive && (!StrCmpIW(GetString(dw), lpwstr))))
{
// Match!
if (piNum)
{
*piNum = (int) dw;
}
return S_OK;
}
}
}
return E_FAIL; // Couldn't find it
}
// CStringList is not optimized for deleting
HRESULT CStringList::DeleteString(int iIndex)
{
TraceMsg(TF_IFORMS, "+DeleteString");
if (!m_psiIndex)
{
return E_FAIL;
}
if ((iIndex<0) || ((DWORD)iIndex >= m_psiIndex->dwNumStrings))
{
return E_INVALIDARG;
}
if ((DWORD)iIndex == (m_psiIndex->dwNumStrings-1))
{
// Simple case - deleting last string
m_dwBufEnd -= (sizeof(WCHAR) * (GetStringLen(iIndex) + 1));
m_psiIndex->dwNumStrings --;
return S_OK;
}
DWORD cbSizeDeleted;
LPCWSTR pwszString1, pwszString2;
pwszString1 = GetString(iIndex);
pwszString2 = GetString(iIndex+1);
// Size in bytes of string to be deleted including null terminator
cbSizeDeleted = (DWORD)((DWORD_PTR)pwszString2 - (DWORD_PTR)pwszString1);
ASSERT(cbSizeDeleted == (sizeof(WCHAR) * (lstrlenW(GetString(iIndex))+1)));
// Delete entry in index
memcpy(&(m_psiIndex->StringEntry[iIndex]), &(m_psiIndex->StringEntry[iIndex+1]),
STRINGENTRY_SIZE*(m_psiIndex->dwNumStrings - iIndex - 1));
m_psiIndex->dwNumStrings --;
// Delete string in buffer
memcpy((LPWSTR)pwszString1, pwszString2, m_dwBufEnd-(int)PtrDiff(pwszString2, m_pBuffer));
m_dwBufEnd -= cbSizeDeleted;
// Fix up pointers in index
for (int i=iIndex; (DWORD)i < m_psiIndex->dwNumStrings; i++)
{
m_psiIndex->StringEntry[i].dwStringPtr -= cbSizeDeleted;
}
TraceMsg(TF_IFORMS, "-DeleteString");
return S_OK;
}
HRESULT CStringList::InsertString(int iIndex, LPCWSTR lpwstr)
{
TraceMsg(TF_IFORMS, "+InsertString");
if (!m_psiIndex)
{
return E_FAIL;
}
if ((iIndex<0) || ((DWORD)iIndex > m_psiIndex->dwNumStrings))
{
return E_INVALIDARG;
}
if ((DWORD)iIndex == m_psiIndex->dwNumStrings)
{
// Simple case - inserting to end
return _AddString(lpwstr, FALSE, NULL);
}
DWORD dwLen = (DWORD)lstrlenW(lpwstr);
DWORD dwSizeInserted = sizeof(WCHAR) * (dwLen + 1);
if (FAILED(EnsureBuffer(m_dwBufEnd + dwSizeInserted)) ||
FAILED(EnsureIndex(m_psiIndex->dwNumStrings + 1)))
{
return E_OUTOFMEMORY;
}
// Insert into buffer
LPWSTR pwszBufLoc = GetStringPtr(iIndex);
memcpy((LPBYTE)pwszBufLoc + dwSizeInserted, pwszBufLoc, m_dwBufEnd - (int) PtrDiff(pwszBufLoc, m_pBuffer));
memcpy(pwszBufLoc, lpwstr, dwSizeInserted);
m_dwBufEnd += dwSizeInserted;
// Insert into index
memcpy(&(m_psiIndex->StringEntry[iIndex+1]), &(m_psiIndex->StringEntry[iIndex]),
STRINGENTRY_SIZE*(m_psiIndex->dwNumStrings - iIndex));
struct StringIndex::tagStringEntry *pse=&(m_psiIndex->StringEntry[iIndex]);
pse->dwStringPtr = (DWORD)PtrDiff(pwszBufLoc, m_pBuffer);
pse->ftLastSubmitted.dwLowDateTime = pse->ftLastSubmitted.dwHighDateTime = 0;
pse->dwStringLen = dwLen;
m_psiIndex->dwNumStrings ++;
// Fix up pointers after inserted string
for (int i=iIndex+1; (DWORD)i<m_psiIndex->dwNumStrings; i++)
{
m_psiIndex->StringEntry[i].dwStringPtr += dwSizeInserted;
}
TraceMsg(TF_IFORMS, "-InsertString");
return S_OK;
}
HRESULT CStringList::ReplaceString(int iIndex, LPCWSTR lpwstr)
{
TraceMsg(TF_IFORMS, "+ReplaceString");
if (!m_psiIndex)
{
return E_FAIL;
}
if ((iIndex<0) || ((DWORD)iIndex >= m_psiIndex->dwNumStrings))
{
return E_INVALIDARG;
}
if ((DWORD)lstrlenW(lpwstr) == m_psiIndex->StringEntry[iIndex].dwStringLen)
{
// Simple case - strings equal length
memcpy( GetStringPtr(iIndex),
lpwstr,
(m_psiIndex->StringEntry[iIndex].dwStringLen)*sizeof(WCHAR));
return S_OK;
}
// Delete old string, then insert new one
DeleteString(iIndex);
HRESULT hr = InsertString(iIndex, lpwstr);
TraceMsg(TF_IFORMS, "-ReplaceString");
return hr;
}
HRESULT CStringList::AddString(LPCWSTR lpwstr, FILETIME ft, int *piNum /*=NULL*/)
{
int iNum;
HRESULT hr;
TraceMsg(TF_IFORMS, "+AddString");
hr = _AddString(lpwstr, TRUE, &iNum);
if (piNum)
{
*piNum = iNum;
}
if (SUCCEEDED(hr))
{
UpdateStringTime(iNum, ft);
}
TraceMsg(TF_IFORMS, "-AddString");
return hr;
}
HRESULT CStringList::AddString(LPCWSTR lpwstr, int *piNum /*=NULL*/)
{
return _AddString(lpwstr, TRUE, piNum);
}
HRESULT CStringList::AppendString(LPCWSTR lpwstr, int *piNum /*=NULL*/)
{
return _AddString(lpwstr, FALSE, piNum);
}
HRESULT CStringList::AppendString(LPCWSTR lpwstr, FILETIME ft, int *piNum /*=NULL*/)
{
int iNum;
HRESULT hr;
hr = _AddString(lpwstr, FALSE, &iNum);
if (piNum)
{
*piNum = iNum;
}
if (SUCCEEDED(hr))
{
SetStringTime(iNum, ft);
}
return hr;
}
HRESULT CStringList::_AddString(LPCWSTR lpwstr, BOOL fCheckDuplicates, int *piNum)
{
DWORD dwSize, dwLen;
int iNum = -1;
WCHAR wchBufTruncated[MAX_URL_STRING];
LPCWSTR lpwstrTruncated=lpwstr;
TraceMsg(TF_IFORMS, "+_AddString");
if (piNum)
{
*piNum = -1;
}
if (!lpwstr)
{
return E_INVALIDARG;
}
if (!m_psiIndex)
{
if (FAILED(Init()))
{
return E_FAIL;
}
}
dwLen = (DWORD) lstrlenW(lpwstr);
// Explicitly truncate strings to MAX_URL characters. If we don't do this, browseui
// autocomplete code truncates it anyway and then we have problems removing
// duplicates and deleting these long strings. All IntelliForms code can handle
// arbitrary length strings.
if (dwLen >= ARRAYSIZE(wchBufTruncated))
{
StrCpyNW(wchBufTruncated, lpwstr, ARRAYSIZE(wchBufTruncated));
lpwstrTruncated = wchBufTruncated;
dwLen = lstrlenW(wchBufTruncated);
}
dwSize = (dwLen+1)*sizeof(WCHAR);
if (fCheckDuplicates && SUCCEEDED(FindString(lpwstrTruncated, (int)dwLen, &iNum, FALSE)))
{
if (piNum)
{
*piNum = iNum;
}
if (!StrCmpW(lpwstrTruncated, GetString(iNum)))
{
return S_FALSE; // String is an exact duplicate
}
// String is a duplicate but has different case. Replace.
ASSERT(m_psiIndex->StringEntry[iNum].dwStringLen == dwLen);
memcpy(GetStringPtr(iNum), lpwstrTruncated, dwSize);
return S_OK; // String was different in case
}
if (m_psiIndex->dwNumStrings >= m_dwMaxStrings)
{
if (m_fAutoScavenge)
{
// Remove the oldest string from our list.
DWORD dwIndex;
int iOldest=-1;
FILETIME ftOldest = { 0xFFFFFFFF, 0x7FFFFFFF };
for (dwIndex=0; dwIndex<m_psiIndex->dwNumStrings; dwIndex++)
{
if ((FILETIME_TO_INT(m_psiIndex->StringEntry[dwIndex].ftLastSubmitted) != 0) &&
(1 == CompareFileTime(&ftOldest, &m_psiIndex->StringEntry[dwIndex].ftLastSubmitted)))
{
ftOldest = m_psiIndex->StringEntry[dwIndex].ftLastSubmitted;
iOldest = (int)dwIndex;
}
}
if (iOldest != -1)
{
DeleteString(iOldest);
}
else
{
// User must not be setting string times.
return E_OUTOFMEMORY;
}
}
else
{
// Auto-scavenge is disabled.
return E_OUTOFMEMORY;
}
}
if (FAILED(EnsureBuffer(m_dwBufEnd + dwSize)) ||
FAILED(EnsureIndex(m_psiIndex->dwNumStrings + 1)))
{
return E_OUTOFMEMORY;
}
// Our buffers are large enough. Do it.
if (piNum)
{
*piNum = (int) m_psiIndex->dwNumStrings;
}
LPWSTR pwszNewString = (LPWSTR)(m_pBuffer + m_dwBufEnd);
memcpy(pwszNewString, lpwstrTruncated, dwSize);
m_dwBufEnd += dwSize;
struct StringIndex::tagStringEntry *pse=&(m_psiIndex->StringEntry[m_psiIndex->dwNumStrings]);
pse->dwStringPtr = (DWORD)PtrDiff(pwszNewString, m_pBuffer);
pse->ftLastSubmitted.dwLowDateTime = pse->ftLastSubmitted.dwHighDateTime = 0;
pse->dwStringLen = dwLen;
m_psiIndex->dwNumStrings ++;
TraceMsg(TF_IFORMS, "-_AddString");
return S_OK; // We added a new string
}
HRESULT CStringList::EnsureBuffer(DWORD dwSizeNeeded)
{
TraceMsg(TF_IFORMS, "+EnsureBuffer");
if (dwSizeNeeded <= m_dwBufSize)
{
return S_OK; // Already big enough
}
if (!m_pBuffer)
{
return E_FAIL;
}
DWORD dwNewBufSize = m_dwBufSize * 2;
// Grow buffer.
if (dwSizeNeeded > dwNewBufSize)
{
TraceMsg(TF_IFORMS, "IForms: StringList special growing size (big string)");
dwNewBufSize = dwSizeNeeded;
}
TraceMsg(TF_IFORMS, "IForms: CStringList growing");
LPBYTE pBuf = (LPBYTE)LocalReAlloc(m_pBuffer, dwNewBufSize, LMEM_MOVEABLE);
if (!pBuf)
{
TraceMsg(TF_IFORMS, "IForms: CStringList: ReAlloc failure");
// Realloc failure: our old memory is still present
return E_FAIL;
}
m_dwBufSize = dwNewBufSize;
m_pBuffer = pBuf;
TraceMsg(TF_IFORMS, "-EnsureBuffer");
// Successfully realloced to bigger buffer
return S_OK;
}
// grow psiIndex if needed
HRESULT CStringList::EnsureIndex(DWORD dwNumStringsNeeded)
{
TraceMsg(TF_IFORMS, "+EnsureIndex");
if (!m_psiIndex)
{
return E_FAIL;
}
if (INDEX_SIZE(dwNumStringsNeeded) > m_dwIndexSize)
{
DWORD dwNewMaxStrings = (m_psiIndex->dwNumStrings) * 2;
DWORD dwNewIndexSize = INDEX_SIZE(dwNewMaxStrings);
TraceMsg(TF_IFORMS, "IForms: CStringList growing max strings");
StringIndex *psiBuf =
(StringIndex *)LocalReAlloc(m_psiIndex, dwNewIndexSize, LMEM_MOVEABLE);
if (!psiBuf)
{
// Realloc failure: Old memory still present
TraceMsg(TF_IFORMS, "IForms: CStringList ReAlloc failure");
return E_OUTOFMEMORY;
}
// Success. Don't need to fix any pointers in index (buffer is unchanged)
m_psiIndex = psiBuf;
m_dwIndexSize = dwNewIndexSize;
}
TraceMsg(TF_IFORMS, "-EnsureIndex");
return S_OK;
}
// This dlg proc is used for password save, change, delete dialogs
INT_PTR AutoSuggestDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_INITDIALOG:
CenterWindow(hDlg, GetParent(hDlg));
SetWindowLongPtr(hDlg, DWLP_USER, (LONG_PTR) lParam);
if (lParam == IDD_AUTOSUGGEST_SAVEPASSWORD)
{
// For "Save" password we default to no. For "Change" and "Delete" we default to yes.
SetFocus(GetDlgItem(hDlg, IDNO));
return FALSE;
}
return TRUE;
case WM_COMMAND:
switch (GET_WM_COMMAND_ID(wParam, lParam))
{
case IDCANCEL: // close box
case IDYES: // yes button
case IDNO: // no button
if (IDD_AUTOSUGGEST_SAVEPASSWORD == GetWindowLongPtr(hDlg, DWLP_USER))
{
// Check the "don't ask me again" checkbox for the save password dlg
if (IsDlgButtonChecked(hDlg, IDC_AUTOSUGGEST_NEVER))
{
SHSetValue(HKEY_CURRENT_USER, c_szRegKeySMIEM, c_szRegValAskPasswords,
REG_SZ, c_szNo, sizeof(c_szNo));
}
}
EndDialog(hDlg, LOWORD(wParam));
return TRUE;
}
break;
#ifdef CHECKBOX_HELP
case WM_HELP:
// Only process WM_HELP for save password dlg
if (IDD_AUTOSUGGEST_SAVEPASSWORD == GetWindowLong(hDlg, DWL_USER))
{
SHWinHelpOnDemandWrap((HWND) ((LPHELPINFO) lParam)->hItemHandle, c_szHelpFile,
HELP_WM_HELP, (DWORD_PTR)(LPTSTR) c_aIFormsHelpIds);
}
break;
case WM_CONTEXTMENU: // right mouse click
// Only process WM_HELP for save password dlg
if (IDD_AUTOSUGGEST_SAVEPASSWORD == GetWindowLong(hDlg, DWL_USER))
{
SHWinHelpOnDemandWrap((HWND) wParam, c_szHelpFile, HELP_CONTEXTMENU,
(DWORD_PTR)(LPTSTR) c_aIFormsHelpIds);
}
break;
#endif
}
return FALSE;
}
//================================================================================
INT_PTR CALLBACK AskUserDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:
CenterWindow(hDlg, GetParent(hDlg));
Animate_OpenEx(GetDlgItem(hDlg, IDD_ANIMATE), HINST_THISDLL, MAKEINTRESOURCE(IDA_AUTOSUGGEST));
return TRUE;
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case IDC_AUTOSUGGEST_HELP:
SHHtmlHelpOnDemandWrap(GetParent(hDlg), TEXT("iexplore.chm > iedefault"),
HH_DISPLAY_TOPIC, (DWORD_PTR) TEXT("autocomp.htm"), ML_CROSSCODEPAGE);
break;
case IDYES:
case IDNO:
{
LPCTSTR pszData;
DWORD cbData;
DWORD dwData=0;
if (LOWORD(wParam) == IDYES)
{
pszData = c_szYes;
cbData = sizeof(c_szYes);
}
else
{
pszData = c_szNo;
cbData = sizeof(c_szNo);
}
// Write the enabled state into our CPL regkey
SHSetValue(HKEY_CURRENT_USER, c_szRegKeySMIEM, c_szRegValUseFormSuggest,
REG_SZ, pszData, cbData);
// Flag it as "asked user" so we don't ask them again
SHSetValue(HKEY_CURRENT_USER, c_szRegKeyIntelliForms, c_szRegValAskUser,
REG_DWORD, &dwData, sizeof(dwData));
}
// Fall through
case IDCANCEL:
{
EndDialog(hDlg, LOWORD(wParam));
}
break;
}
}
return TRUE;
case WM_DESTROY:
break;
}
return FALSE;
}