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