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.
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
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
,
49 IInternetProtocolSink
* prot_sink
,
50 IInternetBindInfo
* bind_info
,
54 STDMETHODIMP
Hook_StartEx(InternetProtocol_StartEx_Fn orig_start_ex
,
55 IInternetProtocolEx
* protocol
,
57 IInternetProtocolSink
* prot_sink
,
58 IInternetBindInfo
* bind_info
,
62 STDMETHODIMP
Hook_Read(InternetProtocol_Read_Fn orig_read
,
63 IInternetProtocol
* protocol
,
68 STDMETHODIMP
Hook_LockRequest(InternetProtocol_LockRequest_Fn orig_req
,
69 IInternetProtocol
* protocol
,
72 STDMETHODIMP
Hook_UnlockRequest(InternetProtocol_UnlockRequest_Fn orig_req
,
73 IInternetProtocol
* protocol
);
75 STDMETHODIMP
Hook_Abort(InternetProtocol_Abort_Fn orig_req
,
76 IInternetProtocol
* protocol
,
80 STDMETHODIMP
Hook_Terminate(InternetProtocol_Terminate_Fn orig_req
,
81 IInternetProtocol
* protocol
,
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
)
94 BEGIN_VTABLE_PATCHES(CTransaction2
)
95 VTABLE_PATCH_ENTRY(kInternetProtocolStartExIndex
, Hook_StartEx
)
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
) {
128 hr
= delegate_
->Switch(protocol_data
);
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
);
142 STDMETHODIMP
ProtocolSinkWrap::ReportData(DWORD flags
, ULONG progress
,
143 ULONG max_progress
) {
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
);
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
);
164 base::win::ScopedComPtr
<IBindCtx
> BindCtxFromIBindInfo(
165 IInternetBindInfo
* bind_info
) {
166 LPOLESTR bind_ctx_string
= NULL
;
168 base::win::ScopedComPtr
<IBindCtx
> bind_ctx
;
169 bind_info
->GetBindString(BINDSTRING_PTR_BIND_CONTEXT
, &bind_ctx_string
, 1,
171 if (bind_ctx_string
) {
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
);
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).
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://")))
194 base::win::ScopedComPtr
<IHttpNegotiate
> http_negotiate
;
195 HRESULT hr
= DoQueryService(GUID_NULL
, sink
, http_negotiate
.Receive());
196 if (http_negotiate
&& !IsSubFrameRequest(http_negotiate
))
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())
212 bool HasProtData(IBindCtx
* pbc
) {
213 base::win::ScopedComPtr
<BindContextInfo
> info
;
214 BindContextInfo::FromBindContext(pbc
, info
.Receive());
217 result
= info
->has_prot_data();
221 void PutProtData(IBindCtx
* pbc
, ProtData
* data
) {
222 // AddRef and Release to avoid a potential leak of a ProtData instance if
223 // FromBindContext fails.
225 base::win::ScopedComPtr
<BindContextInfo
> info
;
226 BindContextInfo::FromBindContext(pbc
, info
.Receive());
228 info
->set_prot_data(data
);
232 bool IsTextHtml(const wchar_t* status_text
) {
233 const std::wstring str
= status_text
;
234 bool is_text_html
= LowerCaseEqualsASCII(str
, kTextHtmlMimeType
);
238 bool IsAdditionallySupportedContentType(const wchar_t* status_text
) {
239 static const char* kHeaderContentTypes
[] = {
240 "application/xhtml+xml",
250 const std::wstring str
= status_text
;
251 for (int i
= 0; i
< arraysize(kHeaderContentTypes
); ++i
) {
252 if (LowerCaseEqualsASCII(str
, kHeaderContentTypes
[i
]))
256 if (PolicySettings::GetInstance()->GetRendererForContentType(
257 status_text
) == PolicySettings::RENDER_IN_CHROME_FRAME
) {
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
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
;
286 RendererType renderer_type
= RendererTypeForUrl(url
);
287 if (IsChrome(renderer_type
)) {
288 return renderer_type
;
293 char buffer
[512] = "x-ua-compatible";
294 DWORD len
= sizeof(buffer
);
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.
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
;
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
;
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;
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;
359 HRESULT
ProtData::Read(void* buffer
, ULONG size
, ULONG
* size_read
) {
360 if (renderer_type_
== RENDERER_TYPE_UNDETERMINED
) {
364 const ULONG bytes_available
= buffer_size_
- buffer_pos_
;
365 const ULONG bytes_to_copy
= std::min(bytes_available
, size
);
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
;
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
);
382 *size_read
= bytes_to_copy
+ new_data
;
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
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
;
413 case BINDSTATUS_REDIRECTING
:
419 case BINDSTATUS_SERVER_MIMETYPEAVAILABLE
:
420 has_server_mime_type_
= true;
421 SaveSuggestedMimeType(status_text
);
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
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_
,
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 "
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
);
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_
);
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
) {
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.
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
;
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
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
);
521 hr
= delegate
->ReportResult(result
, error
, result_text
);
526 void ProtData::UpdateUrl(const wchar_t* url
) {
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
)) {
539 hr_read
= read_fun_(protocol_
, buffer_
+ buffer_size_
,
540 kMaxContentSniffLength
- buffer_size_
, &size_read
);
541 buffer_size_
+= size_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
);
573 char buffer
[4096] = {0};
574 DWORD len
= sizeof(buffer
);
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
);
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
;
596 void ProtData::Invalidate() {
599 base::AutoLock
lock(datamap_lock_
);
600 DCHECK(datamap_
.end() != datamap_
.find(protocol_
));
601 datamap_
.erase(protocol_
);
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
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
);
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
);
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
) {
658 if (!url
|| !prot_sink
|| !bind_info
)
660 DVLOG_IF(1, url
!= NULL
) << "OnStart: " << url
<< PiFlags2Str(flags
);
662 base::win::ScopedComPtr
<IBindCtx
> bind_ctx
= BindCtxFromIBindInfo(bind_info
);
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
,
671 scoped_refptr
<ProtData
> prot_data
= ProtData::DataFromProtocol(protocol
);
672 if (prot_data
&& !HasProtData(bind_ctx
)) {
673 prot_data
->Invalidate();
677 if (HandleAttachToExistingExternalTab(url
, protocol
, prot_sink
, bind_ctx
)) {
681 if (IsCFRequest(bind_ctx
)) {
682 base::win::ScopedComPtr
<BindContextInfo
> info
;
683 BindContextInfo::FromBindContext(bind_ctx
, info
.Receive());
686 info
->set_protocol(protocol
);
688 return ForwardHookStart(orig_start
, protocol
, url
, prot_sink
, bind_info
,
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
,
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
,
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
)
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
);
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();
758 if (HandleAttachToExistingExternalTab(url
, protocol
, prot_sink
, bind_ctx
)) {
762 if (IsCFRequest(bind_ctx
)) {
763 base::win::ScopedComPtr
<BindContextInfo
> info
;
764 BindContextInfo::FromBindContext(bind_ctx
, info
.Receive());
767 info
->set_protocol(protocol
);
769 return ForwardHookStartEx(orig_start_ex
, protocol
, uri
, prot_sink
,
770 bind_info
, flags
, reserved
);
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
);
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
) {
804 scoped_refptr
<ProtData
> prot_data
= ProtData::DataFromProtocol(protocol
);
806 // We are not wrapping this request, avoid false positive crash reports.
807 ExceptionBarrierReportOnlyModule barrier
;
808 hr
= orig_read(protocol
, buffer
, size
, size_read
);
812 if (prot_data
->is_attach_external_tab_request()) {
813 // return EOF always.
819 hr
= prot_data
->Read(buffer
, size
, size_read
);
823 STDMETHODIMP
Hook_LockRequest(InternetProtocol_LockRequest_Fn orig_req
,
824 IInternetProtocol
* protocol
,
828 scoped_refptr
<ProtData
> prot_data
= ProtData::DataFromProtocol(protocol
);
829 if (prot_data
&& prot_data
->is_attach_external_tab_request()) {
834 // We are just pass through at this point, avoid false positive crash
836 ExceptionBarrierReportOnlyModule barrier
;
837 return orig_req(protocol
, options
);
840 STDMETHODIMP
Hook_UnlockRequest(InternetProtocol_UnlockRequest_Fn orig_req
,
841 IInternetProtocol
* protocol
) {
844 scoped_refptr
<ProtData
> prot_data
= ProtData::DataFromProtocol(protocol
);
845 if (prot_data
&& prot_data
->is_attach_external_tab_request()) {
846 prot_data
->Release();
850 // We are just pass through at this point, avoid false positive crash
852 ExceptionBarrierReportOnlyModule barrier
;
853 return orig_req(protocol
);
856 STDMETHODIMP
Hook_Abort(InternetProtocol_Abort_Fn orig_req
,
857 IInternetProtocol
* protocol
,
860 scoped_refptr
<ProtData
> prot_data
= ProtData::DataFromProtocol(protocol
);
862 prot_data
->Invalidate();
864 // We are just pass through at this point, avoid false positive crash
866 ExceptionBarrierReportOnlyModule barrier
;
867 return orig_req(protocol
, hr
, options
);
870 STDMETHODIMP
Hook_Terminate(InternetProtocol_Terminate_Fn orig_req
,
871 IInternetProtocol
* protocol
,
873 scoped_refptr
<ProtData
> prot_data
= ProtData::DataFromProtocol(protocol
);
874 // We should not be invalidating the cached protocol data in the following
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
894 ExceptionBarrierReportOnlyModule barrier
;
895 return orig_req(protocol
, options
);
898 // Patching / Hooking code.
899 class FakeProtocol
: public CComObjectRootEx
<CComSingleThreadModel
>,
900 public IInternetProtocol
{
902 BEGIN_COM_MAP(FakeProtocol
)
903 COM_INTERFACE_ENTRY(IInternetProtocol
)
904 COM_INTERFACE_ENTRY(IInternetProtocolRoot
)
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
)
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
)
934 STDMETHOD(CreateInstance
)(IUnknown
*pUnkOuter
, REFIID riid
, void **ppvObj
) {
936 return CLASS_E_NOAGGREGATION
;
937 HRESULT hr
= obj_
->QueryInterface(riid
, ppvObj
);
941 STDMETHOD(LockServer
)(BOOL fLock
) {
948 static void HookTransactionVtable(IInternetProtocol
* p
) {
949 base::win::ScopedComPtr
<IInternetProtocolEx
> ex
;
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.";
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";
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
;
983 hr
= ::CreateURLMoniker(NULL
, L
"611://512", mk
.Receive());
984 DLOG_IF(FATAL
, FAILED(hr
)) << "CreateURLMoniker failed " << hr
;
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
;
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
);