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