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 <htiframe.h>
      6 #include <mshtml.h>
      7 
      8 #include "chrome_frame/protocol_sink_wrap.h"
      9 
     10 #include "base/logging.h"
     11 #include "base/memory/singleton.h"
     12 #include "base/strings/string_number_conversions.h"
     13 #include "base/strings/string_util.h"
     14 #include "base/strings/stringprintf.h"
     15 #include "base/strings/utf_string_conversions.h"
     16 #include "base/win/scoped_bstr.h"
     17 #include "chrome_frame/bho.h"
     18 #include "chrome_frame/bind_context_info.h"
     19 #include "chrome_frame/exception_barrier.h"
     20 #include "chrome_frame/function_stub.h"
     21 #include "chrome_frame/policy_settings.h"
     22 #include "chrome_frame/utils.h"
     23 
     24 // BINDSTATUS_SERVER_MIMETYPEAVAILABLE == 54. Introduced in IE 8, so
     25 // not in everyone's headers yet. See:
     26 // http://msdn.microsoft.com/en-us/library/ms775133(VS.85,loband).aspx
     27 #ifndef BINDSTATUS_SERVER_MIMETYPEAVAILABLE
     28 #define BINDSTATUS_SERVER_MIMETYPEAVAILABLE 54
     29 #endif
     30 
     31 bool ProtocolSinkWrap::ignore_xua_ = false;
     32 
     33 static const char kTextHtmlMimeType[] = "text/html";
     34 const wchar_t kUrlMonDllName[] = L"urlmon.dll";
     35 
     36 static const int kInternetProtocolStartIndex = 3;
     37 static const int kInternetProtocolReadIndex = 9;
     38 static const int kInternetProtocolStartExIndex = 13;
     39 static const int kInternetProtocolLockRequestIndex = 11;
     40 static const int kInternetProtocolUnlockRequestIndex = 12;
     41 static const int kInternetProtocolAbortIndex = 5;
     42 static const int kInternetProtocolTerminateIndex = 6;
     43 
     44 
     45 // IInternetProtocol/Ex patches.
     46 STDMETHODIMP Hook_Start(InternetProtocol_Start_Fn orig_start,
     47                         IInternetProtocol* protocol,
     48                         LPCWSTR url,
     49                         IInternetProtocolSink* prot_sink,
     50                         IInternetBindInfo* bind_info,
     51                         DWORD flags,
     52                         HANDLE_PTR reserved);
     53 
     54 STDMETHODIMP Hook_StartEx(InternetProtocol_StartEx_Fn orig_start_ex,
     55                           IInternetProtocolEx* protocol,
     56                           IUri* uri,
     57                           IInternetProtocolSink* prot_sink,
     58                           IInternetBindInfo* bind_info,
     59                           DWORD flags,
     60                           HANDLE_PTR reserved);
     61 
     62 STDMETHODIMP Hook_Read(InternetProtocol_Read_Fn orig_read,
     63                        IInternetProtocol* protocol,
     64                        void* buffer,
     65                        ULONG size,
     66                        ULONG* size_read);
     67 
     68 STDMETHODIMP Hook_LockRequest(InternetProtocol_LockRequest_Fn orig_req,
     69                               IInternetProtocol* protocol,
     70                               DWORD options);
     71 
     72 STDMETHODIMP Hook_UnlockRequest(InternetProtocol_UnlockRequest_Fn orig_req,
     73                                 IInternetProtocol* protocol);
     74 
     75 STDMETHODIMP Hook_Abort(InternetProtocol_Abort_Fn orig_req,
     76                         IInternetProtocol* protocol,
     77                         HRESULT hr,
     78                         DWORD options);
     79 
     80 STDMETHODIMP Hook_Terminate(InternetProtocol_Terminate_Fn orig_req,
     81                             IInternetProtocol* protocol,
     82                             DWORD options);
     83 
     84 /////////////////////////////////////////////////////////////////////////////
     85 BEGIN_VTABLE_PATCHES(CTransaction)
     86   VTABLE_PATCH_ENTRY(kInternetProtocolStartIndex, Hook_Start)
     87   VTABLE_PATCH_ENTRY(kInternetProtocolReadIndex, Hook_Read)
     88   VTABLE_PATCH_ENTRY(kInternetProtocolLockRequestIndex, Hook_LockRequest)
     89   VTABLE_PATCH_ENTRY(kInternetProtocolUnlockRequestIndex, Hook_UnlockRequest)
     90   VTABLE_PATCH_ENTRY(kInternetProtocolAbortIndex, Hook_Abort)
     91   VTABLE_PATCH_ENTRY(kInternetProtocolTerminateIndex, Hook_Terminate)
     92 END_VTABLE_PATCHES()
     93 
     94 BEGIN_VTABLE_PATCHES(CTransaction2)
     95   VTABLE_PATCH_ENTRY(kInternetProtocolStartExIndex, Hook_StartEx)
     96 END_VTABLE_PATCHES()
     97 
     98 //
     99 // ProtocolSinkWrap implementation
    100 
    101 // Static map initialization
    102 ProtData::ProtocolDataMap ProtData::datamap_;
    103 base::Lock ProtData::datamap_lock_;
    104 
    105 ProtocolSinkWrap::ProtocolSinkWrap() {
    106   DVLOG(1) << __FUNCTION__ << base::StringPrintf(" 0x%08X", this);
    107 }
    108 
    109 ProtocolSinkWrap::~ProtocolSinkWrap() {
    110   DVLOG(1) << __FUNCTION__ << base::StringPrintf(" 0x%08X", this);
    111 }
    112 
    113 base::win::ScopedComPtr<IInternetProtocolSink> ProtocolSinkWrap::CreateNewSink(
    114     IInternetProtocolSink* sink, ProtData* data) {
    115   DCHECK(sink != NULL);
    116   DCHECK(data != NULL);
    117   CComObject<ProtocolSinkWrap>* new_sink = NULL;
    118   CComObject<ProtocolSinkWrap>::CreateInstance(&new_sink);
    119   new_sink->delegate_ = sink;
    120   new_sink->prot_data_ = data;
    121   return base::win::ScopedComPtr<IInternetProtocolSink>(new_sink);
    122 }
    123 
    124 // IInternetProtocolSink methods
    125 STDMETHODIMP ProtocolSinkWrap::Switch(PROTOCOLDATA* protocol_data) {
    126   HRESULT hr = E_FAIL;
    127   if (delegate_)
    128     hr = delegate_->Switch(protocol_data);
    129   return hr;
    130 }
    131 
    132 STDMETHODIMP ProtocolSinkWrap::ReportProgress(ULONG status_code,
    133                                               LPCWSTR status_text) {
    134   DVLOG(1) << "ProtocolSinkWrap::ReportProgress: "
    135            << BindStatus2Str(status_code)
    136            << " Status: " << (status_text ? status_text : L"");
    137 
    138   HRESULT hr = prot_data_->ReportProgress(delegate_, status_code, status_text);
    139   return hr;
    140 }
    141 
    142 STDMETHODIMP ProtocolSinkWrap::ReportData(DWORD flags, ULONG progress,
    143     ULONG max_progress) {
    144   DCHECK(delegate_);
    145   DVLOG(1) << "ProtocolSinkWrap::ReportData: " << Bscf2Str(flags)
    146            << " progress: " << progress << " progress_max: " << max_progress;
    147 
    148   HRESULT hr = prot_data_->ReportData(delegate_, flags, progress, max_progress);
    149   return hr;
    150 }
    151 
    152 STDMETHODIMP ProtocolSinkWrap::ReportResult(HRESULT result, DWORD error,
    153     LPCWSTR result_text) {
    154   DVLOG(1) << "ProtocolSinkWrap::ReportResult: result: " << result
    155            << " error: " << error
    156            << " Text: " << (result_text ? result_text : L"");
    157   ExceptionBarrier barrier;
    158   HRESULT hr = prot_data_->ReportResult(delegate_, result, error, result_text);
    159   return hr;
    160 }
    161 
    162 
    163 // Helpers
    164 base::win::ScopedComPtr<IBindCtx> BindCtxFromIBindInfo(
    165     IInternetBindInfo* bind_info) {
    166   LPOLESTR bind_ctx_string = NULL;
    167   ULONG count;
    168   base::win::ScopedComPtr<IBindCtx> bind_ctx;
    169   bind_info->GetBindString(BINDSTRING_PTR_BIND_CONTEXT, &bind_ctx_string, 1,
    170                            &count);
    171   if (bind_ctx_string) {
    172     int bind_ctx_int;
    173     base::StringToInt(bind_ctx_string, &bind_ctx_int);
    174     IBindCtx* pbc = reinterpret_cast<IBindCtx*>(bind_ctx_int);
    175     bind_ctx.Attach(pbc);
    176     CoTaskMemFree(bind_ctx_string);
    177   }
    178 
    179   return bind_ctx;
    180 }
    181 
    182 bool ShouldWrapSink(IInternetProtocolSink* sink, const wchar_t* url) {
    183   // Ignore everything that does not start with http:// or https://.
    184   // |url| is already normalized (i.e. no leading spaces, capital letters in
    185   // protocol etc) and non-null (we check in Hook_Start).
    186   DCHECK(url != NULL);
    187 
    188   if (ProtocolSinkWrap::ignore_xua())
    189     return false;  // No need to intercept, we're ignoring X-UA-Compatible tags
    190 
    191   if ((url != StrStrW(url, L"http://")) && (url != StrStrW(url, L"https://")))
    192     return false;
    193 
    194   base::win::ScopedComPtr<IHttpNegotiate> http_negotiate;
    195   HRESULT hr = DoQueryService(GUID_NULL, sink, http_negotiate.Receive());
    196   if (http_negotiate && !IsSubFrameRequest(http_negotiate))
    197     return true;
    198 
    199   return false;
    200 }
    201 
    202 // High level helpers
    203 bool IsCFRequest(IBindCtx* pbc) {
    204   base::win::ScopedComPtr<BindContextInfo> info;
    205   BindContextInfo::FromBindContext(pbc, info.Receive());
    206   if (info && info->chrome_request())
    207     return true;
    208 
    209   return false;
    210 }
    211 
    212 bool HasProtData(IBindCtx* pbc) {
    213   base::win::ScopedComPtr<BindContextInfo> info;
    214   BindContextInfo::FromBindContext(pbc, info.Receive());
    215   bool result = false;
    216   if (info)
    217     result = info->has_prot_data();
    218   return result;
    219 }
    220 
    221 void PutProtData(IBindCtx* pbc, ProtData* data) {
    222   // AddRef and Release to avoid a potential leak of a ProtData instance if
    223   // FromBindContext fails.
    224   data->AddRef();
    225   base::win::ScopedComPtr<BindContextInfo> info;
    226   BindContextInfo::FromBindContext(pbc, info.Receive());
    227   if (info)
    228     info->set_prot_data(data);
    229   data->Release();
    230 }
    231 
    232 bool IsTextHtml(const wchar_t* status_text) {
    233   const std::wstring str = status_text;
    234   bool is_text_html = LowerCaseEqualsASCII(str, kTextHtmlMimeType);
    235   return is_text_html;
    236 }
    237 
    238 bool IsAdditionallySupportedContentType(const wchar_t* status_text) {
    239   static const char* kHeaderContentTypes[] = {
    240     "application/xhtml+xml",
    241     "application/xml",
    242     "image/svg",
    243     "image/svg+xml",
    244     "text/xml",
    245     "video/ogg",
    246     "video/webm",
    247     "video/mp4"
    248   };
    249 
    250   const std::wstring str = status_text;
    251   for (int i = 0; i < arraysize(kHeaderContentTypes); ++i) {
    252     if (LowerCaseEqualsASCII(str, kHeaderContentTypes[i]))
    253       return true;
    254   }
    255 
    256   if (PolicySettings::GetInstance()->GetRendererForContentType(
    257       status_text) == PolicySettings::RENDER_IN_CHROME_FRAME) {
    258     return true;
    259   }
    260 
    261   return false;
    262 }
    263 
    264 // Returns:
    265 // RENDERER_TYPE_OTHER: if suggested mime type is not text/html.
    266 // RENDERER_TYPE_UNDETERMINED: if suggested mime type is text/html.
    267 // RENDERER_TYPE_CHROME_RESPONSE_HEADER: X-UA-Compatible tag is in HTTP headers.
    268 // RENDERER_TYPE_CHROME_DEFAULT_RENDERER: GCF is the default renderer and the
    269 //                                        Url is not listed in the
    270 //                                        RenderInHostUrls registry key.
    271 // RENDERER_TYPE_CHROME_OPT_IN_URL: GCF is not the default renderer and the Url
    272 //                                  is listed in the RenderInGcfUrls registry
    273 //                                  key.
    274 RendererType DetermineRendererTypeFromMetaData(
    275     const wchar_t* suggested_mime_type,
    276     const std::wstring& url,
    277     IWinInetHttpInfo* info) {
    278   bool is_text_html = IsTextHtml(suggested_mime_type);
    279   bool is_supported_content_type = is_text_html ||
    280       IsAdditionallySupportedContentType(suggested_mime_type);
    281 
    282   if (!is_supported_content_type)
    283     return RENDERER_TYPE_OTHER;
    284 
    285   if (!url.empty()) {
    286     RendererType renderer_type = RendererTypeForUrl(url);
    287     if (IsChrome(renderer_type)) {
    288       return renderer_type;
    289     }
    290   }
    291 
    292   if (info) {
    293     char buffer[512] = "x-ua-compatible";
    294     DWORD len = sizeof(buffer);
    295     DWORD flags = 0;
    296     HRESULT hr = info->QueryInfo(HTTP_QUERY_CUSTOM, buffer, &len, &flags, NULL);
    297 
    298     if (hr == S_OK && len > 0) {
    299       if (CheckXUaCompatibleDirective(buffer, GetIEMajorVersion())) {
    300         return RENDERER_TYPE_CHROME_RESPONSE_HEADER;
    301       }
    302     }
    303   }
    304 
    305   // We can (and want) to sniff the content.
    306   if (is_text_html) {
    307     return RENDERER_TYPE_UNDETERMINED;
    308   }
    309 
    310   // We cannot sniff the content.
    311   return RENDERER_TYPE_OTHER;
    312 }
    313 
    314 RendererType DetermineRendererType(void* buffer, DWORD size, bool last_chance) {
    315   RendererType renderer_type = RENDERER_TYPE_UNDETERMINED;
    316   if (last_chance)
    317     renderer_type = RENDERER_TYPE_OTHER;
    318 
    319   std::wstring html_contents;
    320   // TODO(joshia): detect and handle different content encodings
    321   UTF8ToWide(reinterpret_cast<char*>(buffer), size, &html_contents);
    322 
    323   // Note that document_contents_ may have NULL characters in it. While
    324   // browsers may handle this properly, we don't and will stop scanning
    325   // for the XUACompat content value if we encounter one.
    326   std::wstring xua_compat_content;
    327   if (SUCCEEDED(UtilGetXUACompatContentValue(html_contents,
    328                                              &xua_compat_content))) {
    329     if (CheckXUaCompatibleDirective(WideToASCII(xua_compat_content),
    330                                     GetIEMajorVersion())) {
    331       renderer_type = RENDERER_TYPE_CHROME_HTTP_EQUIV;
    332     }
    333   }
    334 
    335   return renderer_type;
    336 }
    337 
    338 // ProtData
    339 ProtData::ProtData(IInternetProtocol* protocol,
    340                    InternetProtocol_Read_Fn read_fun, const wchar_t* url)
    341     : has_suggested_mime_type_(false), has_server_mime_type_(false),
    342       buffer_size_(0), buffer_pos_(0),
    343       renderer_type_(RENDERER_TYPE_UNDETERMINED), protocol_(protocol),
    344       read_fun_(read_fun), url_(url) {
    345   memset(buffer_, 0, arraysize(buffer_));
    346   DVLOG(1) << __FUNCTION__ << " " << this;
    347 
    348   // Add to map.
    349   base::AutoLock lock(datamap_lock_);
    350   DCHECK(datamap_.end() == datamap_.find(protocol_));
    351   datamap_[protocol] = this;
    352 }
    353 
    354 ProtData::~ProtData() {
    355   DVLOG(1) << __FUNCTION__ << " " << this;
    356   Invalidate();
    357 }
    358 
    359 HRESULT ProtData::Read(void* buffer, ULONG size, ULONG* size_read) {
    360   if (renderer_type_ == RENDERER_TYPE_UNDETERMINED) {
    361     return E_PENDING;
    362   }
    363 
    364   const ULONG bytes_available = buffer_size_ - buffer_pos_;
    365   const ULONG bytes_to_copy = std::min(bytes_available, size);
    366   if (bytes_to_copy) {
    367     // Copy from the local buffer.
    368     memcpy(buffer, buffer_ + buffer_pos_, bytes_to_copy);
    369     *size_read = bytes_to_copy;
    370     buffer_pos_ += bytes_to_copy;
    371 
    372     HRESULT hr = S_OK;
    373     ULONG new_data = 0;
    374     if (size > bytes_available) {
    375       // User buffer is greater than what we have.
    376       buffer = reinterpret_cast<uint8*>(buffer) + bytes_to_copy;
    377       size -= bytes_to_copy;
    378       hr = read_fun_(protocol_, buffer, size, &new_data);
    379     }
    380 
    381     if (size_read)
    382       *size_read = bytes_to_copy + new_data;
    383     return hr;
    384   }
    385 
    386   return read_fun_(protocol_, buffer, size, size_read);
    387 }
    388 
    389 // Attempt to detect ChromeFrame from HTTP headers when
    390 // BINDSTATUS_MIMETYPEAVAILABLE is received.
    391 // There are three possible outcomes: CHROME_*/OTHER/UNDETERMINED.
    392 // If RENDERER_TYPE_UNDETERMINED we are going to sniff the content later in
    393 // ReportData().
    394 //
    395 // With not-so-well-written software (mime filters/protocols/protocol patchers)
    396 // BINDSTATUS_MIMETYPEAVAILABLE might be fired multiple times with different
    397 // values for the same client.
    398 // If the renderer_type_ member is:
    399 // RENDERER_TYPE_CHROME_* - 2nd (and any subsequent)
    400 //                          BINDSTATUS_MIMETYPEAVAILABLE is ignored.
    401 // RENDERER_TYPE_OTHER  - 2nd (and any subsequent) BINDSTATUS_MIMETYPEAVAILABLE
    402 //                        is passed through.
    403 // RENDERER_TYPE_UNDETERMINED - Try to detect ChromeFrame from HTTP headers
    404 //                              (acts as if this is the first time
    405 //                              BINDSTATUS_MIMETYPEAVAILABLE is received).
    406 HRESULT ProtData::ReportProgress(IInternetProtocolSink* delegate,
    407                                  ULONG status_code, LPCWSTR status_text) {
    408   switch (status_code) {
    409     case BINDSTATUS_DIRECTBIND:
    410       renderer_type_ = RENDERER_TYPE_OTHER;
    411       break;
    412 
    413     case BINDSTATUS_REDIRECTING:
    414       url_.clear();
    415       if (status_text)
    416         url_ = status_text;
    417       break;
    418 
    419     case BINDSTATUS_SERVER_MIMETYPEAVAILABLE:
    420       has_server_mime_type_ = true;
    421       SaveSuggestedMimeType(status_text);
    422       return S_OK;
    423 
    424     // TODO(stoyan): BINDSTATUS_RAWMIMETYPE
    425     case BINDSTATUS_MIMETYPEAVAILABLE:
    426     case BINDSTATUS_VERIFIEDMIMETYPEAVAILABLE:
    427       SaveSuggestedMimeType(status_text);
    428       // When Transaction is attached i.e. when existing BTS it terminated
    429       // and "converted" to BTO, events will be re-fired for the new sink,
    430       // but we may skip the renderer_type_ determination since it's already
    431       // done.
    432       if (renderer_type_ == RENDERER_TYPE_UNDETERMINED) {
    433         // This may seem awkward. CBinding's implementation of IWinInetHttpInfo
    434         // will forward to CTransaction that will forward to the real protocol.
    435         // We may ask CTransaction (our protocol_ member) for IWinInetHttpInfo.
    436         base::win::ScopedComPtr<IWinInetHttpInfo> info;
    437         info.QueryFrom(delegate);
    438         renderer_type_ = DetermineRendererTypeFromMetaData(suggested_mime_type_,
    439                                                            url_, info);
    440       }
    441 
    442       if (IsChrome(renderer_type_)) {
    443         // Suggested mime type is "text/html" and we have DEFAULT_RENDERER,
    444         // OPT_IN_URL, or RESPONSE_HEADER.
    445         DVLOG(1) << "Forwarding BINDSTATUS_MIMETYPEAVAILABLE "
    446                  << kChromeMimeType;
    447         SaveReferrer(delegate);
    448         delegate->ReportProgress(BINDSTATUS_MIMETYPEAVAILABLE, kChromeMimeType);
    449       } else if (renderer_type_ == RENDERER_TYPE_OTHER) {
    450         // Suggested mime type is not "text/html" - we are not interested in
    451         // this request anymore.
    452         FireSuggestedMimeType(delegate);
    453       } else {
    454         // Suggested mime type is "text/html"; We will try to sniff the
    455         // HTML content in ReportData.
    456         DCHECK_EQ(RENDERER_TYPE_UNDETERMINED, renderer_type_);
    457       }
    458       return S_OK;
    459   }
    460 
    461   // We are just pass through at this point, avoid false positive crash reports.
    462   ExceptionBarrierReportOnlyModule barrier;
    463   return delegate->ReportProgress(status_code, status_text);
    464 }
    465 
    466 HRESULT ProtData::ReportData(IInternetProtocolSink* delegate,
    467                               DWORD flags, ULONG progress, ULONG max_progress) {
    468   if (renderer_type_ != RENDERER_TYPE_UNDETERMINED) {
    469     // We are just pass through now, avoid false positive crash reports.
    470     ExceptionBarrierReportOnlyModule barrier;
    471     return delegate->ReportData(flags, progress, max_progress);
    472   }
    473 
    474   HRESULT hr = FillBuffer();
    475 
    476   bool last_chance = false;
    477   if (hr == S_OK || hr == S_FALSE) {
    478     last_chance = true;
    479   }
    480 
    481   renderer_type_ = DetermineRendererType(buffer_, buffer_size_, last_chance);
    482 
    483   if (renderer_type_ == RENDERER_TYPE_UNDETERMINED) {
    484     // do not report anything, we need more data.
    485     return S_OK;
    486   }
    487 
    488   if (IsChrome(renderer_type_)) {
    489     DVLOG(1) << "Forwarding BINDSTATUS_MIMETYPEAVAILABLE " << kChromeMimeType;
    490     SaveReferrer(delegate);
    491     delegate->ReportProgress(BINDSTATUS_MIMETYPEAVAILABLE, kChromeMimeType);
    492   }
    493 
    494   if (renderer_type_ == RENDERER_TYPE_OTHER) {
    495     FireSuggestedMimeType(delegate);
    496   }
    497 
    498   // This is the first data notification we forward, since up to now we hold
    499   // the content received.
    500   flags |= BSCF_FIRSTDATANOTIFICATION;
    501 
    502   if (hr == S_FALSE) {
    503     flags |= (BSCF_LASTDATANOTIFICATION | BSCF_DATAFULLYAVAILABLE);
    504   }
    505 
    506   return delegate->ReportData(flags, progress, max_progress);
    507 }
    508 
    509 HRESULT ProtData::ReportResult(IInternetProtocolSink* delegate, HRESULT result,
    510                                DWORD error, LPCWSTR result_text) {
    511   // We may receive ReportResult without ReportData, if the connection fails
    512   // for example.
    513   if (renderer_type_ == RENDERER_TYPE_UNDETERMINED) {
    514     DVLOG(1) << "ReportResult received but renderer type is yet unknown.";
    515     renderer_type_ = RENDERER_TYPE_OTHER;
    516     FireSuggestedMimeType(delegate);
    517   }
    518 
    519   HRESULT hr = S_OK;
    520   if (delegate)
    521     hr = delegate->ReportResult(result, error, result_text);
    522   return hr;
    523 }
    524 
    525 
    526 void ProtData::UpdateUrl(const wchar_t* url) {
    527   url_ = url;
    528 }
    529 
    530 // S_FALSE   - EOF
    531 // S_OK      - buffer fully filled
    532 // E_PENDING - some data added to buffer, but buffer is not yet full
    533 // E_XXXX    - some other error.
    534 HRESULT ProtData::FillBuffer() {
    535   HRESULT hr_read = S_OK;
    536 
    537   while ((hr_read == S_OK) && (buffer_size_ < kMaxContentSniffLength)) {
    538     ULONG size_read = 0;
    539     hr_read = read_fun_(protocol_, buffer_ + buffer_size_,
    540                        kMaxContentSniffLength - buffer_size_, &size_read);
    541     buffer_size_ += size_read;
    542   }
    543 
    544   return hr_read;
    545 }
    546 
    547 void ProtData::SaveSuggestedMimeType(LPCWSTR status_text) {
    548   has_suggested_mime_type_ = true;
    549   suggested_mime_type_.Allocate(status_text);
    550 }
    551 
    552 void ProtData::FireSuggestedMimeType(IInternetProtocolSink* delegate) {
    553   if (has_server_mime_type_) {
    554     DVLOG(1) << "Forwarding BINDSTATUS_SERVER_MIMETYPEAVAILABLE "
    555              << suggested_mime_type_;
    556     delegate->ReportProgress(BINDSTATUS_SERVER_MIMETYPEAVAILABLE,
    557                              suggested_mime_type_);
    558   }
    559 
    560   if (has_suggested_mime_type_) {
    561     DVLOG(1) << "Forwarding BINDSTATUS_MIMETYPEAVAILABLE "
    562              << suggested_mime_type_;
    563     delegate->ReportProgress(BINDSTATUS_MIMETYPEAVAILABLE,
    564                              suggested_mime_type_);
    565   }
    566 }
    567 
    568 void ProtData::SaveReferrer(IInternetProtocolSink* delegate) {
    569   DCHECK(IsChrome(renderer_type_));
    570   base::win::ScopedComPtr<IWinInetHttpInfo> info;
    571   info.QueryFrom(delegate);
    572   if (info) {
    573     char buffer[4096] = {0};
    574     DWORD len = sizeof(buffer);
    575     DWORD flags = 0;
    576     HRESULT hr = info->QueryInfo(
    577         HTTP_QUERY_REFERER | HTTP_QUERY_FLAG_REQUEST_HEADERS,
    578         buffer, &len, &flags, 0);
    579     if (hr == S_OK && len > 0)
    580       referrer_.assign(buffer);
    581   } else {
    582     DLOG(WARNING) << "Failed to QI for IWinInetHttpInfo";
    583   }
    584 }
    585 
    586 scoped_refptr<ProtData> ProtData::DataFromProtocol(
    587     IInternetProtocol* protocol) {
    588   scoped_refptr<ProtData> instance;
    589   base::AutoLock lock(datamap_lock_);
    590   ProtocolDataMap::iterator it = datamap_.find(protocol);
    591   if (datamap_.end() != it)
    592     instance = it->second;
    593   return instance;
    594 }
    595 
    596 void ProtData::Invalidate() {
    597   if (protocol_) {
    598     // Remove from map.
    599     base::AutoLock lock(datamap_lock_);
    600     DCHECK(datamap_.end() != datamap_.find(protocol_));
    601     datamap_.erase(protocol_);
    602     protocol_ = NULL;
    603   }
    604 }
    605 
    606 // This function looks for the url pattern indicating that this request needs
    607 // to be forced into chrome frame.
    608 // This hack is required because window.open requests from Chrome don't have
    609 // the URL up front. The URL comes in much later when the renderer initiates a
    610 // top level navigation for the url passed into window.open.
    611 // The new page must be rendered in ChromeFrame to preserve the opener
    612 // relationship with its parent even if the new page does not have the chrome
    613 // meta tag.
    614 bool HandleAttachToExistingExternalTab(LPCWSTR url,
    615                                        IInternetProtocol* protocol,
    616                                        IInternetProtocolSink* prot_sink,
    617                                        IBindCtx* bind_ctx) {
    618   ChromeFrameUrl cf_url;
    619   if (cf_url.Parse(url) && cf_url.attach_to_external_tab()) {
    620     scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol);
    621     if (!prot_data) {
    622       // Pass NULL as the read function which indicates that always return EOF
    623       // without calling the underlying protocol.
    624       prot_data = new ProtData(protocol, NULL, url);
    625       PutProtData(bind_ctx, prot_data);
    626     }
    627 
    628     prot_sink->ReportProgress(BINDSTATUS_MIMETYPEAVAILABLE, kChromeMimeType);
    629 
    630     int data_flags = BSCF_FIRSTDATANOTIFICATION | BSCF_LASTDATANOTIFICATION;
    631     prot_sink->ReportData(data_flags, 0, 0);
    632 
    633     prot_sink->ReportResult(S_OK, 0, NULL);
    634     return true;
    635   }
    636   return false;
    637 }
    638 
    639 HRESULT ForwardHookStart(InternetProtocol_Start_Fn orig_start,
    640     IInternetProtocol* protocol, LPCWSTR url, IInternetProtocolSink* prot_sink,
    641     IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) {
    642   ExceptionBarrierReportOnlyModule barrier;
    643   return orig_start(protocol, url, prot_sink, bind_info, flags, reserved);
    644 }
    645 
    646 HRESULT ForwardWrappedHookStart(InternetProtocol_Start_Fn orig_start,
    647     IInternetProtocol* protocol, LPCWSTR url, IInternetProtocolSink* prot_sink,
    648     IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) {
    649   ExceptionBarrier barrier;
    650   return orig_start(protocol, url, prot_sink, bind_info, flags, reserved);
    651 }
    652 
    653 // IInternetProtocol/Ex hooks.
    654 STDMETHODIMP Hook_Start(InternetProtocol_Start_Fn orig_start,
    655     IInternetProtocol* protocol, LPCWSTR url, IInternetProtocolSink* prot_sink,
    656     IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) {
    657   DCHECK(orig_start);
    658   if (!url || !prot_sink || !bind_info)
    659     return E_INVALIDARG;
    660   DVLOG_IF(1, url != NULL) << "OnStart: " << url << PiFlags2Str(flags);
    661 
    662   base::win::ScopedComPtr<IBindCtx> bind_ctx = BindCtxFromIBindInfo(bind_info);
    663   if (!bind_ctx) {
    664     // MSHTML sometimes takes a short path, skips the creation of
    665     // moniker and binding, by directly grabbing protocol from InternetSession
    666     DVLOG(1) << "DirectBind for " << url;
    667     return ForwardHookStart(orig_start, protocol, url, prot_sink, bind_info,
    668                             flags, reserved);
    669   }
    670 
    671   scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol);
    672   if (prot_data && !HasProtData(bind_ctx)) {
    673     prot_data->Invalidate();
    674     prot_data = NULL;
    675   }
    676 
    677   if (HandleAttachToExistingExternalTab(url, protocol, prot_sink, bind_ctx)) {
    678     return S_OK;
    679   }
    680 
    681   if (IsCFRequest(bind_ctx)) {
    682     base::win::ScopedComPtr<BindContextInfo> info;
    683     BindContextInfo::FromBindContext(bind_ctx, info.Receive());
    684     DCHECK(info);
    685     if (info) {
    686       info->set_protocol(protocol);
    687     }
    688     return ForwardHookStart(orig_start, protocol, url, prot_sink, bind_info,
    689                             flags, reserved);
    690   }
    691 
    692   if (prot_data) {
    693     DVLOG(1) << "Found existing ProtData!";
    694     prot_data->UpdateUrl(url);
    695     base::win::ScopedComPtr<IInternetProtocolSink> new_sink =
    696         ProtocolSinkWrap::CreateNewSink(prot_sink, prot_data);
    697     return ForwardWrappedHookStart(orig_start, protocol, url, new_sink,
    698                                    bind_info, flags, reserved);
    699   }
    700 
    701   if (!ShouldWrapSink(prot_sink, url)) {
    702     return ForwardHookStart(orig_start, protocol, url, prot_sink, bind_info,
    703                             flags, reserved);
    704   }
    705 
    706   // Fresh request.
    707   InternetProtocol_Read_Fn read_fun = reinterpret_cast<InternetProtocol_Read_Fn>
    708       (CTransaction_PatchInfo[1].stub_->argument());
    709   prot_data = new ProtData(protocol, read_fun, url);
    710   PutProtData(bind_ctx, prot_data);
    711 
    712   base::win::ScopedComPtr<IInternetProtocolSink> new_sink =
    713       ProtocolSinkWrap::CreateNewSink(prot_sink, prot_data);
    714   return ForwardWrappedHookStart(orig_start, protocol, url, new_sink, bind_info,
    715                                  flags, reserved);
    716 }
    717 
    718 HRESULT ForwardHookStartEx(InternetProtocol_StartEx_Fn orig_start_ex,
    719     IInternetProtocolEx* protocol, IUri* uri, IInternetProtocolSink* prot_sink,
    720     IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) {
    721   ExceptionBarrierReportOnlyModule barrier;
    722   return orig_start_ex(protocol, uri, prot_sink, bind_info, flags, reserved);
    723 }
    724 
    725 HRESULT ForwardWrappedHookStartEx(InternetProtocol_StartEx_Fn orig_start_ex,
    726     IInternetProtocolEx* protocol, IUri* uri, IInternetProtocolSink* prot_sink,
    727     IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) {
    728   ExceptionBarrier barrier;
    729   return orig_start_ex(protocol, uri, prot_sink, bind_info, flags, reserved);
    730 }
    731 
    732 STDMETHODIMP Hook_StartEx(InternetProtocol_StartEx_Fn orig_start_ex,
    733     IInternetProtocolEx* protocol, IUri* uri, IInternetProtocolSink* prot_sink,
    734     IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) {
    735   DCHECK(orig_start_ex);
    736   if (!uri || !prot_sink || !bind_info)
    737     return E_INVALIDARG;
    738 
    739   base::win::ScopedBstr url;
    740   uri->GetPropertyBSTR(Uri_PROPERTY_ABSOLUTE_URI, url.Receive(), 0);
    741   DVLOG_IF(1, url != NULL) << "OnStartEx: " << url << PiFlags2Str(flags);
    742 
    743   base::win::ScopedComPtr<IBindCtx> bind_ctx = BindCtxFromIBindInfo(bind_info);
    744   if (!bind_ctx) {
    745     // MSHTML sometimes takes a short path, skips the creation of
    746     // moniker and binding, by directly grabbing protocol from InternetSession.
    747     DVLOG(1) << "DirectBind for " << url;
    748     return ForwardHookStartEx(orig_start_ex, protocol, uri, prot_sink,
    749                               bind_info, flags, reserved);
    750   }
    751 
    752   scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol);
    753   if (prot_data && !HasProtData(bind_ctx)) {
    754     prot_data->Invalidate();
    755     prot_data = NULL;
    756   }
    757 
    758   if (HandleAttachToExistingExternalTab(url, protocol, prot_sink, bind_ctx)) {
    759     return S_OK;
    760   }
    761 
    762   if (IsCFRequest(bind_ctx)) {
    763     base::win::ScopedComPtr<BindContextInfo> info;
    764     BindContextInfo::FromBindContext(bind_ctx, info.Receive());
    765     DCHECK(info);
    766     if (info) {
    767       info->set_protocol(protocol);
    768     }
    769     return ForwardHookStartEx(orig_start_ex, protocol, uri, prot_sink,
    770                               bind_info, flags, reserved);
    771   }
    772 
    773   if (prot_data) {
    774     DVLOG(1) << "Found existing ProtData!";
    775     prot_data->UpdateUrl(url);
    776     base::win::ScopedComPtr<IInternetProtocolSink> new_sink =
    777         ProtocolSinkWrap::CreateNewSink(prot_sink, prot_data);
    778     return ForwardWrappedHookStartEx(orig_start_ex, protocol, uri, new_sink,
    779                                      bind_info, flags, reserved);
    780   }
    781 
    782   if (!ShouldWrapSink(prot_sink, url)) {
    783     return ForwardHookStartEx(orig_start_ex, protocol, uri, prot_sink,
    784                               bind_info, flags, reserved);
    785   }
    786 
    787   // Fresh request.
    788   InternetProtocol_Read_Fn read_fun = reinterpret_cast<InternetProtocol_Read_Fn>
    789     (CTransaction_PatchInfo[1].stub_->argument());
    790   prot_data = new ProtData(protocol, read_fun, url);
    791   PutProtData(bind_ctx, prot_data);
    792 
    793   base::win::ScopedComPtr<IInternetProtocolSink> new_sink =
    794       ProtocolSinkWrap::CreateNewSink(prot_sink, prot_data);
    795   return ForwardWrappedHookStartEx(orig_start_ex, protocol, uri, new_sink,
    796                                    bind_info, flags, reserved);
    797 }
    798 
    799 STDMETHODIMP Hook_Read(InternetProtocol_Read_Fn orig_read,
    800     IInternetProtocol* protocol, void* buffer, ULONG size, ULONG* size_read) {
    801   DCHECK(orig_read);
    802   HRESULT hr = E_FAIL;
    803 
    804   scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol);
    805   if (!prot_data) {
    806     // We are not wrapping this request, avoid false positive crash reports.
    807     ExceptionBarrierReportOnlyModule barrier;
    808     hr = orig_read(protocol, buffer, size, size_read);
    809     return hr;
    810   }
    811 
    812   if (prot_data->is_attach_external_tab_request()) {
    813     // return EOF always.
    814     if (size_read)
    815       *size_read = 0;
    816     return S_FALSE;
    817   }
    818 
    819   hr = prot_data->Read(buffer, size, size_read);
    820   return hr;
    821 }
    822 
    823 STDMETHODIMP Hook_LockRequest(InternetProtocol_LockRequest_Fn orig_req,
    824                               IInternetProtocol* protocol,
    825                               DWORD options) {
    826   DCHECK(orig_req);
    827 
    828   scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol);
    829   if (prot_data && prot_data->is_attach_external_tab_request()) {
    830     prot_data->AddRef();
    831     return S_OK;
    832   }
    833 
    834   // We are just pass through at this point, avoid false positive crash
    835   // reports.
    836   ExceptionBarrierReportOnlyModule barrier;
    837   return orig_req(protocol, options);
    838 }
    839 
    840 STDMETHODIMP Hook_UnlockRequest(InternetProtocol_UnlockRequest_Fn orig_req,
    841                                 IInternetProtocol* protocol) {
    842   DCHECK(orig_req);
    843 
    844   scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol);
    845   if (prot_data && prot_data->is_attach_external_tab_request()) {
    846     prot_data->Release();
    847     return S_OK;
    848   }
    849 
    850   // We are just pass through at this point, avoid false positive crash
    851   // reports.
    852   ExceptionBarrierReportOnlyModule barrier;
    853   return orig_req(protocol);
    854 }
    855 
    856 STDMETHODIMP Hook_Abort(InternetProtocol_Abort_Fn orig_req,
    857                         IInternetProtocol* protocol,
    858                         HRESULT hr,
    859                         DWORD options) {
    860   scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol);
    861   if (prot_data)
    862     prot_data->Invalidate();
    863 
    864   // We are just pass through at this point, avoid false positive crash
    865   // reports.
    866   ExceptionBarrierReportOnlyModule barrier;
    867   return orig_req(protocol, hr, options);
    868 }
    869 
    870 STDMETHODIMP Hook_Terminate(InternetProtocol_Terminate_Fn orig_req,
    871                             IInternetProtocol* protocol,
    872                             DWORD options) {
    873   scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol);
    874   // We should not be invalidating the cached protocol data in the following
    875   // cases:-
    876   // 1. Pages which are switching into ChromeFrame.
    877   //    When IE switches into ChromeFrame, we report the Chrome mime type as
    878   //    the handler for the page. This results in urlmon terminating the
    879   //    protocol. When Chrome attempts to read the data we need to report the
    880   //    cached data back to Chrome.
    881   // 2. For the attach external tab requests which are temporary navigations
    882   //    to ensure that a top level URL is opened in IE from ChromeFrame.
    883   //    We rely on the mapping to identify these requests as attach tab
    884   //    requests. This mapping is referred to in the
    885   //    IInternetProtocol::LockRequest/IInternetProtocol::UnlockRequest
    886   //    intercepts. Invalidating the mapping after LockRequest is called and
    887   //    before UnlockRequest causes IE to crash.
    888   if (prot_data && !IsChrome(prot_data->renderer_type()) &&
    889       !prot_data->is_attach_external_tab_request())
    890     prot_data->Invalidate();
    891 
    892   // We are just pass through at this point, avoid false positive crash
    893   // reports.
    894   ExceptionBarrierReportOnlyModule barrier;
    895   return orig_req(protocol, options);
    896 }
    897 
    898 // Patching / Hooking code.
    899 class FakeProtocol : public CComObjectRootEx<CComSingleThreadModel>,
    900                      public IInternetProtocol {
    901  public:
    902   BEGIN_COM_MAP(FakeProtocol)
    903     COM_INTERFACE_ENTRY(IInternetProtocol)
    904     COM_INTERFACE_ENTRY(IInternetProtocolRoot)
    905   END_COM_MAP()
    906 
    907   STDMETHOD(Start)(LPCWSTR url, IInternetProtocolSink *protocol_sink,
    908       IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) {
    909     transaction_.QueryFrom(protocol_sink);
    910     // Return some unusual error code.
    911     return INET_E_INVALID_CERTIFICATE;
    912   }
    913 
    914   STDMETHOD(Continue)(PROTOCOLDATA* protocol_data) { return S_OK; }
    915   STDMETHOD(Abort)(HRESULT reason, DWORD options) { return S_OK; }
    916   STDMETHOD(Terminate)(DWORD options) { return S_OK; }
    917   STDMETHOD(Suspend)() { return S_OK; }
    918   STDMETHOD(Resume)() { return S_OK; }
    919   STDMETHOD(Read)(void *buffer, ULONG size, ULONG* size_read) { return S_OK; }
    920   STDMETHOD(Seek)(LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER* new_pos)
    921     { return S_OK; }
    922   STDMETHOD(LockRequest)(DWORD options) { return S_OK; }
    923   STDMETHOD(UnlockRequest)() { return S_OK; }
    924 
    925   base::win::ScopedComPtr<IInternetProtocol> transaction_;
    926 };
    927 
    928 struct FakeFactory : public IClassFactory,
    929                      public CComObjectRootEx<CComSingleThreadModel> {
    930   BEGIN_COM_MAP(FakeFactory)
    931     COM_INTERFACE_ENTRY(IClassFactory)
    932   END_COM_MAP()
    933 
    934   STDMETHOD(CreateInstance)(IUnknown *pUnkOuter, REFIID riid, void **ppvObj) {
    935     if (pUnkOuter)
    936       return CLASS_E_NOAGGREGATION;
    937     HRESULT hr = obj_->QueryInterface(riid, ppvObj);
    938     return hr;
    939   }
    940 
    941   STDMETHOD(LockServer)(BOOL fLock) {
    942     return S_OK;
    943   }
    944 
    945   IUnknown* obj_;
    946 };
    947 
    948 static void HookTransactionVtable(IInternetProtocol* p) {
    949   base::win::ScopedComPtr<IInternetProtocolEx> ex;
    950   ex.QueryFrom(p);
    951 
    952   HRESULT hr = vtable_patch::PatchInterfaceMethods(p, CTransaction_PatchInfo);
    953   if (hr == S_OK && ex) {
    954     vtable_patch::PatchInterfaceMethods(ex.get(), CTransaction2_PatchInfo);
    955   }
    956 }
    957 
    958 void TransactionHooks::InstallHooks() {
    959   if (IS_PATCHED(CTransaction)) {
    960     DLOG(WARNING) << __FUNCTION__ << " called more than once.";
    961     return;
    962   }
    963 
    964   CComObjectStackEx<FakeProtocol> prot;
    965   CComObjectStackEx<FakeFactory> factory;
    966   factory.obj_ = &prot;
    967   base::win::ScopedComPtr<IInternetSession> session;
    968   HRESULT hr = ::CoInternetGetSession(0, session.Receive(), 0);
    969   hr = session->RegisterNameSpace(&factory, CLSID_NULL, L"611", 0, 0, 0);
    970   DLOG_IF(FATAL, FAILED(hr)) << "Failed to register namespace";
    971   if (hr != S_OK)
    972     return;
    973 
    974   do {
    975     base::win::ScopedComPtr<IMoniker> mk;
    976     base::win::ScopedComPtr<IBindCtx> bc;
    977     base::win::ScopedComPtr<IStream> stream;
    978     hr = ::CreateAsyncBindCtxEx(0, 0, 0, 0, bc.Receive(), 0);
    979     DLOG_IF(FATAL, FAILED(hr)) << "CreateAsyncBindCtxEx failed " << hr;
    980     if (hr != S_OK)
    981       break;
    982 
    983     hr = ::CreateURLMoniker(NULL, L"611://512", mk.Receive());
    984     DLOG_IF(FATAL, FAILED(hr)) << "CreateURLMoniker failed " << hr;
    985     if (hr != S_OK)
    986       break;
    987 
    988     hr = mk->BindToStorage(bc, NULL, IID_IStream,
    989                            reinterpret_cast<void**>(stream.Receive()));
    990     DLOG_IF(FATAL, hr != INET_E_INVALID_CERTIFICATE) <<
    991         "BindToStorage failed " << hr;
    992   } while (0);
    993 
    994   hr = session->UnregisterNameSpace(&factory, L"611");
    995   if (prot.transaction_) {
    996     HookTransactionVtable(prot.transaction_);
    997     // Explicit release, otherwise ~CComObjectStackEx will complain about
    998     // outstanding reference to us, because it runs before ~FakeProtocol
    999     prot.transaction_.Release();
   1000   }
   1001 }
   1002 
   1003 void TransactionHooks::RevertHooks() {
   1004   vtable_patch::UnpatchInterfaceMethods(CTransaction_PatchInfo);
   1005   vtable_patch::UnpatchInterfaceMethods(CTransaction2_PatchInfo);
   1006 }
   1007