// Copyright (c) 2009 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome_frame/bho.h" #include #include #include "base/logging.h" #include "base/registry.h" #include "base/scoped_bstr_win.h" #include "base/scoped_comptr_win.h" #include "base/scoped_variant_win.h" #include "base/string_util.h" #include "chrome_tab.h" // NOLINT #include "chrome_frame/http_negotiate.h" #include "chrome_frame/protocol_sink_wrap.h" #include "chrome_frame/utils.h" #include "chrome_frame/vtable_patch_manager.h" #include "net/http/http_util.h" const wchar_t kPatchProtocols[] = L"PatchProtocols"; static const int kIBrowserServiceOnHttpEquivIndex = 30; base::LazyInstance > Bho::bho_current_thread_instance_(base::LINKER_INITIALIZED); PatchHelper g_patch_helper; BEGIN_VTABLE_PATCHES(IBrowserService) VTABLE_PATCH_ENTRY(kIBrowserServiceOnHttpEquivIndex, Bho::OnHttpEquiv) END_VTABLE_PATCHES() _ATL_FUNC_INFO Bho::kBeforeNavigate2Info = { CC_STDCALL, VT_EMPTY, 7, { VT_DISPATCH, VT_VARIANT | VT_BYREF, VT_VARIANT | VT_BYREF, VT_VARIANT | VT_BYREF, VT_VARIANT | VT_BYREF, VT_VARIANT | VT_BYREF, VT_BOOL | VT_BYREF } }; Bho::Bho() { } STDMETHODIMP Bho::SetSite(IUnknown* site) { HRESULT hr = S_OK; if (site) { ScopedComPtr web_browser2; web_browser2.QueryFrom(site); if (web_browser2) { hr = DispEventAdvise(web_browser2, &DIID_DWebBrowserEvents2); DCHECK(SUCCEEDED(hr)) << "DispEventAdvise failed. Error: " << hr; } if (g_patch_helper.state() == PatchHelper::PATCH_IBROWSER) { ScopedComPtr browser_service; hr = DoQueryService(SID_SShellBrowser, site, browser_service.Receive()); DCHECK(browser_service) << "DoQueryService - SID_SShellBrowser failed." << " Site: " << site << " Error: " << hr; if (browser_service) { g_patch_helper.PatchBrowserService(browser_service); DCHECK(SUCCEEDED(hr)) << "vtable_patch::PatchInterfaceMethods failed." << " Site: " << site << " Error: " << hr; } } // Save away our BHO instance in TLS which enables it to be referenced by // our active document/activex instances to query referrer and other // information for a URL. AddRef(); bho_current_thread_instance_.Pointer()->Set(this); } else { bho_current_thread_instance_.Pointer()->Set(NULL); Release(); } return IObjectWithSiteImpl::SetSite(site); } STDMETHODIMP Bho::BeforeNavigate2(IDispatch* dispatch, VARIANT* url, VARIANT* flags, VARIANT* target_frame_name, VARIANT* post_data, VARIANT* headers, VARIANT_BOOL* cancel) { ScopedComPtr web_browser2; if (dispatch) web_browser2.QueryFrom(dispatch); if (!web_browser2) { NOTREACHED() << "Can't find WebBrowser2 with given dispatch"; return S_OK; // Return success, we operate on best effort basis. } DLOG(INFO) << "BeforeNavigate2: " << url->bstrVal; if (g_patch_helper.state() == PatchHelper::PATCH_IBROWSER) { VARIANT_BOOL is_top_level = VARIANT_FALSE; web_browser2->get_TopLevelContainer(&is_top_level); std::wstring current_url; bool is_chrome_protocol = false; if (is_top_level && IsValidUrlScheme(url->bstrVal, false)) { current_url.assign(url->bstrVal, SysStringLen(url->bstrVal)); is_chrome_protocol = StartsWith(current_url, kChromeProtocolPrefix, false); if (!is_chrome_protocol && IsOptInUrl(current_url.c_str())) { DLOG(INFO) << "Canceling navigation and switching to cf"; // Cancel original navigation *cancel = VARIANT_TRUE; // Issue new request with 'cf:' current_url.insert(0, kChromeProtocolPrefix); ScopedVariant new_url(current_url.c_str()); HRESULT hr = web_browser2->Navigate2(new_url.AsInput(), flags, target_frame_name, post_data, headers); DCHECK(SUCCEEDED(hr)) << "web_browser2->Navigate2 failed. Error: " << hr << std::endl << "Url: " << current_url << std::endl << "flags: " << flags << std::endl << "post data: " << post_data << std::endl << "headers: " << headers; } } } referrer_.clear(); // Save away the referrer in case our active document needs it to initiate // navigation in chrome. if (headers && V_VT(headers) == VT_BSTR && headers->bstrVal != NULL) { std::string raw_headers_utf8 = WideToUTF8(headers->bstrVal); std::string http_headers = net::HttpUtil::AssembleRawHeaders(raw_headers_utf8.c_str(), raw_headers_utf8.length()); net::HttpUtil::HeadersIterator it(http_headers.begin(), http_headers.end(), "\r\n"); while (it.GetNext()) { if (LowerCaseEqualsASCII(it.name(), "referer")) { referrer_ = it.values(); break; } } } return S_OK; } HRESULT Bho::FinalConstruct() { return S_OK; } void Bho::FinalRelease() { } HRESULT STDMETHODCALLTYPE Bho::OnHttpEquiv( IBrowserService_OnHttpEquiv_Fn original_httpequiv, IBrowserService* browser, IShellView* shell_view, BOOL done, VARIANT* in_arg, VARIANT* out_arg) { if (!done && in_arg && (VT_BSTR == V_VT(in_arg))) { if (StrStrI(V_BSTR(in_arg), kChromeContentPrefix)) { // OnHttpEquiv is invoked for meta tags within sub frames as well. // We want to switch renderers only for the top level frame. Since // the same |browser| and |shell_view| are passed in to OnHttpEquiv // even for sub iframes, we determine if this is the top one by // checking if there are any sub frames created or not. ScopedComPtr web_browser2; DoQueryService(SID_SWebBrowserApp, browser, web_browser2.Receive()); if (web_browser2 && !HasSubFrames(web_browser2)) SwitchRenderer(web_browser2, browser, shell_view, V_BSTR(in_arg)); } } return original_httpequiv(browser, shell_view, done, in_arg, out_arg); } bool Bho::HasSubFrames(IWebBrowser2* web_browser2) { bool has_sub_frames = false; ScopedComPtr doc_dispatch; if (web_browser2) { HRESULT hr = web_browser2->get_Document(doc_dispatch.Receive()); DCHECK(SUCCEEDED(hr) && doc_dispatch) << "web_browser2->get_Document failed. Error: " << hr; ScopedComPtr container; if (SUCCEEDED(hr) && doc_dispatch) { container.QueryFrom(doc_dispatch); ScopedComPtr enumerator; if (container) { container->EnumObjects(OLECONTF_EMBEDDINGS, enumerator.Receive()); ScopedComPtr unk; ULONG items_retrieved = 0; if (enumerator) enumerator->Next(1, unk.Receive(), &items_retrieved); has_sub_frames = (items_retrieved != 0); } } } return has_sub_frames; } HRESULT Bho::SwitchRenderer(IWebBrowser2* web_browser2, IBrowserService* browser, IShellView* shell_view, const wchar_t* meta_tag) { DCHECK(web_browser2 && browser && shell_view && meta_tag); // Get access to the mshtml instance and the moniker ScopedComPtr mshtml_ole_object; HRESULT hr = shell_view->GetItemObject(SVGIO_BACKGROUND, IID_IOleObject, reinterpret_cast(mshtml_ole_object.Receive())); if (!mshtml_ole_object) { NOTREACHED() << "shell_view->GetItemObject failed. Error: " << hr; return hr; } std::wstring url; ScopedComPtr moniker; hr = mshtml_ole_object->GetMoniker(OLEGETMONIKER_ONLYIFTHERE, OLEWHICHMK_OBJFULL, moniker.Receive()); DCHECK(moniker) << "mshtml_ole_object->GetMoniker failed. Error: " << hr; if (moniker) hr = GetUrlFromMoniker(moniker, NULL, &url); DCHECK(!url.empty()) << "GetUrlFromMoniker failed. Error: " << hr; DCHECK(!StartsWith(url, kChromeProtocolPrefix, false)); if (!url.empty()) { url.insert(0, kChromeProtocolPrefix); // Navigate to new url VARIANT empty = ScopedVariant::kEmptyVariant; VARIANT flags = { VT_I4 }; V_I4(&flags) = 0; ScopedVariant url_var(url.c_str()); hr = web_browser2->Navigate2(url_var.AsInput(), &flags, &empty, &empty, &empty); DCHECK(SUCCEEDED(hr)) << "web_browser2->Navigate2 failed. Error: " << hr << std::endl << "Url: " << url; } return S_OK; } Bho* Bho::GetCurrentThreadBhoInstance() { DCHECK(bho_current_thread_instance_.Pointer()->Get() != NULL); return bho_current_thread_instance_.Pointer()->Get(); } bool PatchHelper::InitializeAndPatchProtocolsIfNeeded() { bool ret = false; _pAtlModule->m_csStaticDataInitAndTypeInfo.Lock(); if (state_ == UNKNOWN) { HttpNegotiatePatch::Initialize(); bool patch_protocol = GetConfigBool(true, kPatchProtocols); if (patch_protocol) { ProtocolSinkWrap::PatchProtocolHandlers(); state_ = PATCH_PROTOCOL; } else { state_ = PATCH_IBROWSER; } ret = true; } _pAtlModule->m_csStaticDataInitAndTypeInfo.Unlock(); return ret; } void PatchHelper::PatchBrowserService(IBrowserService* browser_service) { DCHECK(state_ == PATCH_IBROWSER); state_ = PATCH_IBROWSER_OK; vtable_patch::PatchInterfaceMethods(browser_service, IBrowserService_PatchInfo); } void PatchHelper::UnpatchIfNeeded() { if (state_ == PATCH_PROTOCOL) { ProtocolSinkWrap::UnpatchProtocolHandlers(); } else if (state_ == PATCH_IBROWSER_OK) { vtable_patch::UnpatchInterfaceMethods(IBrowserService_PatchInfo); } HttpNegotiatePatch::Uninitialize(); state_ = UNKNOWN; }