1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome_frame/urlmon_moniker.h" 6 7 #include <exdisp.h> 8 #include <shlguid.h> 9 10 #include "base/strings/string_util.h" 11 #include "base/strings/stringprintf.h" 12 #include "base/strings/utf_string_conversions.h" 13 #include "chrome_frame/bho.h" 14 #include "chrome_frame/bind_context_info.h" 15 #include "chrome_frame/chrome_active_document.h" 16 #include "chrome_frame/exception_barrier.h" 17 #include "chrome_frame/urlmon_bind_status_callback.h" 18 #include "chrome_frame/utils.h" 19 #include "chrome_frame/vtable_patch_manager.h" 20 #include "net/http/http_util.h" 21 22 static const int kMonikerBindToObject = 8; 23 static const int kMonikerBindToStorage = kMonikerBindToObject + 1; 24 25 base::LazyInstance<base::ThreadLocalPointer<NavigationManager> > 26 NavigationManager::thread_singleton_ = LAZY_INSTANCE_INITIALIZER; 27 28 BEGIN_VTABLE_PATCHES(IMoniker) 29 VTABLE_PATCH_ENTRY(kMonikerBindToObject, MonikerPatch::BindToObject) 30 VTABLE_PATCH_ENTRY(kMonikerBindToStorage, MonikerPatch::BindToStorage) 31 END_VTABLE_PATCHES() 32 33 //////////////////////////// 34 35 HRESULT NavigationManager::NavigateToCurrentUrlInCF(IBrowserService* browser) { 36 DCHECK(browser); 37 DVLOG(1) << __FUNCTION__ << " " << url(); 38 39 MarkBrowserOnThreadForCFNavigation(browser); 40 41 HRESULT hr = S_OK; 42 base::win::ScopedComPtr<IShellBrowser> shell_browser; 43 base::win::ScopedComPtr<IBindCtx> bind_context; 44 hr = ::CreateAsyncBindCtxEx(NULL, 0, NULL, NULL, bind_context.Receive(), 0); 45 46 base::win::ScopedComPtr<IMoniker> moniker; 47 DCHECK(bind_context); 48 if (SUCCEEDED(hr) && 49 SUCCEEDED(hr = ::CreateURLMonikerEx(NULL, url_.c_str(), moniker.Receive(), 50 URL_MK_UNIFORM))) { 51 if (SUCCEEDED(hr)) { 52 // If there's a referrer, preserve it. 53 std::wstring headers; 54 if (!referrer_.empty()) { 55 headers = base::StringPrintf(L"Referer: %ls\r\n\r\n", 56 ASCIIToWide(referrer_).c_str()); 57 } 58 59 // Pass in URL fragments if applicable. 60 std::wstring fragment; 61 GURL parsed_moniker_url(url_); 62 if (parsed_moniker_url.has_ref()) { 63 fragment = UTF8ToWide(parsed_moniker_url.ref()); 64 } 65 66 VARIANT flags = { VT_I4 }; 67 V_VT(&flags) = navNoHistory | navOpenInNewWindow; 68 69 hr = NavigateBrowserToMoniker(browser, moniker, headers.c_str(), 70 bind_context, fragment.c_str(), NULL, &flags); 71 DVLOG(1) << base::StringPrintf("NavigateBrowserToMoniker: 0x%08X", hr); 72 } 73 } 74 75 return hr; 76 } 77 78 bool NavigationManager::IsTopLevelUrl(const wchar_t* url) { 79 return CompareUrlsWithoutFragment(url_.c_str(), url); 80 } 81 82 ///////////////////////////////////////// 83 84 NavigationManager* NavigationManager::GetThreadInstance() { 85 return thread_singleton_.Pointer()->Get(); 86 } 87 88 void NavigationManager::RegisterThreadInstance() { 89 DCHECK(GetThreadInstance() == NULL); 90 thread_singleton_.Pointer()->Set(this); 91 } 92 93 void NavigationManager::UnregisterThreadInstance() { 94 DCHECK(GetThreadInstance() == this); 95 thread_singleton_.Pointer()->Set(NULL); 96 } 97 98 ///////////////////////////////////////// 99 100 // static 101 bool MonikerPatch::Initialize() { 102 if (IS_PATCHED(IMoniker)) { 103 DLOG(WARNING) << __FUNCTION__ << " called more than once."; 104 return true; 105 } 106 107 base::win::ScopedComPtr<IMoniker> moniker; 108 HRESULT hr = ::CreateURLMoniker(NULL, L"http://localhost/", 109 moniker.Receive()); 110 DCHECK(SUCCEEDED(hr)); 111 if (SUCCEEDED(hr)) { 112 hr = vtable_patch::PatchInterfaceMethods(moniker, IMoniker_PatchInfo); 113 DLOG_IF(ERROR, FAILED(hr)) << base::StringPrintf( 114 "patch failed 0x%08X", hr); 115 } 116 117 return SUCCEEDED(hr); 118 } 119 120 // static 121 void MonikerPatch::Uninitialize() { 122 vtable_patch::UnpatchInterfaceMethods(IMoniker_PatchInfo); 123 } 124 125 bool ShouldWrapCallback(IMoniker* moniker, REFIID iid, IBindCtx* bind_context) { 126 CComHeapPtr<WCHAR> url; 127 HRESULT hr = moniker->GetDisplayName(bind_context, NULL, &url); 128 if (!url) { 129 DVLOG(1) << __FUNCTION__ 130 << base::StringPrintf(" GetDisplayName failed. Error: 0x%x", hr); 131 return false; 132 } 133 134 if (!IsEqualIID(IID_IStream, iid)) { 135 DVLOG(1) << __FUNCTION__ << " Url: " << url 136 << " Not wrapping: IID is not IStream."; 137 return false; 138 } 139 140 base::win::ScopedComPtr<BindContextInfo> info; 141 BindContextInfo::FromBindContext(bind_context, info.Receive()); 142 DCHECK(info); 143 if (info && info->chrome_request()) { 144 DVLOG(1) << __FUNCTION__ << " Url: " << url 145 << " Not wrapping: request from chrome frame."; 146 return false; 147 } 148 149 NavigationManager* mgr = NavigationManager::GetThreadInstance(); 150 if (!mgr) { 151 DVLOG(1) << __FUNCTION__ << " Url: " << url 152 << " No navigation manager to wrap"; 153 return false; 154 } 155 156 // Check whether request comes from MSHTML by checking for IInternetBindInfo. 157 // We prefer to avoid wrapping if BindToStorage is called from AcroPDF.dll 158 // (as a result of OnObjectAvailable) 159 base::win::ScopedComPtr<IUnknown> bscb_holder; 160 if (S_OK == bind_context->GetObjectParam(L"_BSCB_Holder_", 161 bscb_holder.Receive())) { 162 base::win::ScopedComPtr<IBindStatusCallback> bscb; 163 if (S_OK != DoQueryService(IID_IBindStatusCallback, bscb_holder, 164 bscb.Receive())) 165 return false; 166 167 if (!bscb.get()) 168 return false; 169 170 base::win::ScopedComPtr<IInternetBindInfo> bind_info; 171 if (S_OK != bind_info.QueryFrom(bscb)) 172 return false; 173 } 174 175 // TODO(ananta) 176 // Use the IsSubFrameRequest function to determine if a request is a top 177 // level request. Something like this. 178 // base::win::ScopedComPtr<IUnknown> bscb_holder; 179 // bind_context->GetObjectParam(L"_BSCB_Holder_", bscb_holder.Receive()); 180 // if (bscb_holder) { 181 // base::win::ScopedComPtr<IHttpNegotiate> http_negotiate; 182 // http_negotiate.QueryFrom(bscb_holder); 183 // if (http_negotiate && !IsSubFrameRequest(http_negotiate)) 184 // return true; 185 // } 186 // There are some cases where the IsSubFrameRequest function can return 187 // incorrect results. 188 bool should_wrap = mgr->IsTopLevelUrl(url); 189 if (!should_wrap) { 190 DVLOG(1) << __FUNCTION__ << " Url: " << url 191 << " Not wrapping: Not top level url."; 192 } 193 return should_wrap; 194 } 195 196 // static 197 HRESULT MonikerPatch::BindToObject(IMoniker_BindToObject_Fn original, 198 IMoniker* me, IBindCtx* bind_ctx, 199 IMoniker* to_left, REFIID iid, void** obj) { 200 DVLOG(1) << __FUNCTION__; 201 DCHECK(to_left == NULL); 202 203 ExceptionBarrierReportOnlyModule barrier; 204 205 HRESULT hr = S_OK; 206 // Bind context is marked for switch when we sniff data in BSCBStorageBind 207 // and determine that the renderer to be used is Chrome. 208 base::win::ScopedComPtr<BindContextInfo> info; 209 BindContextInfo::FromBindContext(bind_ctx, info.Receive()); 210 DCHECK(info); 211 if (info) { 212 if (info->is_switching()) { 213 // We could implement the BindToObject ourselves here but instead we 214 // simply register Chrome Frame ActiveDoc as a handler for 'text/html' 215 // in this bind context. This makes urlmon instantiate CF Active doc 216 // instead of mshtml. 217 const char* media_types[] = { "text/html" }; 218 CLSID classes[] = { CLSID_ChromeActiveDocument }; 219 hr = RegisterMediaTypeClass(bind_ctx, arraysize(media_types), media_types, 220 classes, 0); 221 } else { 222 // In case the binding begins with BindToObject we do not need 223 // to cache the data in the sniffing code. 224 info->set_no_cache(true); 225 } 226 } 227 228 hr = original(me, bind_ctx, to_left, iid, obj); 229 return hr; 230 } 231 232 // static 233 HRESULT MonikerPatch::BindToStorage(IMoniker_BindToStorage_Fn original, 234 IMoniker* me, IBindCtx* bind_ctx, 235 IMoniker* to_left, REFIID iid, void** obj) { 236 DCHECK(to_left == NULL); 237 238 // Report a crash if the crash is in our own module. 239 ExceptionBarrierReportOnlyModule barrier; 240 241 HRESULT hr = S_OK; 242 scoped_refptr<BSCBStorageBind> auto_release_callback; 243 CComObject<BSCBStorageBind>* callback = NULL; 244 if (ShouldWrapCallback(me, iid, bind_ctx)) { 245 hr = CComObject<BSCBStorageBind>::CreateInstance(&callback); 246 DCHECK(SUCCEEDED(hr)); 247 auto_release_callback = callback; 248 DCHECK_EQ(callback->m_dwRef, 1); 249 hr = callback->Initialize(me, bind_ctx); 250 DCHECK(SUCCEEDED(hr)); 251 } 252 253 hr = original(me, bind_ctx, to_left, iid, obj); 254 255 // If the binding terminates before the data could be played back 256 // now is the chance. Sometimes OnStopBinding happens after this returns 257 // and then it's too late. 258 if ((S_OK == hr) && callback) 259 callback->MayPlayBack(BSCF_LASTDATANOTIFICATION); 260 261 return hr; 262 } 263 264