ChromiumEnv::GetChildren() now clearing prior results.
[chromium-blink-merge.git] / chrome_frame / protocol_sink_wrap.cc
blobb94e7c020fb39c38139a04abc01f73e066e2b091
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>
7 #include <algorithm>
9 #include "chrome_frame/protocol_sink_wrap.h"
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"
25 using std::min;
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
34 bool ProtocolSinkWrap::ignore_xua_ = false;
36 static const char kTextHtmlMimeType[] = "text/html";
37 const wchar_t kUrlMonDllName[] = L"urlmon.dll";
39 static const int kInternetProtocolStartIndex = 3;
40 static const int kInternetProtocolReadIndex = 9;
41 static const int kInternetProtocolStartExIndex = 13;
42 static const int kInternetProtocolLockRequestIndex = 11;
43 static const int kInternetProtocolUnlockRequestIndex = 12;
44 static const int kInternetProtocolAbortIndex = 5;
45 static const int kInternetProtocolTerminateIndex = 6;
48 // IInternetProtocol/Ex patches.
49 STDMETHODIMP Hook_Start(InternetProtocol_Start_Fn orig_start,
50 IInternetProtocol* protocol,
51 LPCWSTR url,
52 IInternetProtocolSink* prot_sink,
53 IInternetBindInfo* bind_info,
54 DWORD flags,
55 HANDLE_PTR reserved);
57 STDMETHODIMP Hook_StartEx(InternetProtocol_StartEx_Fn orig_start_ex,
58 IInternetProtocolEx* protocol,
59 IUri* uri,
60 IInternetProtocolSink* prot_sink,
61 IInternetBindInfo* bind_info,
62 DWORD flags,
63 HANDLE_PTR reserved);
65 STDMETHODIMP Hook_Read(InternetProtocol_Read_Fn orig_read,
66 IInternetProtocol* protocol,
67 void* buffer,
68 ULONG size,
69 ULONG* size_read);
71 STDMETHODIMP Hook_LockRequest(InternetProtocol_LockRequest_Fn orig_req,
72 IInternetProtocol* protocol,
73 DWORD options);
75 STDMETHODIMP Hook_UnlockRequest(InternetProtocol_UnlockRequest_Fn orig_req,
76 IInternetProtocol* protocol);
78 STDMETHODIMP Hook_Abort(InternetProtocol_Abort_Fn orig_req,
79 IInternetProtocol* protocol,
80 HRESULT hr,
81 DWORD options);
83 STDMETHODIMP Hook_Terminate(InternetProtocol_Terminate_Fn orig_req,
84 IInternetProtocol* protocol,
85 DWORD options);
87 /////////////////////////////////////////////////////////////////////////////
88 BEGIN_VTABLE_PATCHES(CTransaction)
89 VTABLE_PATCH_ENTRY(kInternetProtocolStartIndex, Hook_Start)
90 VTABLE_PATCH_ENTRY(kInternetProtocolReadIndex, Hook_Read)
91 VTABLE_PATCH_ENTRY(kInternetProtocolLockRequestIndex, Hook_LockRequest)
92 VTABLE_PATCH_ENTRY(kInternetProtocolUnlockRequestIndex, Hook_UnlockRequest)
93 VTABLE_PATCH_ENTRY(kInternetProtocolAbortIndex, Hook_Abort)
94 VTABLE_PATCH_ENTRY(kInternetProtocolTerminateIndex, Hook_Terminate)
95 END_VTABLE_PATCHES()
97 BEGIN_VTABLE_PATCHES(CTransaction2)
98 VTABLE_PATCH_ENTRY(kInternetProtocolStartExIndex, Hook_StartEx)
99 END_VTABLE_PATCHES()
102 // ProtocolSinkWrap implementation
104 // Static map initialization
105 ProtData::ProtocolDataMap ProtData::datamap_;
106 base::Lock ProtData::datamap_lock_;
108 ProtocolSinkWrap::ProtocolSinkWrap() {
109 DVLOG(1) << __FUNCTION__ << base::StringPrintf(" 0x%08X", this);
112 ProtocolSinkWrap::~ProtocolSinkWrap() {
113 DVLOG(1) << __FUNCTION__ << base::StringPrintf(" 0x%08X", this);
116 base::win::ScopedComPtr<IInternetProtocolSink> ProtocolSinkWrap::CreateNewSink(
117 IInternetProtocolSink* sink, ProtData* data) {
118 DCHECK(sink != NULL);
119 DCHECK(data != NULL);
120 CComObject<ProtocolSinkWrap>* new_sink = NULL;
121 CComObject<ProtocolSinkWrap>::CreateInstance(&new_sink);
122 new_sink->delegate_ = sink;
123 new_sink->prot_data_ = data;
124 return base::win::ScopedComPtr<IInternetProtocolSink>(new_sink);
127 // IInternetProtocolSink methods
128 STDMETHODIMP ProtocolSinkWrap::Switch(PROTOCOLDATA* protocol_data) {
129 HRESULT hr = E_FAIL;
130 if (delegate_)
131 hr = delegate_->Switch(protocol_data);
132 return hr;
135 STDMETHODIMP ProtocolSinkWrap::ReportProgress(ULONG status_code,
136 LPCWSTR status_text) {
137 DVLOG(1) << "ProtocolSinkWrap::ReportProgress: "
138 << BindStatus2Str(status_code)
139 << " Status: " << (status_text ? status_text : L"");
141 HRESULT hr = prot_data_->ReportProgress(delegate_, status_code, status_text);
142 return hr;
145 STDMETHODIMP ProtocolSinkWrap::ReportData(DWORD flags, ULONG progress,
146 ULONG max_progress) {
147 DCHECK(delegate_);
148 DVLOG(1) << "ProtocolSinkWrap::ReportData: " << Bscf2Str(flags)
149 << " progress: " << progress << " progress_max: " << max_progress;
151 HRESULT hr = prot_data_->ReportData(delegate_, flags, progress, max_progress);
152 return hr;
155 STDMETHODIMP ProtocolSinkWrap::ReportResult(HRESULT result, DWORD error,
156 LPCWSTR result_text) {
157 DVLOG(1) << "ProtocolSinkWrap::ReportResult: result: " << result
158 << " error: " << error
159 << " Text: " << (result_text ? result_text : L"");
160 ExceptionBarrier barrier;
161 HRESULT hr = prot_data_->ReportResult(delegate_, result, error, result_text);
162 return hr;
166 // Helpers
167 base::win::ScopedComPtr<IBindCtx> BindCtxFromIBindInfo(
168 IInternetBindInfo* bind_info) {
169 LPOLESTR bind_ctx_string = NULL;
170 ULONG count;
171 base::win::ScopedComPtr<IBindCtx> bind_ctx;
172 bind_info->GetBindString(BINDSTRING_PTR_BIND_CONTEXT, &bind_ctx_string, 1,
173 &count);
174 if (bind_ctx_string) {
175 int bind_ctx_int;
176 base::StringToInt(bind_ctx_string, &bind_ctx_int);
177 IBindCtx* pbc = reinterpret_cast<IBindCtx*>(bind_ctx_int);
178 bind_ctx.Attach(pbc);
179 CoTaskMemFree(bind_ctx_string);
182 return bind_ctx;
185 bool ShouldWrapSink(IInternetProtocolSink* sink, const wchar_t* url) {
186 // Ignore everything that does not start with http:// or https://.
187 // |url| is already normalized (i.e. no leading spaces, capital letters in
188 // protocol etc) and non-null (we check in Hook_Start).
189 DCHECK(url != NULL);
191 if (ProtocolSinkWrap::ignore_xua())
192 return false; // No need to intercept, we're ignoring X-UA-Compatible tags
194 if ((url != StrStrW(url, L"http://")) && (url != StrStrW(url, L"https://")))
195 return false;
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;
202 return false;
205 // High level helpers
206 bool IsCFRequest(IBindCtx* pbc) {
207 base::win::ScopedComPtr<BindContextInfo> info;
208 BindContextInfo::FromBindContext(pbc, info.Receive());
209 if (info && info->chrome_request())
210 return true;
212 return false;
215 bool HasProtData(IBindCtx* pbc) {
216 base::win::ScopedComPtr<BindContextInfo> info;
217 BindContextInfo::FromBindContext(pbc, info.Receive());
218 bool result = false;
219 if (info)
220 result = info->has_prot_data();
221 return result;
224 void PutProtData(IBindCtx* pbc, ProtData* data) {
225 // AddRef and Release to avoid a potential leak of a ProtData instance if
226 // FromBindContext fails.
227 data->AddRef();
228 base::win::ScopedComPtr<BindContextInfo> info;
229 BindContextInfo::FromBindContext(pbc, info.Receive());
230 if (info)
231 info->set_prot_data(data);
232 data->Release();
235 bool IsTextHtml(const wchar_t* status_text) {
236 const std::wstring str = status_text;
237 bool is_text_html = LowerCaseEqualsASCII(str, kTextHtmlMimeType);
238 return is_text_html;
241 bool IsAdditionallySupportedContentType(const wchar_t* status_text) {
242 static const char* kHeaderContentTypes[] = {
243 "application/xhtml+xml",
244 "application/xml",
245 "image/svg",
246 "image/svg+xml",
247 "text/xml",
248 "video/ogg",
249 "video/webm",
250 "video/mp4"
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;
259 if (PolicySettings::GetInstance()->GetRendererForContentType(
260 status_text) == PolicySettings::RENDER_IN_CHROME_FRAME) {
261 return true;
264 return false;
267 // Returns:
268 // RENDERER_TYPE_OTHER: if suggested mime type is not text/html.
269 // RENDERER_TYPE_UNDETERMINED: if suggested mime type is text/html.
270 // RENDERER_TYPE_CHROME_RESPONSE_HEADER: X-UA-Compatible tag is in HTTP headers.
271 // RENDERER_TYPE_CHROME_DEFAULT_RENDERER: GCF is the default renderer and the
272 // Url is not listed in the
273 // RenderInHostUrls registry key.
274 // RENDERER_TYPE_CHROME_OPT_IN_URL: GCF is not the default renderer and the Url
275 // is listed in the RenderInGcfUrls registry
276 // key.
277 RendererType DetermineRendererTypeFromMetaData(
278 const wchar_t* suggested_mime_type,
279 const std::wstring& url,
280 IWinInetHttpInfo* info) {
281 bool is_text_html = IsTextHtml(suggested_mime_type);
282 bool is_supported_content_type = is_text_html ||
283 IsAdditionallySupportedContentType(suggested_mime_type);
285 if (!is_supported_content_type)
286 return RENDERER_TYPE_OTHER;
288 if (!url.empty()) {
289 RendererType renderer_type = RendererTypeForUrl(url);
290 if (IsChrome(renderer_type)) {
291 return renderer_type;
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);
301 if (hr == S_OK && len > 0) {
302 if (CheckXUaCompatibleDirective(buffer, GetIEMajorVersion())) {
303 return RENDERER_TYPE_CHROME_RESPONSE_HEADER;
308 // We can (and want) to sniff the content.
309 if (is_text_html) {
310 return RENDERER_TYPE_UNDETERMINED;
313 // We cannot sniff the content.
314 return RENDERER_TYPE_OTHER;
317 RendererType DetermineRendererType(void* buffer, DWORD size, bool last_chance) {
318 RendererType renderer_type = RENDERER_TYPE_UNDETERMINED;
319 if (last_chance)
320 renderer_type = RENDERER_TYPE_OTHER;
322 std::wstring html_contents;
323 // TODO(joshia): detect and handle different content encodings
324 UTF8ToWide(reinterpret_cast<char*>(buffer), size, &html_contents);
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;
338 return renderer_type;
341 // ProtData
342 ProtData::ProtData(IInternetProtocol* protocol,
343 InternetProtocol_Read_Fn read_fun, const wchar_t* url)
344 : has_suggested_mime_type_(false), has_server_mime_type_(false),
345 buffer_size_(0), buffer_pos_(0),
346 renderer_type_(RENDERER_TYPE_UNDETERMINED), protocol_(protocol),
347 read_fun_(read_fun), url_(url) {
348 memset(buffer_, 0, arraysize(buffer_));
349 DVLOG(1) << __FUNCTION__ << " " << this;
351 // Add to map.
352 base::AutoLock lock(datamap_lock_);
353 DCHECK(datamap_.end() == datamap_.find(protocol_));
354 datamap_[protocol] = this;
357 ProtData::~ProtData() {
358 DVLOG(1) << __FUNCTION__ << " " << this;
359 Invalidate();
362 HRESULT ProtData::Read(void* buffer, ULONG size, ULONG* size_read) {
363 if (renderer_type_ == RENDERER_TYPE_UNDETERMINED) {
364 return E_PENDING;
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;
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);
384 if (size_read)
385 *size_read = bytes_to_copy + new_data;
386 return hr;
389 return read_fun_(protocol_, buffer, size, size_read);
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().
398 // With not-so-well-written software (mime filters/protocols/protocol patchers)
399 // BINDSTATUS_MIMETYPEAVAILABLE might be fired multiple times with different
400 // values for the same client.
401 // If the renderer_type_ member is:
402 // RENDERER_TYPE_CHROME_* - 2nd (and any subsequent)
403 // BINDSTATUS_MIMETYPEAVAILABLE is ignored.
404 // RENDERER_TYPE_OTHER - 2nd (and any subsequent) BINDSTATUS_MIMETYPEAVAILABLE
405 // is passed through.
406 // RENDERER_TYPE_UNDETERMINED - Try to detect ChromeFrame from HTTP headers
407 // (acts as if this is the first time
408 // BINDSTATUS_MIMETYPEAVAILABLE is received).
409 HRESULT ProtData::ReportProgress(IInternetProtocolSink* delegate,
410 ULONG status_code, LPCWSTR status_text) {
411 switch (status_code) {
412 case BINDSTATUS_DIRECTBIND:
413 renderer_type_ = RENDERER_TYPE_OTHER;
414 break;
416 case BINDSTATUS_REDIRECTING:
417 url_.clear();
418 if (status_text)
419 url_ = status_text;
420 break;
422 case BINDSTATUS_SERVER_MIMETYPEAVAILABLE:
423 has_server_mime_type_ = true;
424 SaveSuggestedMimeType(status_text);
425 return S_OK;
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);
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_);
461 return S_OK;
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);
469 HRESULT ProtData::ReportData(IInternetProtocolSink* delegate,
470 DWORD flags, ULONG progress, ULONG max_progress) {
471 if (renderer_type_ != RENDERER_TYPE_UNDETERMINED) {
472 // We are just pass through now, avoid false positive crash reports.
473 ExceptionBarrierReportOnlyModule barrier;
474 return delegate->ReportData(flags, progress, max_progress);
477 HRESULT hr = FillBuffer();
479 bool last_chance = false;
480 if (hr == S_OK || hr == S_FALSE) {
481 last_chance = true;
484 renderer_type_ = SkipMetadataCheck() ? RENDERER_TYPE_OTHER
485 : DetermineRendererType(buffer_, buffer_size_, last_chance);
487 if (renderer_type_ == RENDERER_TYPE_UNDETERMINED) {
488 // do not report anything, we need more data.
489 return S_OK;
492 if (IsChrome(renderer_type_)) {
493 DVLOG(1) << "Forwarding BINDSTATUS_MIMETYPEAVAILABLE " << kChromeMimeType;
494 SaveReferrer(delegate);
495 delegate->ReportProgress(BINDSTATUS_MIMETYPEAVAILABLE, kChromeMimeType);
498 if (renderer_type_ == RENDERER_TYPE_OTHER) {
499 FireSuggestedMimeType(delegate);
502 // This is the first data notification we forward, since up to now we hold
503 // the content received.
504 flags |= BSCF_FIRSTDATANOTIFICATION;
506 if (hr == S_FALSE) {
507 flags |= (BSCF_LASTDATANOTIFICATION | BSCF_DATAFULLYAVAILABLE);
510 return delegate->ReportData(flags, progress, max_progress);
513 HRESULT ProtData::ReportResult(IInternetProtocolSink* delegate, HRESULT result,
514 DWORD error, LPCWSTR result_text) {
515 // We may receive ReportResult without ReportData, if the connection fails
516 // for example.
517 if (renderer_type_ == RENDERER_TYPE_UNDETERMINED) {
518 DVLOG(1) << "ReportResult received but renderer type is yet unknown.";
519 renderer_type_ = RENDERER_TYPE_OTHER;
520 FireSuggestedMimeType(delegate);
523 HRESULT hr = S_OK;
524 if (delegate)
525 hr = delegate->ReportResult(result, error, result_text);
526 return hr;
530 void ProtData::UpdateUrl(const wchar_t* url) {
531 url_ = url;
534 // S_FALSE - EOF
535 // S_OK - buffer fully filled
536 // E_PENDING - some data added to buffer, but buffer is not yet full
537 // E_XXXX - some other error.
538 HRESULT ProtData::FillBuffer() {
539 HRESULT hr_read = S_OK;
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;
548 return hr_read;
551 void ProtData::SaveSuggestedMimeType(LPCWSTR status_text) {
552 has_suggested_mime_type_ = true;
553 suggested_mime_type_.Allocate(status_text);
556 void ProtData::FireSuggestedMimeType(IInternetProtocolSink* delegate) {
557 if (has_server_mime_type_) {
558 DVLOG(1) << "Forwarding BINDSTATUS_SERVER_MIMETYPEAVAILABLE "
559 << suggested_mime_type_;
560 delegate->ReportProgress(BINDSTATUS_SERVER_MIMETYPEAVAILABLE,
561 suggested_mime_type_);
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_);
572 void ProtData::SaveReferrer(IInternetProtocolSink* delegate) {
573 DCHECK(IsChrome(renderer_type_));
574 base::win::ScopedComPtr<IWinInetHttpInfo> info;
575 info.QueryFrom(delegate);
576 if (info) {
577 char buffer[4096] = {0};
578 DWORD len = sizeof(buffer);
579 DWORD flags = 0;
580 HRESULT hr = info->QueryInfo(
581 HTTP_QUERY_REFERER | HTTP_QUERY_FLAG_REQUEST_HEADERS,
582 buffer, &len, &flags, 0);
583 if (hr == S_OK && len > 0)
584 referrer_.assign(buffer);
585 } else {
586 DLOG(WARNING) << "Failed to QI for IWinInetHttpInfo";
590 scoped_refptr<ProtData> ProtData::DataFromProtocol(
591 IInternetProtocol* protocol) {
592 scoped_refptr<ProtData> instance;
593 base::AutoLock lock(datamap_lock_);
594 ProtocolDataMap::iterator it = datamap_.find(protocol);
595 if (datamap_.end() != it)
596 instance = it->second;
597 return instance;
600 void ProtData::Invalidate() {
601 if (protocol_) {
602 // Remove from map.
603 base::AutoLock lock(datamap_lock_);
604 DCHECK(datamap_.end() != datamap_.find(protocol_));
605 datamap_.erase(protocol_);
606 protocol_ = NULL;
610 // This function looks for the url pattern indicating that this request needs
611 // to be forced into chrome frame.
612 // This hack is required because window.open requests from Chrome don't have
613 // the URL up front. The URL comes in much later when the renderer initiates a
614 // top level navigation for the url passed into window.open.
615 // The new page must be rendered in ChromeFrame to preserve the opener
616 // relationship with its parent even if the new page does not have the chrome
617 // meta tag.
618 bool HandleAttachToExistingExternalTab(LPCWSTR url,
619 IInternetProtocol* protocol,
620 IInternetProtocolSink* prot_sink,
621 IBindCtx* bind_ctx) {
622 ChromeFrameUrl cf_url;
623 if (cf_url.Parse(url) && cf_url.attach_to_external_tab()) {
624 scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol);
625 if (!prot_data) {
626 // Pass NULL as the read function which indicates that always return EOF
627 // without calling the underlying protocol.
628 prot_data = new ProtData(protocol, NULL, url);
629 PutProtData(bind_ctx, prot_data);
632 prot_sink->ReportProgress(BINDSTATUS_MIMETYPEAVAILABLE, kChromeMimeType);
634 int data_flags = BSCF_FIRSTDATANOTIFICATION | BSCF_LASTDATANOTIFICATION;
635 prot_sink->ReportData(data_flags, 0, 0);
637 prot_sink->ReportResult(S_OK, 0, NULL);
638 return true;
640 return false;
643 HRESULT ForwardHookStart(InternetProtocol_Start_Fn orig_start,
644 IInternetProtocol* protocol, LPCWSTR url, IInternetProtocolSink* prot_sink,
645 IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) {
646 ExceptionBarrierReportOnlyModule barrier;
647 return orig_start(protocol, url, prot_sink, bind_info, flags, reserved);
650 HRESULT ForwardWrappedHookStart(InternetProtocol_Start_Fn orig_start,
651 IInternetProtocol* protocol, LPCWSTR url, IInternetProtocolSink* prot_sink,
652 IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) {
653 ExceptionBarrier barrier;
654 return orig_start(protocol, url, prot_sink, bind_info, flags, reserved);
657 // IInternetProtocol/Ex hooks.
658 STDMETHODIMP Hook_Start(InternetProtocol_Start_Fn orig_start,
659 IInternetProtocol* protocol, LPCWSTR url, IInternetProtocolSink* prot_sink,
660 IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) {
661 DCHECK(orig_start);
662 if (!url || !prot_sink || !bind_info)
663 return E_INVALIDARG;
664 DVLOG_IF(1, url != NULL) << "OnStart: " << url << PiFlags2Str(flags);
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);
675 scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol);
676 if (prot_data && !HasProtData(bind_ctx)) {
677 prot_data->Invalidate();
678 prot_data = NULL;
681 if (HandleAttachToExistingExternalTab(url, protocol, prot_sink, bind_ctx)) {
682 return S_OK;
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);
692 return ForwardHookStart(orig_start, protocol, url, prot_sink, bind_info,
693 flags, reserved);
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);
705 if (!ShouldWrapSink(prot_sink, url)) {
706 return ForwardHookStart(orig_start, protocol, url, prot_sink, bind_info,
707 flags, reserved);
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);
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);
722 HRESULT ForwardHookStartEx(InternetProtocol_StartEx_Fn orig_start_ex,
723 IInternetProtocolEx* protocol, IUri* uri, IInternetProtocolSink* prot_sink,
724 IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) {
725 ExceptionBarrierReportOnlyModule barrier;
726 return orig_start_ex(protocol, uri, prot_sink, bind_info, flags, reserved);
729 HRESULT ForwardWrappedHookStartEx(InternetProtocol_StartEx_Fn orig_start_ex,
730 IInternetProtocolEx* protocol, IUri* uri, IInternetProtocolSink* prot_sink,
731 IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) {
732 ExceptionBarrier barrier;
733 return orig_start_ex(protocol, uri, prot_sink, bind_info, flags, reserved);
736 STDMETHODIMP Hook_StartEx(InternetProtocol_StartEx_Fn orig_start_ex,
737 IInternetProtocolEx* protocol, IUri* uri, IInternetProtocolSink* prot_sink,
738 IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) {
739 DCHECK(orig_start_ex);
740 if (!uri || !prot_sink || !bind_info)
741 return E_INVALIDARG;
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);
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);
756 scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol);
757 if (prot_data && !HasProtData(bind_ctx)) {
758 prot_data->Invalidate();
759 prot_data = NULL;
762 if (HandleAttachToExistingExternalTab(url, protocol, prot_sink, bind_ctx)) {
763 return S_OK;
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);
773 return ForwardHookStartEx(orig_start_ex, protocol, uri, prot_sink,
774 bind_info, flags, reserved);
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);
786 if (!ShouldWrapSink(prot_sink, url)) {
787 return ForwardHookStartEx(orig_start_ex, protocol, uri, prot_sink,
788 bind_info, flags, reserved);
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);
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);
803 STDMETHODIMP Hook_Read(InternetProtocol_Read_Fn orig_read,
804 IInternetProtocol* protocol, void* buffer, ULONG size, ULONG* size_read) {
805 DCHECK(orig_read);
806 HRESULT hr = E_FAIL;
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;
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;
823 hr = prot_data->Read(buffer, size, size_read);
824 return hr;
827 STDMETHODIMP Hook_LockRequest(InternetProtocol_LockRequest_Fn orig_req,
828 IInternetProtocol* protocol,
829 DWORD options) {
830 DCHECK(orig_req);
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;
838 // We are just pass through at this point, avoid false positive crash
839 // reports.
840 ExceptionBarrierReportOnlyModule barrier;
841 return orig_req(protocol, options);
844 STDMETHODIMP Hook_UnlockRequest(InternetProtocol_UnlockRequest_Fn orig_req,
845 IInternetProtocol* protocol) {
846 DCHECK(orig_req);
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;
854 // We are just pass through at this point, avoid false positive crash
855 // reports.
856 ExceptionBarrierReportOnlyModule barrier;
857 return orig_req(protocol);
860 STDMETHODIMP Hook_Abort(InternetProtocol_Abort_Fn orig_req,
861 IInternetProtocol* protocol,
862 HRESULT hr,
863 DWORD options) {
864 scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol);
865 if (prot_data)
866 prot_data->Invalidate();
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);
874 STDMETHODIMP Hook_Terminate(InternetProtocol_Terminate_Fn orig_req,
875 IInternetProtocol* protocol,
876 DWORD options) {
877 scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol);
878 // We should not be invalidating the cached protocol data in the following
879 // cases:-
880 // 1. Pages which are switching into ChromeFrame.
881 // When IE switches into ChromeFrame, we report the Chrome mime type as
882 // the handler for the page. This results in urlmon terminating the
883 // protocol. When Chrome attempts to read the data we need to report the
884 // cached data back to Chrome.
885 // 2. For the attach external tab requests which are temporary navigations
886 // to ensure that a top level URL is opened in IE from ChromeFrame.
887 // We rely on the mapping to identify these requests as attach tab
888 // requests. This mapping is referred to in the
889 // IInternetProtocol::LockRequest/IInternetProtocol::UnlockRequest
890 // intercepts. Invalidating the mapping after LockRequest is called and
891 // before UnlockRequest causes IE to crash.
892 if (prot_data && !IsChrome(prot_data->renderer_type()) &&
893 !prot_data->is_attach_external_tab_request())
894 prot_data->Invalidate();
896 // We are just pass through at this point, avoid false positive crash
897 // reports.
898 ExceptionBarrierReportOnlyModule barrier;
899 return orig_req(protocol, options);
902 // Patching / Hooking code.
903 class FakeProtocol : public CComObjectRootEx<CComSingleThreadModel>,
904 public IInternetProtocol {
905 public:
906 BEGIN_COM_MAP(FakeProtocol)
907 COM_INTERFACE_ENTRY(IInternetProtocol)
908 COM_INTERFACE_ENTRY(IInternetProtocolRoot)
909 END_COM_MAP()
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;
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; }
929 base::win::ScopedComPtr<IInternetProtocol> transaction_;
932 struct FakeFactory : public IClassFactory,
933 public CComObjectRootEx<CComSingleThreadModel> {
934 BEGIN_COM_MAP(FakeFactory)
935 COM_INTERFACE_ENTRY(IClassFactory)
936 END_COM_MAP()
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;
945 STDMETHOD(LockServer)(BOOL fLock) {
946 return S_OK;
949 IUnknown* obj_;
952 static void HookTransactionVtable(IInternetProtocol* p) {
953 base::win::ScopedComPtr<IInternetProtocolEx> ex;
954 ex.QueryFrom(p);
956 HRESULT hr = vtable_patch::PatchInterfaceMethods(p, CTransaction_PatchInfo);
957 if (hr == S_OK && ex) {
958 vtable_patch::PatchInterfaceMethods(ex.get(), CTransaction2_PatchInfo);
962 void TransactionHooks::InstallHooks() {
963 if (IS_PATCHED(CTransaction)) {
964 DLOG(WARNING) << __FUNCTION__ << " called more than once.";
965 return;
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;
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;
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;
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);
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();
1007 void TransactionHooks::RevertHooks() {
1008 vtable_patch::UnpatchInterfaceMethods(CTransaction_PatchInfo);
1009 vtable_patch::UnpatchInterfaceMethods(CTransaction2_PatchInfo);