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
25using 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
34bool ProtocolSinkWrap::ignore_xua_ = false;
35
36static const char kTextHtmlMimeType[] = "text/html";
37const wchar_t kUrlMonDllName[] = L"urlmon.dll";
38
39static const int kInternetProtocolStartIndex = 3;
40static const int kInternetProtocolReadIndex = 9;
41static const int kInternetProtocolStartExIndex = 13;
42static const int kInternetProtocolLockRequestIndex = 11;
43static const int kInternetProtocolUnlockRequestIndex = 12;
44static const int kInternetProtocolAbortIndex = 5;
45static const int kInternetProtocolTerminateIndex = 6;
46
47
48// IInternetProtocol/Ex patches.
49STDMETHODIMP 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
57STDMETHODIMP 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
65STDMETHODIMP Hook_Read(InternetProtocol_Read_Fn orig_read,
66                       IInternetProtocol* protocol,
67                       void* buffer,
68                       ULONG size,
69                       ULONG* size_read);
70
71STDMETHODIMP Hook_LockRequest(InternetProtocol_LockRequest_Fn orig_req,
72                              IInternetProtocol* protocol,
73                              DWORD options);
74
75STDMETHODIMP Hook_UnlockRequest(InternetProtocol_UnlockRequest_Fn orig_req,
76                                IInternetProtocol* protocol);
77
78STDMETHODIMP Hook_Abort(InternetProtocol_Abort_Fn orig_req,
79                        IInternetProtocol* protocol,
80                        HRESULT hr,
81                        DWORD options);
82
83STDMETHODIMP Hook_Terminate(InternetProtocol_Terminate_Fn orig_req,
84                            IInternetProtocol* protocol,
85                            DWORD options);
86
87/////////////////////////////////////////////////////////////////////////////
88BEGIN_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)
95END_VTABLE_PATCHES()
96
97BEGIN_VTABLE_PATCHES(CTransaction2)
98  VTABLE_PATCH_ENTRY(kInternetProtocolStartExIndex, Hook_StartEx)
99END_VTABLE_PATCHES()
100
101//
102// ProtocolSinkWrap implementation
103
104// Static map initialization
105ProtData::ProtocolDataMap ProtData::datamap_;
106base::Lock ProtData::datamap_lock_;
107
108ProtocolSinkWrap::ProtocolSinkWrap() {
109  DVLOG(1) << __FUNCTION__ << base::StringPrintf(" 0x%08X", this);
110}
111
112ProtocolSinkWrap::~ProtocolSinkWrap() {
113  DVLOG(1) << __FUNCTION__ << base::StringPrintf(" 0x%08X", this);
114}
115
116base::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
128STDMETHODIMP ProtocolSinkWrap::Switch(PROTOCOLDATA* protocol_data) {
129  HRESULT hr = E_FAIL;
130  if (delegate_)
131    hr = delegate_->Switch(protocol_data);
132  return hr;
133}
134
135STDMETHODIMP 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
145STDMETHODIMP 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
155STDMETHODIMP 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
167base::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
185bool 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
206bool 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
215bool 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
224void 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
235bool 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
241bool 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.
277RendererType 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
317RendererType 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
342ProtData::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
357ProtData::~ProtData() {
358  DVLOG(1) << __FUNCTION__ << " " << this;
359  Invalidate();
360}
361
362HRESULT 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).
409HRESULT 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
469HRESULT 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
513HRESULT 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
530void 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.
538HRESULT 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
551void ProtData::SaveSuggestedMimeType(LPCWSTR status_text) {
552  has_suggested_mime_type_ = true;
553  suggested_mime_type_.Allocate(status_text);
554}
555
556void 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
572void 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
590scoped_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
600void 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.
618bool 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
643HRESULT 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
650HRESULT 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.
658STDMETHODIMP 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
722HRESULT 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
729HRESULT 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
736STDMETHODIMP 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
803STDMETHODIMP 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
827STDMETHODIMP 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
844STDMETHODIMP 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
860STDMETHODIMP 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
874STDMETHODIMP 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.
903class 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
932struct 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
952static 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
962void 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
1007void TransactionHooks::RevertHooks() {
1008  vtable_patch::UnpatchInterfaceMethods(CTransaction_PatchInfo);
1009  vtable_patch::UnpatchInterfaceMethods(CTransaction2_PatchInfo);
1010}
1011