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