Add VisibleForTesting to content/public/android
[chromium-blink-merge.git] / chrome_frame / protocol_sink_wrap.cc
blob69d6e6868ecf5ee1e65f734b2717dc1e98a0bcdc
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.
5 #include <htiframe.h>
6 #include <mshtml.h>
8 #include "chrome_frame/protocol_sink_wrap.h"
10 #include "base/logging.h"
11 #include "base/memory/singleton.h"
12 #include "base/string_number_conversions.h"
13 #include "base/string_util.h"
14 #include "base/stringprintf.h"
15 #include "base/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"
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
31 bool ProtocolSinkWrap::ignore_xua_ = false;
33 static const char kTextHtmlMimeType[] = "text/html";
34 const wchar_t kUrlMonDllName[] = L"urlmon.dll";
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;
43 // IInternetProtocol/Ex patches.
44 STDMETHODIMP Hook_Start(InternetProtocol_Start_Fn orig_start,
45 IInternetProtocol* protocol,
46 LPCWSTR url,
47 IInternetProtocolSink* prot_sink,
48 IInternetBindInfo* bind_info,
49 DWORD flags,
50 HANDLE_PTR reserved);
52 STDMETHODIMP Hook_StartEx(InternetProtocol_StartEx_Fn orig_start_ex,
53 IInternetProtocolEx* protocol,
54 IUri* uri,
55 IInternetProtocolSink* prot_sink,
56 IInternetBindInfo* bind_info,
57 DWORD flags,
58 HANDLE_PTR reserved);
60 STDMETHODIMP Hook_Read(InternetProtocol_Read_Fn orig_read,
61 IInternetProtocol* protocol,
62 void* buffer,
63 ULONG size,
64 ULONG* size_read);
66 STDMETHODIMP Hook_LockRequest(InternetProtocol_LockRequest_Fn orig_req,
67 IInternetProtocol* protocol, DWORD dwOptions);
69 STDMETHODIMP Hook_UnlockRequest(InternetProtocol_UnlockRequest_Fn orig_req,
70 IInternetProtocol* protocol);
72 /////////////////////////////////////////////////////////////////////////////
73 BEGIN_VTABLE_PATCHES(CTransaction)
74 VTABLE_PATCH_ENTRY(kInternetProtocolStartIndex, Hook_Start)
75 VTABLE_PATCH_ENTRY(kInternetProtocolReadIndex, Hook_Read)
76 VTABLE_PATCH_ENTRY(kInternetProtocolLockRequestIndex, Hook_LockRequest)
77 VTABLE_PATCH_ENTRY(kInternetProtocolUnlockRequestIndex, Hook_UnlockRequest)
78 END_VTABLE_PATCHES()
80 BEGIN_VTABLE_PATCHES(CTransaction2)
81 VTABLE_PATCH_ENTRY(kInternetProtocolStartExIndex, Hook_StartEx)
82 END_VTABLE_PATCHES()
85 // ProtocolSinkWrap implementation
87 // Static map initialization
88 ProtData::ProtocolDataMap ProtData::datamap_;
89 base::Lock ProtData::datamap_lock_;
91 ProtocolSinkWrap::ProtocolSinkWrap() {
92 DVLOG(1) << __FUNCTION__ << base::StringPrintf(" 0x%08X", this);
95 ProtocolSinkWrap::~ProtocolSinkWrap() {
96 DVLOG(1) << __FUNCTION__ << base::StringPrintf(" 0x%08X", this);
99 base::win::ScopedComPtr<IInternetProtocolSink> ProtocolSinkWrap::CreateNewSink(
100 IInternetProtocolSink* sink, ProtData* data) {
101 DCHECK(sink != NULL);
102 DCHECK(data != NULL);
103 CComObject<ProtocolSinkWrap>* new_sink = NULL;
104 CComObject<ProtocolSinkWrap>::CreateInstance(&new_sink);
105 new_sink->delegate_ = sink;
106 new_sink->prot_data_ = data;
107 return base::win::ScopedComPtr<IInternetProtocolSink>(new_sink);
110 // IInternetProtocolSink methods
111 STDMETHODIMP ProtocolSinkWrap::Switch(PROTOCOLDATA* protocol_data) {
112 HRESULT hr = E_FAIL;
113 if (delegate_)
114 hr = delegate_->Switch(protocol_data);
115 return hr;
118 STDMETHODIMP ProtocolSinkWrap::ReportProgress(ULONG status_code,
119 LPCWSTR status_text) {
120 DVLOG(1) << "ProtocolSinkWrap::ReportProgress: "
121 << BindStatus2Str(status_code)
122 << " Status: " << (status_text ? status_text : L"");
124 HRESULT hr = prot_data_->ReportProgress(delegate_, status_code, status_text);
125 return hr;
128 STDMETHODIMP ProtocolSinkWrap::ReportData(DWORD flags, ULONG progress,
129 ULONG max_progress) {
130 DCHECK(delegate_);
131 DVLOG(1) << "ProtocolSinkWrap::ReportData: " << Bscf2Str(flags)
132 << " progress: " << progress << " progress_max: " << max_progress;
134 HRESULT hr = prot_data_->ReportData(delegate_, flags, progress, max_progress);
135 return hr;
138 STDMETHODIMP ProtocolSinkWrap::ReportResult(HRESULT result, DWORD error,
139 LPCWSTR result_text) {
140 DVLOG(1) << "ProtocolSinkWrap::ReportResult: result: " << result
141 << " error: " << error
142 << " Text: " << (result_text ? result_text : L"");
143 ExceptionBarrier barrier;
144 HRESULT hr = prot_data_->ReportResult(delegate_, result, error, result_text);
145 return hr;
149 // Helpers
150 base::win::ScopedComPtr<IBindCtx> BindCtxFromIBindInfo(
151 IInternetBindInfo* bind_info) {
152 LPOLESTR bind_ctx_string = NULL;
153 ULONG count;
154 base::win::ScopedComPtr<IBindCtx> bind_ctx;
155 bind_info->GetBindString(BINDSTRING_PTR_BIND_CONTEXT, &bind_ctx_string, 1,
156 &count);
157 if (bind_ctx_string) {
158 int bind_ctx_int;
159 base::StringToInt(bind_ctx_string, &bind_ctx_int);
160 IBindCtx* pbc = reinterpret_cast<IBindCtx*>(bind_ctx_int);
161 bind_ctx.Attach(pbc);
162 CoTaskMemFree(bind_ctx_string);
165 return bind_ctx;
168 bool ShouldWrapSink(IInternetProtocolSink* sink, const wchar_t* url) {
169 // Ignore everything that does not start with http:// or https://.
170 // |url| is already normalized (i.e. no leading spaces, capital letters in
171 // protocol etc) and non-null (we check in Hook_Start).
172 DCHECK(url != NULL);
174 if (ProtocolSinkWrap::ignore_xua())
175 return false; // No need to intercept, we're ignoring X-UA-Compatible tags
177 if ((url != StrStrW(url, L"http://")) && (url != StrStrW(url, L"https://")))
178 return false;
180 base::win::ScopedComPtr<IHttpNegotiate> http_negotiate;
181 HRESULT hr = DoQueryService(GUID_NULL, sink, http_negotiate.Receive());
182 if (http_negotiate && !IsSubFrameRequest(http_negotiate))
183 return true;
185 return false;
188 // High level helpers
189 bool IsCFRequest(IBindCtx* pbc) {
190 base::win::ScopedComPtr<BindContextInfo> info;
191 BindContextInfo::FromBindContext(pbc, info.Receive());
192 if (info && info->chrome_request())
193 return true;
195 return false;
198 bool HasProtData(IBindCtx* pbc) {
199 base::win::ScopedComPtr<BindContextInfo> info;
200 BindContextInfo::FromBindContext(pbc, info.Receive());
201 bool result = false;
202 if (info)
203 result = info->has_prot_data();
204 return result;
207 void PutProtData(IBindCtx* pbc, ProtData* data) {
208 // AddRef and Release to avoid a potential leak of a ProtData instance if
209 // FromBindContext fails.
210 data->AddRef();
211 base::win::ScopedComPtr<BindContextInfo> info;
212 BindContextInfo::FromBindContext(pbc, info.Receive());
213 if (info)
214 info->set_prot_data(data);
215 data->Release();
218 bool IsTextHtml(const wchar_t* status_text) {
219 const std::wstring str = status_text;
220 bool is_text_html = LowerCaseEqualsASCII(str, kTextHtmlMimeType);
221 return is_text_html;
224 bool IsAdditionallySupportedContentType(const wchar_t* status_text) {
225 static const char* kHeaderContentTypes[] = {
226 "application/xhtml+xml",
227 "application/xml",
228 "image/svg",
229 "image/svg+xml",
230 "text/xml",
231 "video/ogg",
232 "video/webm",
233 "video/mp4"
236 const std::wstring str = status_text;
237 for (int i = 0; i < arraysize(kHeaderContentTypes); ++i) {
238 if (LowerCaseEqualsASCII(str, kHeaderContentTypes[i]))
239 return true;
242 if (PolicySettings::GetInstance()->GetRendererForContentType(
243 status_text) == PolicySettings::RENDER_IN_CHROME_FRAME) {
244 return true;
247 return false;
250 // Returns:
251 // RENDERER_TYPE_OTHER: if suggested mime type is not text/html.
252 // RENDERER_TYPE_UNDETERMINED: if suggested mime type is text/html.
253 // RENDERER_TYPE_CHROME_RESPONSE_HEADER: X-UA-Compatible tag is in HTTP headers.
254 // RENDERER_TYPE_CHROME_DEFAULT_RENDERER: GCF is the default renderer and the
255 // Url is not listed in the
256 // RenderInHostUrls registry key.
257 // RENDERER_TYPE_CHROME_OPT_IN_URL: GCF is not the default renderer and the Url
258 // is listed in the RenderInGcfUrls registry
259 // key.
260 RendererType DetermineRendererTypeFromMetaData(
261 const wchar_t* suggested_mime_type,
262 const std::wstring& url,
263 IWinInetHttpInfo* info) {
264 bool is_text_html = IsTextHtml(suggested_mime_type);
265 bool is_supported_content_type = is_text_html ||
266 IsAdditionallySupportedContentType(suggested_mime_type);
268 if (!is_supported_content_type)
269 return RENDERER_TYPE_OTHER;
271 if (!url.empty()) {
272 RendererType renderer_type = RendererTypeForUrl(url);
273 if (IsChrome(renderer_type)) {
274 return renderer_type;
278 if (info) {
279 char buffer[512] = "x-ua-compatible";
280 DWORD len = sizeof(buffer);
281 DWORD flags = 0;
282 HRESULT hr = info->QueryInfo(HTTP_QUERY_CUSTOM, buffer, &len, &flags, NULL);
284 if (hr == S_OK && len > 0) {
285 if (CheckXUaCompatibleDirective(buffer, GetIEMajorVersion())) {
286 return RENDERER_TYPE_CHROME_RESPONSE_HEADER;
291 // We can (and want) to sniff the content.
292 if (is_text_html) {
293 return RENDERER_TYPE_UNDETERMINED;
296 // We cannot sniff the content.
297 return RENDERER_TYPE_OTHER;
300 RendererType DetermineRendererType(void* buffer, DWORD size, bool last_chance) {
301 RendererType renderer_type = RENDERER_TYPE_UNDETERMINED;
302 if (last_chance)
303 renderer_type = RENDERER_TYPE_OTHER;
305 std::wstring html_contents;
306 // TODO(joshia): detect and handle different content encodings
307 UTF8ToWide(reinterpret_cast<char*>(buffer), size, &html_contents);
309 // Note that document_contents_ may have NULL characters in it. While
310 // browsers may handle this properly, we don't and will stop scanning
311 // for the XUACompat content value if we encounter one.
312 std::wstring xua_compat_content;
313 if (SUCCEEDED(UtilGetXUACompatContentValue(html_contents,
314 &xua_compat_content))) {
315 if (CheckXUaCompatibleDirective(WideToASCII(xua_compat_content),
316 GetIEMajorVersion())) {
317 renderer_type = RENDERER_TYPE_CHROME_HTTP_EQUIV;
321 return renderer_type;
324 // ProtData
325 ProtData::ProtData(IInternetProtocol* protocol,
326 InternetProtocol_Read_Fn read_fun, const wchar_t* url)
327 : has_suggested_mime_type_(false), has_server_mime_type_(false),
328 buffer_size_(0), buffer_pos_(0),
329 renderer_type_(RENDERER_TYPE_UNDETERMINED), protocol_(protocol),
330 read_fun_(read_fun), url_(url) {
331 memset(buffer_, 0, arraysize(buffer_));
332 DVLOG(1) << __FUNCTION__ << " " << this;
334 // Add to map.
335 base::AutoLock lock(datamap_lock_);
336 DCHECK(datamap_.end() == datamap_.find(protocol_));
337 datamap_[protocol] = this;
340 ProtData::~ProtData() {
341 DVLOG(1) << __FUNCTION__ << " " << this;
342 Invalidate();
345 HRESULT ProtData::Read(void* buffer, ULONG size, ULONG* size_read) {
346 if (renderer_type_ == RENDERER_TYPE_UNDETERMINED) {
347 return E_PENDING;
350 const ULONG bytes_available = buffer_size_ - buffer_pos_;
351 const ULONG bytes_to_copy = std::min(bytes_available, size);
352 if (bytes_to_copy) {
353 // Copy from the local buffer.
354 memcpy(buffer, buffer_ + buffer_pos_, bytes_to_copy);
355 *size_read = bytes_to_copy;
356 buffer_pos_ += bytes_to_copy;
358 HRESULT hr = S_OK;
359 ULONG new_data = 0;
360 if (size > bytes_available) {
361 // User buffer is greater than what we have.
362 buffer = reinterpret_cast<uint8*>(buffer) + bytes_to_copy;
363 size -= bytes_to_copy;
364 hr = read_fun_(protocol_, buffer, size, &new_data);
367 if (size_read)
368 *size_read = bytes_to_copy + new_data;
369 return hr;
372 return read_fun_(protocol_, buffer, size, size_read);
375 // Attempt to detect ChromeFrame from HTTP headers when
376 // BINDSTATUS_MIMETYPEAVAILABLE is received.
377 // There are three possible outcomes: CHROME_*/OTHER/UNDETERMINED.
378 // If RENDERER_TYPE_UNDETERMINED we are going to sniff the content later in
379 // ReportData().
381 // With not-so-well-written software (mime filters/protocols/protocol patchers)
382 // BINDSTATUS_MIMETYPEAVAILABLE might be fired multiple times with different
383 // values for the same client.
384 // If the renderer_type_ member is:
385 // RENDERER_TYPE_CHROME_* - 2nd (and any subsequent)
386 // BINDSTATUS_MIMETYPEAVAILABLE is ignored.
387 // RENDERER_TYPE_OTHER - 2nd (and any subsequent) BINDSTATUS_MIMETYPEAVAILABLE
388 // is passed through.
389 // RENDERER_TYPE_UNDETERMINED - Try to detect ChromeFrame from HTTP headers
390 // (acts as if this is the first time
391 // BINDSTATUS_MIMETYPEAVAILABLE is received).
392 HRESULT ProtData::ReportProgress(IInternetProtocolSink* delegate,
393 ULONG status_code, LPCWSTR status_text) {
394 switch (status_code) {
395 case BINDSTATUS_DIRECTBIND:
396 renderer_type_ = RENDERER_TYPE_OTHER;
397 break;
399 case BINDSTATUS_REDIRECTING:
400 url_.clear();
401 if (status_text)
402 url_ = status_text;
403 break;
405 case BINDSTATUS_SERVER_MIMETYPEAVAILABLE:
406 has_server_mime_type_ = true;
407 SaveSuggestedMimeType(status_text);
408 return S_OK;
410 // TODO(stoyan): BINDSTATUS_RAWMIMETYPE
411 case BINDSTATUS_MIMETYPEAVAILABLE:
412 case BINDSTATUS_VERIFIEDMIMETYPEAVAILABLE:
413 SaveSuggestedMimeType(status_text);
414 // When Transaction is attached i.e. when existing BTS it terminated
415 // and "converted" to BTO, events will be re-fired for the new sink,
416 // but we may skip the renderer_type_ determination since it's already
417 // done.
418 if (renderer_type_ == RENDERER_TYPE_UNDETERMINED) {
419 // This may seem awkward. CBinding's implementation of IWinInetHttpInfo
420 // will forward to CTransaction that will forward to the real protocol.
421 // We may ask CTransaction (our protocol_ member) for IWinInetHttpInfo.
422 base::win::ScopedComPtr<IWinInetHttpInfo> info;
423 info.QueryFrom(delegate);
424 renderer_type_ = DetermineRendererTypeFromMetaData(suggested_mime_type_,
425 url_, info);
428 if (IsChrome(renderer_type_)) {
429 // Suggested mime type is "text/html" and we have DEFAULT_RENDERER,
430 // OPT_IN_URL, or RESPONSE_HEADER.
431 DVLOG(1) << "Forwarding BINDSTATUS_MIMETYPEAVAILABLE "
432 << kChromeMimeType;
433 SaveReferrer(delegate);
434 delegate->ReportProgress(BINDSTATUS_MIMETYPEAVAILABLE, kChromeMimeType);
435 } else if (renderer_type_ == RENDERER_TYPE_OTHER) {
436 // Suggested mime type is not "text/html" - we are not interested in
437 // this request anymore.
438 FireSuggestedMimeType(delegate);
439 } else {
440 // Suggested mime type is "text/html"; We will try to sniff the
441 // HTML content in ReportData.
442 DCHECK_EQ(RENDERER_TYPE_UNDETERMINED, renderer_type_);
444 return S_OK;
447 // We are just pass through at this point, avoid false positive crash reports.
448 ExceptionBarrierReportOnlyModule barrier;
449 return delegate->ReportProgress(status_code, status_text);
452 HRESULT ProtData::ReportData(IInternetProtocolSink* delegate,
453 DWORD flags, ULONG progress, ULONG max_progress) {
454 if (renderer_type_ != RENDERER_TYPE_UNDETERMINED) {
455 // We are just pass through now, avoid false positive crash reports.
456 ExceptionBarrierReportOnlyModule barrier;
457 return delegate->ReportData(flags, progress, max_progress);
460 HRESULT hr = FillBuffer();
462 bool last_chance = false;
463 if (hr == S_OK || hr == S_FALSE) {
464 last_chance = true;
467 renderer_type_ = DetermineRendererType(buffer_, buffer_size_, last_chance);
469 if (renderer_type_ == RENDERER_TYPE_UNDETERMINED) {
470 // do not report anything, we need more data.
471 return S_OK;
474 if (IsChrome(renderer_type_)) {
475 DVLOG(1) << "Forwarding BINDSTATUS_MIMETYPEAVAILABLE " << kChromeMimeType;
476 SaveReferrer(delegate);
477 delegate->ReportProgress(BINDSTATUS_MIMETYPEAVAILABLE, kChromeMimeType);
480 if (renderer_type_ == RENDERER_TYPE_OTHER) {
481 FireSuggestedMimeType(delegate);
484 // This is the first data notification we forward, since up to now we hold
485 // the content received.
486 flags |= BSCF_FIRSTDATANOTIFICATION;
488 if (hr == S_FALSE) {
489 flags |= (BSCF_LASTDATANOTIFICATION | BSCF_DATAFULLYAVAILABLE);
492 return delegate->ReportData(flags, progress, max_progress);
495 HRESULT ProtData::ReportResult(IInternetProtocolSink* delegate, HRESULT result,
496 DWORD error, LPCWSTR result_text) {
497 // We may receive ReportResult without ReportData, if the connection fails
498 // for example.
499 if (renderer_type_ == RENDERER_TYPE_UNDETERMINED) {
500 DVLOG(1) << "ReportResult received but renderer type is yet unknown.";
501 renderer_type_ = RENDERER_TYPE_OTHER;
502 FireSuggestedMimeType(delegate);
505 HRESULT hr = S_OK;
506 if (delegate)
507 hr = delegate->ReportResult(result, error, result_text);
508 return hr;
512 void ProtData::UpdateUrl(const wchar_t* url) {
513 url_ = url;
516 // S_FALSE - EOF
517 // S_OK - buffer fully filled
518 // E_PENDING - some data added to buffer, but buffer is not yet full
519 // E_XXXX - some other error.
520 HRESULT ProtData::FillBuffer() {
521 HRESULT hr_read = S_OK;
523 while ((hr_read == S_OK) && (buffer_size_ < kMaxContentSniffLength)) {
524 ULONG size_read = 0;
525 hr_read = read_fun_(protocol_, buffer_ + buffer_size_,
526 kMaxContentSniffLength - buffer_size_, &size_read);
527 buffer_size_ += size_read;
530 return hr_read;
533 void ProtData::SaveSuggestedMimeType(LPCWSTR status_text) {
534 has_suggested_mime_type_ = true;
535 suggested_mime_type_.Allocate(status_text);
538 void ProtData::FireSuggestedMimeType(IInternetProtocolSink* delegate) {
539 if (has_server_mime_type_) {
540 DVLOG(1) << "Forwarding BINDSTATUS_SERVER_MIMETYPEAVAILABLE "
541 << suggested_mime_type_;
542 delegate->ReportProgress(BINDSTATUS_SERVER_MIMETYPEAVAILABLE,
543 suggested_mime_type_);
546 if (has_suggested_mime_type_) {
547 DVLOG(1) << "Forwarding BINDSTATUS_MIMETYPEAVAILABLE "
548 << suggested_mime_type_;
549 delegate->ReportProgress(BINDSTATUS_MIMETYPEAVAILABLE,
550 suggested_mime_type_);
554 void ProtData::SaveReferrer(IInternetProtocolSink* delegate) {
555 DCHECK(IsChrome(renderer_type_));
556 base::win::ScopedComPtr<IWinInetHttpInfo> info;
557 info.QueryFrom(delegate);
558 if (info) {
559 char buffer[4096] = {0};
560 DWORD len = sizeof(buffer);
561 DWORD flags = 0;
562 HRESULT hr = info->QueryInfo(
563 HTTP_QUERY_REFERER | HTTP_QUERY_FLAG_REQUEST_HEADERS,
564 buffer, &len, &flags, 0);
565 if (hr == S_OK && len > 0)
566 referrer_.assign(buffer);
567 } else {
568 DLOG(WARNING) << "Failed to QI for IWinInetHttpInfo";
572 scoped_refptr<ProtData> ProtData::DataFromProtocol(
573 IInternetProtocol* protocol) {
574 scoped_refptr<ProtData> instance;
575 base::AutoLock lock(datamap_lock_);
576 ProtocolDataMap::iterator it = datamap_.find(protocol);
577 if (datamap_.end() != it)
578 instance = it->second;
579 return instance;
582 void ProtData::Invalidate() {
583 if (protocol_) {
584 // Remove from map.
585 base::AutoLock lock(datamap_lock_);
586 DCHECK(datamap_.end() != datamap_.find(protocol_));
587 datamap_.erase(protocol_);
588 protocol_ = NULL;
592 // This function looks for the url pattern indicating that this request needs
593 // to be forced into chrome frame.
594 // This hack is required because window.open requests from Chrome don't have
595 // the URL up front. The URL comes in much later when the renderer initiates a
596 // top level navigation for the url passed into window.open.
597 // The new page must be rendered in ChromeFrame to preserve the opener
598 // relationship with its parent even if the new page does not have the chrome
599 // meta tag.
600 bool HandleAttachToExistingExternalTab(LPCWSTR url,
601 IInternetProtocol* protocol,
602 IInternetProtocolSink* prot_sink,
603 IBindCtx* bind_ctx) {
604 ChromeFrameUrl cf_url;
605 if (cf_url.Parse(url) && cf_url.attach_to_external_tab()) {
606 scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol);
607 if (!prot_data) {
608 // Pass NULL as the read function which indicates that always return EOF
609 // without calling the underlying protocol.
610 prot_data = new ProtData(protocol, NULL, url);
611 PutProtData(bind_ctx, prot_data);
614 prot_sink->ReportProgress(BINDSTATUS_MIMETYPEAVAILABLE, kChromeMimeType);
616 int data_flags = BSCF_FIRSTDATANOTIFICATION | BSCF_LASTDATANOTIFICATION;
617 prot_sink->ReportData(data_flags, 0, 0);
619 prot_sink->ReportResult(S_OK, 0, NULL);
620 return true;
622 return false;
625 HRESULT ForwardHookStart(InternetProtocol_Start_Fn orig_start,
626 IInternetProtocol* protocol, LPCWSTR url, IInternetProtocolSink* prot_sink,
627 IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) {
628 ExceptionBarrierReportOnlyModule barrier;
629 return orig_start(protocol, url, prot_sink, bind_info, flags, reserved);
632 HRESULT ForwardWrappedHookStart(InternetProtocol_Start_Fn orig_start,
633 IInternetProtocol* protocol, LPCWSTR url, IInternetProtocolSink* prot_sink,
634 IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) {
635 ExceptionBarrier barrier;
636 return orig_start(protocol, url, prot_sink, bind_info, flags, reserved);
639 // IInternetProtocol/Ex hooks.
640 STDMETHODIMP Hook_Start(InternetProtocol_Start_Fn orig_start,
641 IInternetProtocol* protocol, LPCWSTR url, IInternetProtocolSink* prot_sink,
642 IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) {
643 DCHECK(orig_start);
644 if (!url || !prot_sink || !bind_info)
645 return E_INVALIDARG;
646 DVLOG_IF(1, url != NULL) << "OnStart: " << url << PiFlags2Str(flags);
648 base::win::ScopedComPtr<IBindCtx> bind_ctx = BindCtxFromIBindInfo(bind_info);
649 if (!bind_ctx) {
650 // MSHTML sometimes takes a short path, skips the creation of
651 // moniker and binding, by directly grabbing protocol from InternetSession
652 DVLOG(1) << "DirectBind for " << url;
653 return ForwardHookStart(orig_start, protocol, url, prot_sink, bind_info,
654 flags, reserved);
657 scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol);
658 if (prot_data && !HasProtData(bind_ctx)) {
659 prot_data->Invalidate();
660 prot_data = NULL;
663 if (HandleAttachToExistingExternalTab(url, protocol, prot_sink, bind_ctx)) {
664 return S_OK;
667 if (IsCFRequest(bind_ctx)) {
668 base::win::ScopedComPtr<BindContextInfo> info;
669 BindContextInfo::FromBindContext(bind_ctx, info.Receive());
670 DCHECK(info);
671 if (info) {
672 info->set_protocol(protocol);
674 return ForwardHookStart(orig_start, protocol, url, prot_sink, bind_info,
675 flags, reserved);
678 if (prot_data) {
679 DVLOG(1) << "Found existing ProtData!";
680 prot_data->UpdateUrl(url);
681 base::win::ScopedComPtr<IInternetProtocolSink> new_sink =
682 ProtocolSinkWrap::CreateNewSink(prot_sink, prot_data);
683 return ForwardWrappedHookStart(orig_start, protocol, url, new_sink,
684 bind_info, flags, reserved);
687 if (!ShouldWrapSink(prot_sink, url)) {
688 return ForwardHookStart(orig_start, protocol, url, prot_sink, bind_info,
689 flags, reserved);
692 // Fresh request.
693 InternetProtocol_Read_Fn read_fun = reinterpret_cast<InternetProtocol_Read_Fn>
694 (CTransaction_PatchInfo[1].stub_->argument());
695 prot_data = new ProtData(protocol, read_fun, url);
696 PutProtData(bind_ctx, prot_data);
698 base::win::ScopedComPtr<IInternetProtocolSink> new_sink =
699 ProtocolSinkWrap::CreateNewSink(prot_sink, prot_data);
700 return ForwardWrappedHookStart(orig_start, protocol, url, new_sink, bind_info,
701 flags, reserved);
704 HRESULT ForwardHookStartEx(InternetProtocol_StartEx_Fn orig_start_ex,
705 IInternetProtocolEx* protocol, IUri* uri, IInternetProtocolSink* prot_sink,
706 IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) {
707 ExceptionBarrierReportOnlyModule barrier;
708 return orig_start_ex(protocol, uri, prot_sink, bind_info, flags, reserved);
711 HRESULT ForwardWrappedHookStartEx(InternetProtocol_StartEx_Fn orig_start_ex,
712 IInternetProtocolEx* protocol, IUri* uri, IInternetProtocolSink* prot_sink,
713 IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) {
714 ExceptionBarrier barrier;
715 return orig_start_ex(protocol, uri, prot_sink, bind_info, flags, reserved);
718 STDMETHODIMP Hook_StartEx(InternetProtocol_StartEx_Fn orig_start_ex,
719 IInternetProtocolEx* protocol, IUri* uri, IInternetProtocolSink* prot_sink,
720 IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) {
721 DCHECK(orig_start_ex);
722 if (!uri || !prot_sink || !bind_info)
723 return E_INVALIDARG;
725 base::win::ScopedBstr url;
726 uri->GetPropertyBSTR(Uri_PROPERTY_ABSOLUTE_URI, url.Receive(), 0);
727 DVLOG_IF(1, url != NULL) << "OnStartEx: " << url << PiFlags2Str(flags);
729 base::win::ScopedComPtr<IBindCtx> bind_ctx = BindCtxFromIBindInfo(bind_info);
730 if (!bind_ctx) {
731 // MSHTML sometimes takes a short path, skips the creation of
732 // moniker and binding, by directly grabbing protocol from InternetSession.
733 DVLOG(1) << "DirectBind for " << url;
734 return ForwardHookStartEx(orig_start_ex, protocol, uri, prot_sink,
735 bind_info, flags, reserved);
738 scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol);
739 if (prot_data && !HasProtData(bind_ctx)) {
740 prot_data->Invalidate();
741 prot_data = NULL;
744 if (HandleAttachToExistingExternalTab(url, protocol, prot_sink, bind_ctx)) {
745 return S_OK;
748 if (IsCFRequest(bind_ctx)) {
749 base::win::ScopedComPtr<BindContextInfo> info;
750 BindContextInfo::FromBindContext(bind_ctx, info.Receive());
751 DCHECK(info);
752 if (info) {
753 info->set_protocol(protocol);
755 return ForwardHookStartEx(orig_start_ex, protocol, uri, prot_sink,
756 bind_info, flags, reserved);
759 if (prot_data) {
760 DVLOG(1) << "Found existing ProtData!";
761 prot_data->UpdateUrl(url);
762 base::win::ScopedComPtr<IInternetProtocolSink> new_sink =
763 ProtocolSinkWrap::CreateNewSink(prot_sink, prot_data);
764 return ForwardWrappedHookStartEx(orig_start_ex, protocol, uri, new_sink,
765 bind_info, flags, reserved);
768 if (!ShouldWrapSink(prot_sink, url)) {
769 return ForwardHookStartEx(orig_start_ex, protocol, uri, prot_sink,
770 bind_info, flags, reserved);
773 // Fresh request.
774 InternetProtocol_Read_Fn read_fun = reinterpret_cast<InternetProtocol_Read_Fn>
775 (CTransaction_PatchInfo[1].stub_->argument());
776 prot_data = new ProtData(protocol, read_fun, url);
777 PutProtData(bind_ctx, prot_data);
779 base::win::ScopedComPtr<IInternetProtocolSink> new_sink =
780 ProtocolSinkWrap::CreateNewSink(prot_sink, prot_data);
781 return ForwardWrappedHookStartEx(orig_start_ex, protocol, uri, new_sink,
782 bind_info, flags, reserved);
785 STDMETHODIMP Hook_Read(InternetProtocol_Read_Fn orig_read,
786 IInternetProtocol* protocol, void* buffer, ULONG size, ULONG* size_read) {
787 DCHECK(orig_read);
788 HRESULT hr = E_FAIL;
790 scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol);
791 if (!prot_data) {
792 // We are not wrapping this request, avoid false positive crash reports.
793 ExceptionBarrierReportOnlyModule barrier;
794 hr = orig_read(protocol, buffer, size, size_read);
795 return hr;
798 if (prot_data->is_attach_external_tab_request()) {
799 // return EOF always.
800 if (size_read)
801 *size_read = 0;
802 return S_FALSE;
805 hr = prot_data->Read(buffer, size, size_read);
806 return hr;
809 STDMETHODIMP Hook_LockRequest(InternetProtocol_LockRequest_Fn orig_req,
810 IInternetProtocol* protocol, DWORD options) {
811 DCHECK(orig_req);
813 scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol);
814 if (prot_data && prot_data->is_attach_external_tab_request()) {
815 prot_data->AddRef();
816 return S_OK;
819 // We are just pass through at this point, avoid false positive crash
820 // reports.
821 ExceptionBarrierReportOnlyModule barrier;
822 return orig_req(protocol, options);
825 STDMETHODIMP Hook_UnlockRequest(InternetProtocol_UnlockRequest_Fn orig_req,
826 IInternetProtocol* protocol) {
827 DCHECK(orig_req);
829 scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol);
830 if (prot_data && prot_data->is_attach_external_tab_request()) {
831 prot_data->Release();
832 return S_OK;
835 // We are just pass through at this point, avoid false positive crash
836 // reports.
837 ExceptionBarrierReportOnlyModule barrier;
838 return orig_req(protocol);
841 // Patching / Hooking code.
842 class FakeProtocol : public CComObjectRootEx<CComSingleThreadModel>,
843 public IInternetProtocol {
844 public:
845 BEGIN_COM_MAP(FakeProtocol)
846 COM_INTERFACE_ENTRY(IInternetProtocol)
847 COM_INTERFACE_ENTRY(IInternetProtocolRoot)
848 END_COM_MAP()
850 STDMETHOD(Start)(LPCWSTR url, IInternetProtocolSink *protocol_sink,
851 IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) {
852 transaction_.QueryFrom(protocol_sink);
853 // Return some unusual error code.
854 return INET_E_INVALID_CERTIFICATE;
857 STDMETHOD(Continue)(PROTOCOLDATA* protocol_data) { return S_OK; }
858 STDMETHOD(Abort)(HRESULT reason, DWORD options) { return S_OK; }
859 STDMETHOD(Terminate)(DWORD options) { return S_OK; }
860 STDMETHOD(Suspend)() { return S_OK; }
861 STDMETHOD(Resume)() { return S_OK; }
862 STDMETHOD(Read)(void *buffer, ULONG size, ULONG* size_read) { return S_OK; }
863 STDMETHOD(Seek)(LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER* new_pos)
864 { return S_OK; }
865 STDMETHOD(LockRequest)(DWORD options) { return S_OK; }
866 STDMETHOD(UnlockRequest)() { return S_OK; }
868 base::win::ScopedComPtr<IInternetProtocol> transaction_;
871 struct FakeFactory : public IClassFactory,
872 public CComObjectRootEx<CComSingleThreadModel> {
873 BEGIN_COM_MAP(FakeFactory)
874 COM_INTERFACE_ENTRY(IClassFactory)
875 END_COM_MAP()
877 STDMETHOD(CreateInstance)(IUnknown *pUnkOuter, REFIID riid, void **ppvObj) {
878 if (pUnkOuter)
879 return CLASS_E_NOAGGREGATION;
880 HRESULT hr = obj_->QueryInterface(riid, ppvObj);
881 return hr;
884 STDMETHOD(LockServer)(BOOL fLock) {
885 return S_OK;
888 IUnknown* obj_;
891 static void HookTransactionVtable(IInternetProtocol* p) {
892 base::win::ScopedComPtr<IInternetProtocolEx> ex;
893 ex.QueryFrom(p);
895 HRESULT hr = vtable_patch::PatchInterfaceMethods(p, CTransaction_PatchInfo);
896 if (hr == S_OK && ex) {
897 vtable_patch::PatchInterfaceMethods(ex.get(), CTransaction2_PatchInfo);
901 void TransactionHooks::InstallHooks() {
902 if (IS_PATCHED(CTransaction)) {
903 DLOG(WARNING) << __FUNCTION__ << " called more than once.";
904 return;
907 CComObjectStackEx<FakeProtocol> prot;
908 CComObjectStackEx<FakeFactory> factory;
909 factory.obj_ = &prot;
910 base::win::ScopedComPtr<IInternetSession> session;
911 HRESULT hr = ::CoInternetGetSession(0, session.Receive(), 0);
912 hr = session->RegisterNameSpace(&factory, CLSID_NULL, L"611", 0, 0, 0);
913 DLOG_IF(FATAL, FAILED(hr)) << "Failed to register namespace";
914 if (hr != S_OK)
915 return;
917 do {
918 base::win::ScopedComPtr<IMoniker> mk;
919 base::win::ScopedComPtr<IBindCtx> bc;
920 base::win::ScopedComPtr<IStream> stream;
921 hr = ::CreateAsyncBindCtxEx(0, 0, 0, 0, bc.Receive(), 0);
922 DLOG_IF(FATAL, FAILED(hr)) << "CreateAsyncBindCtxEx failed " << hr;
923 if (hr != S_OK)
924 break;
926 hr = ::CreateURLMoniker(NULL, L"611://512", mk.Receive());
927 DLOG_IF(FATAL, FAILED(hr)) << "CreateURLMoniker failed " << hr;
928 if (hr != S_OK)
929 break;
931 hr = mk->BindToStorage(bc, NULL, IID_IStream,
932 reinterpret_cast<void**>(stream.Receive()));
933 DLOG_IF(FATAL, hr != INET_E_INVALID_CERTIFICATE) <<
934 "BindToStorage failed " << hr;
935 } while (0);
937 hr = session->UnregisterNameSpace(&factory, L"611");
938 if (prot.transaction_) {
939 HookTransactionVtable(prot.transaction_);
940 // Explicit release, otherwise ~CComObjectStackEx will complain about
941 // outstanding reference to us, because it runs before ~FakeProtocol
942 prot.transaction_.Release();
946 void TransactionHooks::RevertHooks() {
947 vtable_patch::UnpatchInterfaceMethods(CTransaction_PatchInfo);
948 vtable_patch::UnpatchInterfaceMethods(CTransaction2_PatchInfo);