Home | History | Annotate | Download | only in chrome_frame
      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     auto_release_callback = callback;
    247     DCHECK_EQ(callback->m_dwRef, 1);
    248     hr = callback->Initialize(me, bind_ctx);
    249     DCHECK(SUCCEEDED(hr));
    250   }
    251 
    252   hr = original(me, bind_ctx, to_left, iid, obj);
    253 
    254   // If the binding terminates before the data could be played back
    255   // now is the chance. Sometimes OnStopBinding happens after this returns
    256   // and then it's too late.
    257   if ((S_OK == hr) && callback)
    258     callback->MayPlayBack(BSCF_LASTDATANOTIFICATION);
    259 
    260   return hr;
    261 }
    262 
    263