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. 4 5 #include <htiframe.h> 6 #include <mshtml.h> 7 8 #include "chrome_frame/protocol_sink_wrap.h" 9 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" 23 24 // BINDSTATUS_SERVER_MIMETYPEAVAILABLE == 54. Introduced in IE 8, so 25 // not in everyone's headers yet. See: 26 // http://msdn.microsoft.com/en-us/library/ms775133(VS.85,loband).aspx 27 #ifndef BINDSTATUS_SERVER_MIMETYPEAVAILABLE 28 #define BINDSTATUS_SERVER_MIMETYPEAVAILABLE 54 29 #endif 30 31 bool ProtocolSinkWrap::ignore_xua_ = false; 32 33 static const char kTextHtmlMimeType[] = "text/html"; 34 const wchar_t kUrlMonDllName[] = L"urlmon.dll"; 35 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; 43 44 45 // IInternetProtocol/Ex patches. 46 STDMETHODIMP Hook_Start(InternetProtocol_Start_Fn orig_start, 47 IInternetProtocol* protocol, 48 LPCWSTR url, 49 IInternetProtocolSink* prot_sink, 50 IInternetBindInfo* bind_info, 51 DWORD flags, 52 HANDLE_PTR reserved); 53 54 STDMETHODIMP Hook_StartEx(InternetProtocol_StartEx_Fn orig_start_ex, 55 IInternetProtocolEx* protocol, 56 IUri* uri, 57 IInternetProtocolSink* prot_sink, 58 IInternetBindInfo* bind_info, 59 DWORD flags, 60 HANDLE_PTR reserved); 61 62 STDMETHODIMP Hook_Read(InternetProtocol_Read_Fn orig_read, 63 IInternetProtocol* protocol, 64 void* buffer, 65 ULONG size, 66 ULONG* size_read); 67 68 STDMETHODIMP Hook_LockRequest(InternetProtocol_LockRequest_Fn orig_req, 69 IInternetProtocol* protocol, 70 DWORD options); 71 72 STDMETHODIMP Hook_UnlockRequest(InternetProtocol_UnlockRequest_Fn orig_req, 73 IInternetProtocol* protocol); 74 75 STDMETHODIMP Hook_Abort(InternetProtocol_Abort_Fn orig_req, 76 IInternetProtocol* protocol, 77 HRESULT hr, 78 DWORD options); 79 80 STDMETHODIMP Hook_Terminate(InternetProtocol_Terminate_Fn orig_req, 81 IInternetProtocol* protocol, 82 DWORD options); 83 84 ///////////////////////////////////////////////////////////////////////////// 85 BEGIN_VTABLE_PATCHES(CTransaction) 86 VTABLE_PATCH_ENTRY(kInternetProtocolStartIndex, Hook_Start) 87 VTABLE_PATCH_ENTRY(kInternetProtocolReadIndex, Hook_Read) 88 VTABLE_PATCH_ENTRY(kInternetProtocolLockRequestIndex, Hook_LockRequest) 89 VTABLE_PATCH_ENTRY(kInternetProtocolUnlockRequestIndex, Hook_UnlockRequest) 90 VTABLE_PATCH_ENTRY(kInternetProtocolAbortIndex, Hook_Abort) 91 VTABLE_PATCH_ENTRY(kInternetProtocolTerminateIndex, Hook_Terminate) 92 END_VTABLE_PATCHES() 93 94 BEGIN_VTABLE_PATCHES(CTransaction2) 95 VTABLE_PATCH_ENTRY(kInternetProtocolStartExIndex, Hook_StartEx) 96 END_VTABLE_PATCHES() 97 98 // 99 // ProtocolSinkWrap implementation 100 101 // Static map initialization 102 ProtData::ProtocolDataMap ProtData::datamap_; 103 base::Lock ProtData::datamap_lock_; 104 105 ProtocolSinkWrap::ProtocolSinkWrap() { 106 DVLOG(1) << __FUNCTION__ << base::StringPrintf(" 0x%08X", this); 107 } 108 109 ProtocolSinkWrap::~ProtocolSinkWrap() { 110 DVLOG(1) << __FUNCTION__ << base::StringPrintf(" 0x%08X", this); 111 } 112 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); 122 } 123 124 // IInternetProtocolSink methods 125 STDMETHODIMP ProtocolSinkWrap::Switch(PROTOCOLDATA* protocol_data) { 126 HRESULT hr = E_FAIL; 127 if (delegate_) 128 hr = delegate_->Switch(protocol_data); 129 return hr; 130 } 131 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""); 137 138 HRESULT hr = prot_data_->ReportProgress(delegate_, status_code, status_text); 139 return hr; 140 } 141 142 STDMETHODIMP ProtocolSinkWrap::ReportData(DWORD flags, ULONG progress, 143 ULONG max_progress) { 144 DCHECK(delegate_); 145 DVLOG(1) << "ProtocolSinkWrap::ReportData: " << Bscf2Str(flags) 146 << " progress: " << progress << " progress_max: " << max_progress; 147 148 HRESULT hr = prot_data_->ReportData(delegate_, flags, progress, max_progress); 149 return hr; 150 } 151 152 STDMETHODIMP ProtocolSinkWrap::ReportResult(HRESULT result, DWORD error, 153 LPCWSTR result_text) { 154 DVLOG(1) << "ProtocolSinkWrap::ReportResult: result: " << result 155 << " error: " << error 156 << " Text: " << (result_text ? result_text : L""); 157 ExceptionBarrier barrier; 158 HRESULT hr = prot_data_->ReportResult(delegate_, result, error, result_text); 159 return hr; 160 } 161 162 163 // Helpers 164 base::win::ScopedComPtr<IBindCtx> BindCtxFromIBindInfo( 165 IInternetBindInfo* bind_info) { 166 LPOLESTR bind_ctx_string = NULL; 167 ULONG count; 168 base::win::ScopedComPtr<IBindCtx> bind_ctx; 169 bind_info->GetBindString(BINDSTRING_PTR_BIND_CONTEXT, &bind_ctx_string, 1, 170 &count); 171 if (bind_ctx_string) { 172 int bind_ctx_int; 173 base::StringToInt(bind_ctx_string, &bind_ctx_int); 174 IBindCtx* pbc = reinterpret_cast<IBindCtx*>(bind_ctx_int); 175 bind_ctx.Attach(pbc); 176 CoTaskMemFree(bind_ctx_string); 177 } 178 179 return bind_ctx; 180 } 181 182 bool ShouldWrapSink(IInternetProtocolSink* sink, const wchar_t* url) { 183 // Ignore everything that does not start with http:// or https://. 184 // |url| is already normalized (i.e. no leading spaces, capital letters in 185 // protocol etc) and non-null (we check in Hook_Start). 186 DCHECK(url != NULL); 187 188 if (ProtocolSinkWrap::ignore_xua()) 189 return false; // No need to intercept, we're ignoring X-UA-Compatible tags 190 191 if ((url != StrStrW(url, L"http://")) && (url != StrStrW(url, L"https://"))) 192 return false; 193 194 base::win::ScopedComPtr<IHttpNegotiate> http_negotiate; 195 HRESULT hr = DoQueryService(GUID_NULL, sink, http_negotiate.Receive()); 196 if (http_negotiate && !IsSubFrameRequest(http_negotiate)) 197 return true; 198 199 return false; 200 } 201 202 // High level helpers 203 bool IsCFRequest(IBindCtx* pbc) { 204 base::win::ScopedComPtr<BindContextInfo> info; 205 BindContextInfo::FromBindContext(pbc, info.Receive()); 206 if (info && info->chrome_request()) 207 return true; 208 209 return false; 210 } 211 212 bool HasProtData(IBindCtx* pbc) { 213 base::win::ScopedComPtr<BindContextInfo> info; 214 BindContextInfo::FromBindContext(pbc, info.Receive()); 215 bool result = false; 216 if (info) 217 result = info->has_prot_data(); 218 return result; 219 } 220 221 void PutProtData(IBindCtx* pbc, ProtData* data) { 222 // AddRef and Release to avoid a potential leak of a ProtData instance if 223 // FromBindContext fails. 224 data->AddRef(); 225 base::win::ScopedComPtr<BindContextInfo> info; 226 BindContextInfo::FromBindContext(pbc, info.Receive()); 227 if (info) 228 info->set_prot_data(data); 229 data->Release(); 230 } 231 232 bool IsTextHtml(const wchar_t* status_text) { 233 const std::wstring str = status_text; 234 bool is_text_html = LowerCaseEqualsASCII(str, kTextHtmlMimeType); 235 return is_text_html; 236 } 237 238 bool IsAdditionallySupportedContentType(const wchar_t* status_text) { 239 static const char* kHeaderContentTypes[] = { 240 "application/xhtml+xml", 241 "application/xml", 242 "image/svg", 243 "image/svg+xml", 244 "text/xml", 245 "video/ogg", 246 "video/webm", 247 "video/mp4" 248 }; 249 250 const std::wstring str = status_text; 251 for (int i = 0; i < arraysize(kHeaderContentTypes); ++i) { 252 if (LowerCaseEqualsASCII(str, kHeaderContentTypes[i])) 253 return true; 254 } 255 256 if (PolicySettings::GetInstance()->GetRendererForContentType( 257 status_text) == PolicySettings::RENDER_IN_CHROME_FRAME) { 258 return true; 259 } 260 261 return false; 262 } 263 264 // Returns: 265 // RENDERER_TYPE_OTHER: if suggested mime type is not text/html. 266 // RENDERER_TYPE_UNDETERMINED: if suggested mime type is text/html. 267 // RENDERER_TYPE_CHROME_RESPONSE_HEADER: X-UA-Compatible tag is in HTTP headers. 268 // RENDERER_TYPE_CHROME_DEFAULT_RENDERER: GCF is the default renderer and the 269 // Url is not listed in the 270 // RenderInHostUrls registry key. 271 // RENDERER_TYPE_CHROME_OPT_IN_URL: GCF is not the default renderer and the Url 272 // is listed in the RenderInGcfUrls registry 273 // key. 274 RendererType DetermineRendererTypeFromMetaData( 275 const wchar_t* suggested_mime_type, 276 const std::wstring& url, 277 IWinInetHttpInfo* info) { 278 bool is_text_html = IsTextHtml(suggested_mime_type); 279 bool is_supported_content_type = is_text_html || 280 IsAdditionallySupportedContentType(suggested_mime_type); 281 282 if (!is_supported_content_type) 283 return RENDERER_TYPE_OTHER; 284 285 if (!url.empty()) { 286 RendererType renderer_type = RendererTypeForUrl(url); 287 if (IsChrome(renderer_type)) { 288 return renderer_type; 289 } 290 } 291 292 if (info) { 293 char buffer[512] = "x-ua-compatible"; 294 DWORD len = sizeof(buffer); 295 DWORD flags = 0; 296 HRESULT hr = info->QueryInfo(HTTP_QUERY_CUSTOM, buffer, &len, &flags, NULL); 297 298 if (hr == S_OK && len > 0) { 299 if (CheckXUaCompatibleDirective(buffer, GetIEMajorVersion())) { 300 return RENDERER_TYPE_CHROME_RESPONSE_HEADER; 301 } 302 } 303 } 304 305 // We can (and want) to sniff the content. 306 if (is_text_html) { 307 return RENDERER_TYPE_UNDETERMINED; 308 } 309 310 // We cannot sniff the content. 311 return RENDERER_TYPE_OTHER; 312 } 313 314 RendererType DetermineRendererType(void* buffer, DWORD size, bool last_chance) { 315 RendererType renderer_type = RENDERER_TYPE_UNDETERMINED; 316 if (last_chance) 317 renderer_type = RENDERER_TYPE_OTHER; 318 319 std::wstring html_contents; 320 // TODO(joshia): detect and handle different content encodings 321 UTF8ToWide(reinterpret_cast<char*>(buffer), size, &html_contents); 322 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; 332 } 333 } 334 335 return renderer_type; 336 } 337 338 // ProtData 339 ProtData::ProtData(IInternetProtocol* protocol, 340 InternetProtocol_Read_Fn read_fun, const wchar_t* url) 341 : has_suggested_mime_type_(false), has_server_mime_type_(false), 342 buffer_size_(0), buffer_pos_(0), 343 renderer_type_(RENDERER_TYPE_UNDETERMINED), protocol_(protocol), 344 read_fun_(read_fun), url_(url) { 345 memset(buffer_, 0, arraysize(buffer_)); 346 DVLOG(1) << __FUNCTION__ << " " << this; 347 348 // Add to map. 349 base::AutoLock lock(datamap_lock_); 350 DCHECK(datamap_.end() == datamap_.find(protocol_)); 351 datamap_[protocol] = this; 352 } 353 354 ProtData::~ProtData() { 355 DVLOG(1) << __FUNCTION__ << " " << this; 356 Invalidate(); 357 } 358 359 HRESULT ProtData::Read(void* buffer, ULONG size, ULONG* size_read) { 360 if (renderer_type_ == RENDERER_TYPE_UNDETERMINED) { 361 return E_PENDING; 362 } 363 364 const ULONG bytes_available = buffer_size_ - buffer_pos_; 365 const ULONG bytes_to_copy = std::min(bytes_available, size); 366 if (bytes_to_copy) { 367 // Copy from the local buffer. 368 memcpy(buffer, buffer_ + buffer_pos_, bytes_to_copy); 369 *size_read = bytes_to_copy; 370 buffer_pos_ += bytes_to_copy; 371 372 HRESULT hr = S_OK; 373 ULONG new_data = 0; 374 if (size > bytes_available) { 375 // User buffer is greater than what we have. 376 buffer = reinterpret_cast<uint8*>(buffer) + bytes_to_copy; 377 size -= bytes_to_copy; 378 hr = read_fun_(protocol_, buffer, size, &new_data); 379 } 380 381 if (size_read) 382 *size_read = bytes_to_copy + new_data; 383 return hr; 384 } 385 386 return read_fun_(protocol_, buffer, size, size_read); 387 } 388 389 // Attempt to detect ChromeFrame from HTTP headers when 390 // BINDSTATUS_MIMETYPEAVAILABLE is received. 391 // There are three possible outcomes: CHROME_*/OTHER/UNDETERMINED. 392 // If RENDERER_TYPE_UNDETERMINED we are going to sniff the content later in 393 // ReportData(). 394 // 395 // With not-so-well-written software (mime filters/protocols/protocol patchers) 396 // BINDSTATUS_MIMETYPEAVAILABLE might be fired multiple times with different 397 // values for the same client. 398 // If the renderer_type_ member is: 399 // RENDERER_TYPE_CHROME_* - 2nd (and any subsequent) 400 // BINDSTATUS_MIMETYPEAVAILABLE is ignored. 401 // RENDERER_TYPE_OTHER - 2nd (and any subsequent) BINDSTATUS_MIMETYPEAVAILABLE 402 // is passed through. 403 // RENDERER_TYPE_UNDETERMINED - Try to detect ChromeFrame from HTTP headers 404 // (acts as if this is the first time 405 // BINDSTATUS_MIMETYPEAVAILABLE is received). 406 HRESULT ProtData::ReportProgress(IInternetProtocolSink* delegate, 407 ULONG status_code, LPCWSTR status_text) { 408 switch (status_code) { 409 case BINDSTATUS_DIRECTBIND: 410 renderer_type_ = RENDERER_TYPE_OTHER; 411 break; 412 413 case BINDSTATUS_REDIRECTING: 414 url_.clear(); 415 if (status_text) 416 url_ = status_text; 417 break; 418 419 case BINDSTATUS_SERVER_MIMETYPEAVAILABLE: 420 has_server_mime_type_ = true; 421 SaveSuggestedMimeType(status_text); 422 return S_OK; 423 424 // TODO(stoyan): BINDSTATUS_RAWMIMETYPE 425 case BINDSTATUS_MIMETYPEAVAILABLE: 426 case BINDSTATUS_VERIFIEDMIMETYPEAVAILABLE: 427 SaveSuggestedMimeType(status_text); 428 // When Transaction is attached i.e. when existing BTS it terminated 429 // and "converted" to BTO, events will be re-fired for the new sink, 430 // but we may skip the renderer_type_ determination since it's already 431 // done. 432 if (renderer_type_ == RENDERER_TYPE_UNDETERMINED) { 433 // This may seem awkward. CBinding's implementation of IWinInetHttpInfo 434 // will forward to CTransaction that will forward to the real protocol. 435 // We may ask CTransaction (our protocol_ member) for IWinInetHttpInfo. 436 base::win::ScopedComPtr<IWinInetHttpInfo> info; 437 info.QueryFrom(delegate); 438 renderer_type_ = DetermineRendererTypeFromMetaData(suggested_mime_type_, 439 url_, info); 440 } 441 442 if (IsChrome(renderer_type_)) { 443 // Suggested mime type is "text/html" and we have DEFAULT_RENDERER, 444 // OPT_IN_URL, or RESPONSE_HEADER. 445 DVLOG(1) << "Forwarding BINDSTATUS_MIMETYPEAVAILABLE " 446 << kChromeMimeType; 447 SaveReferrer(delegate); 448 delegate->ReportProgress(BINDSTATUS_MIMETYPEAVAILABLE, kChromeMimeType); 449 } else if (renderer_type_ == RENDERER_TYPE_OTHER) { 450 // Suggested mime type is not "text/html" - we are not interested in 451 // this request anymore. 452 FireSuggestedMimeType(delegate); 453 } else { 454 // Suggested mime type is "text/html"; We will try to sniff the 455 // HTML content in ReportData. 456 DCHECK_EQ(RENDERER_TYPE_UNDETERMINED, renderer_type_); 457 } 458 return S_OK; 459 } 460 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); 464 } 465 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); 472 } 473 474 HRESULT hr = FillBuffer(); 475 476 bool last_chance = false; 477 if (hr == S_OK || hr == S_FALSE) { 478 last_chance = true; 479 } 480 481 renderer_type_ = DetermineRendererType(buffer_, buffer_size_, last_chance); 482 483 if (renderer_type_ == RENDERER_TYPE_UNDETERMINED) { 484 // do not report anything, we need more data. 485 return S_OK; 486 } 487 488 if (IsChrome(renderer_type_)) { 489 DVLOG(1) << "Forwarding BINDSTATUS_MIMETYPEAVAILABLE " << kChromeMimeType; 490 SaveReferrer(delegate); 491 delegate->ReportProgress(BINDSTATUS_MIMETYPEAVAILABLE, kChromeMimeType); 492 } 493 494 if (renderer_type_ == RENDERER_TYPE_OTHER) { 495 FireSuggestedMimeType(delegate); 496 } 497 498 // This is the first data notification we forward, since up to now we hold 499 // the content received. 500 flags |= BSCF_FIRSTDATANOTIFICATION; 501 502 if (hr == S_FALSE) { 503 flags |= (BSCF_LASTDATANOTIFICATION | BSCF_DATAFULLYAVAILABLE); 504 } 505 506 return delegate->ReportData(flags, progress, max_progress); 507 } 508 509 HRESULT ProtData::ReportResult(IInternetProtocolSink* delegate, HRESULT result, 510 DWORD error, LPCWSTR result_text) { 511 // We may receive ReportResult without ReportData, if the connection fails 512 // for example. 513 if (renderer_type_ == RENDERER_TYPE_UNDETERMINED) { 514 DVLOG(1) << "ReportResult received but renderer type is yet unknown."; 515 renderer_type_ = RENDERER_TYPE_OTHER; 516 FireSuggestedMimeType(delegate); 517 } 518 519 HRESULT hr = S_OK; 520 if (delegate) 521 hr = delegate->ReportResult(result, error, result_text); 522 return hr; 523 } 524 525 526 void ProtData::UpdateUrl(const wchar_t* url) { 527 url_ = url; 528 } 529 530 // S_FALSE - EOF 531 // S_OK - buffer fully filled 532 // E_PENDING - some data added to buffer, but buffer is not yet full 533 // E_XXXX - some other error. 534 HRESULT ProtData::FillBuffer() { 535 HRESULT hr_read = S_OK; 536 537 while ((hr_read == S_OK) && (buffer_size_ < kMaxContentSniffLength)) { 538 ULONG size_read = 0; 539 hr_read = read_fun_(protocol_, buffer_ + buffer_size_, 540 kMaxContentSniffLength - buffer_size_, &size_read); 541 buffer_size_ += size_read; 542 } 543 544 return hr_read; 545 } 546 547 void ProtData::SaveSuggestedMimeType(LPCWSTR status_text) { 548 has_suggested_mime_type_ = true; 549 suggested_mime_type_.Allocate(status_text); 550 } 551 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_); 558 } 559 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_); 565 } 566 } 567 568 void ProtData::SaveReferrer(IInternetProtocolSink* delegate) { 569 DCHECK(IsChrome(renderer_type_)); 570 base::win::ScopedComPtr<IWinInetHttpInfo> info; 571 info.QueryFrom(delegate); 572 if (info) { 573 char buffer[4096] = {0}; 574 DWORD len = sizeof(buffer); 575 DWORD flags = 0; 576 HRESULT hr = info->QueryInfo( 577 HTTP_QUERY_REFERER | HTTP_QUERY_FLAG_REQUEST_HEADERS, 578 buffer, &len, &flags, 0); 579 if (hr == S_OK && len > 0) 580 referrer_.assign(buffer); 581 } else { 582 DLOG(WARNING) << "Failed to QI for IWinInetHttpInfo"; 583 } 584 } 585 586 scoped_refptr<ProtData> ProtData::DataFromProtocol( 587 IInternetProtocol* protocol) { 588 scoped_refptr<ProtData> instance; 589 base::AutoLock lock(datamap_lock_); 590 ProtocolDataMap::iterator it = datamap_.find(protocol); 591 if (datamap_.end() != it) 592 instance = it->second; 593 return instance; 594 } 595 596 void ProtData::Invalidate() { 597 if (protocol_) { 598 // Remove from map. 599 base::AutoLock lock(datamap_lock_); 600 DCHECK(datamap_.end() != datamap_.find(protocol_)); 601 datamap_.erase(protocol_); 602 protocol_ = NULL; 603 } 604 } 605 606 // This function looks for the url pattern indicating that this request needs 607 // to be forced into chrome frame. 608 // This hack is required because window.open requests from Chrome don't have 609 // the URL up front. The URL comes in much later when the renderer initiates a 610 // top level navigation for the url passed into window.open. 611 // The new page must be rendered in ChromeFrame to preserve the opener 612 // relationship with its parent even if the new page does not have the chrome 613 // meta tag. 614 bool HandleAttachToExistingExternalTab(LPCWSTR url, 615 IInternetProtocol* protocol, 616 IInternetProtocolSink* prot_sink, 617 IBindCtx* bind_ctx) { 618 ChromeFrameUrl cf_url; 619 if (cf_url.Parse(url) && cf_url.attach_to_external_tab()) { 620 scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol); 621 if (!prot_data) { 622 // Pass NULL as the read function which indicates that always return EOF 623 // without calling the underlying protocol. 624 prot_data = new ProtData(protocol, NULL, url); 625 PutProtData(bind_ctx, prot_data); 626 } 627 628 prot_sink->ReportProgress(BINDSTATUS_MIMETYPEAVAILABLE, kChromeMimeType); 629 630 int data_flags = BSCF_FIRSTDATANOTIFICATION | BSCF_LASTDATANOTIFICATION; 631 prot_sink->ReportData(data_flags, 0, 0); 632 633 prot_sink->ReportResult(S_OK, 0, NULL); 634 return true; 635 } 636 return false; 637 } 638 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); 644 } 645 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); 651 } 652 653 // IInternetProtocol/Ex hooks. 654 STDMETHODIMP Hook_Start(InternetProtocol_Start_Fn orig_start, 655 IInternetProtocol* protocol, LPCWSTR url, IInternetProtocolSink* prot_sink, 656 IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) { 657 DCHECK(orig_start); 658 if (!url || !prot_sink || !bind_info) 659 return E_INVALIDARG; 660 DVLOG_IF(1, url != NULL) << "OnStart: " << url << PiFlags2Str(flags); 661 662 base::win::ScopedComPtr<IBindCtx> bind_ctx = BindCtxFromIBindInfo(bind_info); 663 if (!bind_ctx) { 664 // MSHTML sometimes takes a short path, skips the creation of 665 // moniker and binding, by directly grabbing protocol from InternetSession 666 DVLOG(1) << "DirectBind for " << url; 667 return ForwardHookStart(orig_start, protocol, url, prot_sink, bind_info, 668 flags, reserved); 669 } 670 671 scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol); 672 if (prot_data && !HasProtData(bind_ctx)) { 673 prot_data->Invalidate(); 674 prot_data = NULL; 675 } 676 677 if (HandleAttachToExistingExternalTab(url, protocol, prot_sink, bind_ctx)) { 678 return S_OK; 679 } 680 681 if (IsCFRequest(bind_ctx)) { 682 base::win::ScopedComPtr<BindContextInfo> info; 683 BindContextInfo::FromBindContext(bind_ctx, info.Receive()); 684 DCHECK(info); 685 if (info) { 686 info->set_protocol(protocol); 687 } 688 return ForwardHookStart(orig_start, protocol, url, prot_sink, bind_info, 689 flags, reserved); 690 } 691 692 if (prot_data) { 693 DVLOG(1) << "Found existing ProtData!"; 694 prot_data->UpdateUrl(url); 695 base::win::ScopedComPtr<IInternetProtocolSink> new_sink = 696 ProtocolSinkWrap::CreateNewSink(prot_sink, prot_data); 697 return ForwardWrappedHookStart(orig_start, protocol, url, new_sink, 698 bind_info, flags, reserved); 699 } 700 701 if (!ShouldWrapSink(prot_sink, url)) { 702 return ForwardHookStart(orig_start, protocol, url, prot_sink, bind_info, 703 flags, reserved); 704 } 705 706 // Fresh request. 707 InternetProtocol_Read_Fn read_fun = reinterpret_cast<InternetProtocol_Read_Fn> 708 (CTransaction_PatchInfo[1].stub_->argument()); 709 prot_data = new ProtData(protocol, read_fun, url); 710 PutProtData(bind_ctx, prot_data); 711 712 base::win::ScopedComPtr<IInternetProtocolSink> new_sink = 713 ProtocolSinkWrap::CreateNewSink(prot_sink, prot_data); 714 return ForwardWrappedHookStart(orig_start, protocol, url, new_sink, bind_info, 715 flags, reserved); 716 } 717 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); 723 } 724 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); 730 } 731 732 STDMETHODIMP Hook_StartEx(InternetProtocol_StartEx_Fn orig_start_ex, 733 IInternetProtocolEx* protocol, IUri* uri, IInternetProtocolSink* prot_sink, 734 IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) { 735 DCHECK(orig_start_ex); 736 if (!uri || !prot_sink || !bind_info) 737 return E_INVALIDARG; 738 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); 742 743 base::win::ScopedComPtr<IBindCtx> bind_ctx = BindCtxFromIBindInfo(bind_info); 744 if (!bind_ctx) { 745 // MSHTML sometimes takes a short path, skips the creation of 746 // moniker and binding, by directly grabbing protocol from InternetSession. 747 DVLOG(1) << "DirectBind for " << url; 748 return ForwardHookStartEx(orig_start_ex, protocol, uri, prot_sink, 749 bind_info, flags, reserved); 750 } 751 752 scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol); 753 if (prot_data && !HasProtData(bind_ctx)) { 754 prot_data->Invalidate(); 755 prot_data = NULL; 756 } 757 758 if (HandleAttachToExistingExternalTab(url, protocol, prot_sink, bind_ctx)) { 759 return S_OK; 760 } 761 762 if (IsCFRequest(bind_ctx)) { 763 base::win::ScopedComPtr<BindContextInfo> info; 764 BindContextInfo::FromBindContext(bind_ctx, info.Receive()); 765 DCHECK(info); 766 if (info) { 767 info->set_protocol(protocol); 768 } 769 return ForwardHookStartEx(orig_start_ex, protocol, uri, prot_sink, 770 bind_info, flags, reserved); 771 } 772 773 if (prot_data) { 774 DVLOG(1) << "Found existing ProtData!"; 775 prot_data->UpdateUrl(url); 776 base::win::ScopedComPtr<IInternetProtocolSink> new_sink = 777 ProtocolSinkWrap::CreateNewSink(prot_sink, prot_data); 778 return ForwardWrappedHookStartEx(orig_start_ex, protocol, uri, new_sink, 779 bind_info, flags, reserved); 780 } 781 782 if (!ShouldWrapSink(prot_sink, url)) { 783 return ForwardHookStartEx(orig_start_ex, protocol, uri, prot_sink, 784 bind_info, flags, reserved); 785 } 786 787 // Fresh request. 788 InternetProtocol_Read_Fn read_fun = reinterpret_cast<InternetProtocol_Read_Fn> 789 (CTransaction_PatchInfo[1].stub_->argument()); 790 prot_data = new ProtData(protocol, read_fun, url); 791 PutProtData(bind_ctx, prot_data); 792 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); 797 } 798 799 STDMETHODIMP Hook_Read(InternetProtocol_Read_Fn orig_read, 800 IInternetProtocol* protocol, void* buffer, ULONG size, ULONG* size_read) { 801 DCHECK(orig_read); 802 HRESULT hr = E_FAIL; 803 804 scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol); 805 if (!prot_data) { 806 // We are not wrapping this request, avoid false positive crash reports. 807 ExceptionBarrierReportOnlyModule barrier; 808 hr = orig_read(protocol, buffer, size, size_read); 809 return hr; 810 } 811 812 if (prot_data->is_attach_external_tab_request()) { 813 // return EOF always. 814 if (size_read) 815 *size_read = 0; 816 return S_FALSE; 817 } 818 819 hr = prot_data->Read(buffer, size, size_read); 820 return hr; 821 } 822 823 STDMETHODIMP Hook_LockRequest(InternetProtocol_LockRequest_Fn orig_req, 824 IInternetProtocol* protocol, 825 DWORD options) { 826 DCHECK(orig_req); 827 828 scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol); 829 if (prot_data && prot_data->is_attach_external_tab_request()) { 830 prot_data->AddRef(); 831 return S_OK; 832 } 833 834 // We are just pass through at this point, avoid false positive crash 835 // reports. 836 ExceptionBarrierReportOnlyModule barrier; 837 return orig_req(protocol, options); 838 } 839 840 STDMETHODIMP Hook_UnlockRequest(InternetProtocol_UnlockRequest_Fn orig_req, 841 IInternetProtocol* protocol) { 842 DCHECK(orig_req); 843 844 scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol); 845 if (prot_data && prot_data->is_attach_external_tab_request()) { 846 prot_data->Release(); 847 return S_OK; 848 } 849 850 // We are just pass through at this point, avoid false positive crash 851 // reports. 852 ExceptionBarrierReportOnlyModule barrier; 853 return orig_req(protocol); 854 } 855 856 STDMETHODIMP Hook_Abort(InternetProtocol_Abort_Fn orig_req, 857 IInternetProtocol* protocol, 858 HRESULT hr, 859 DWORD options) { 860 scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol); 861 if (prot_data) 862 prot_data->Invalidate(); 863 864 // We are just pass through at this point, avoid false positive crash 865 // reports. 866 ExceptionBarrierReportOnlyModule barrier; 867 return orig_req(protocol, hr, options); 868 } 869 870 STDMETHODIMP Hook_Terminate(InternetProtocol_Terminate_Fn orig_req, 871 IInternetProtocol* protocol, 872 DWORD options) { 873 scoped_refptr<ProtData> prot_data = ProtData::DataFromProtocol(protocol); 874 // We should not be invalidating the cached protocol data in the following 875 // cases:- 876 // 1. Pages which are switching into ChromeFrame. 877 // When IE switches into ChromeFrame, we report the Chrome mime type as 878 // the handler for the page. This results in urlmon terminating the 879 // protocol. When Chrome attempts to read the data we need to report the 880 // cached data back to Chrome. 881 // 2. For the attach external tab requests which are temporary navigations 882 // to ensure that a top level URL is opened in IE from ChromeFrame. 883 // We rely on the mapping to identify these requests as attach tab 884 // requests. This mapping is referred to in the 885 // IInternetProtocol::LockRequest/IInternetProtocol::UnlockRequest 886 // intercepts. Invalidating the mapping after LockRequest is called and 887 // before UnlockRequest causes IE to crash. 888 if (prot_data && !IsChrome(prot_data->renderer_type()) && 889 !prot_data->is_attach_external_tab_request()) 890 prot_data->Invalidate(); 891 892 // We are just pass through at this point, avoid false positive crash 893 // reports. 894 ExceptionBarrierReportOnlyModule barrier; 895 return orig_req(protocol, options); 896 } 897 898 // Patching / Hooking code. 899 class FakeProtocol : public CComObjectRootEx<CComSingleThreadModel>, 900 public IInternetProtocol { 901 public: 902 BEGIN_COM_MAP(FakeProtocol) 903 COM_INTERFACE_ENTRY(IInternetProtocol) 904 COM_INTERFACE_ENTRY(IInternetProtocolRoot) 905 END_COM_MAP() 906 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; 912 } 913 914 STDMETHOD(Continue)(PROTOCOLDATA* protocol_data) { return S_OK; } 915 STDMETHOD(Abort)(HRESULT reason, DWORD options) { return S_OK; } 916 STDMETHOD(Terminate)(DWORD options) { return S_OK; } 917 STDMETHOD(Suspend)() { return S_OK; } 918 STDMETHOD(Resume)() { return S_OK; } 919 STDMETHOD(Read)(void *buffer, ULONG size, ULONG* size_read) { return S_OK; } 920 STDMETHOD(Seek)(LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER* new_pos) 921 { return S_OK; } 922 STDMETHOD(LockRequest)(DWORD options) { return S_OK; } 923 STDMETHOD(UnlockRequest)() { return S_OK; } 924 925 base::win::ScopedComPtr<IInternetProtocol> transaction_; 926 }; 927 928 struct FakeFactory : public IClassFactory, 929 public CComObjectRootEx<CComSingleThreadModel> { 930 BEGIN_COM_MAP(FakeFactory) 931 COM_INTERFACE_ENTRY(IClassFactory) 932 END_COM_MAP() 933 934 STDMETHOD(CreateInstance)(IUnknown *pUnkOuter, REFIID riid, void **ppvObj) { 935 if (pUnkOuter) 936 return CLASS_E_NOAGGREGATION; 937 HRESULT hr = obj_->QueryInterface(riid, ppvObj); 938 return hr; 939 } 940 941 STDMETHOD(LockServer)(BOOL fLock) { 942 return S_OK; 943 } 944 945 IUnknown* obj_; 946 }; 947 948 static void HookTransactionVtable(IInternetProtocol* p) { 949 base::win::ScopedComPtr<IInternetProtocolEx> ex; 950 ex.QueryFrom(p); 951 952 HRESULT hr = vtable_patch::PatchInterfaceMethods(p, CTransaction_PatchInfo); 953 if (hr == S_OK && ex) { 954 vtable_patch::PatchInterfaceMethods(ex.get(), CTransaction2_PatchInfo); 955 } 956 } 957 958 void TransactionHooks::InstallHooks() { 959 if (IS_PATCHED(CTransaction)) { 960 DLOG(WARNING) << __FUNCTION__ << " called more than once."; 961 return; 962 } 963 964 CComObjectStackEx<FakeProtocol> prot; 965 CComObjectStackEx<FakeFactory> factory; 966 factory.obj_ = &prot; 967 base::win::ScopedComPtr<IInternetSession> session; 968 HRESULT hr = ::CoInternetGetSession(0, session.Receive(), 0); 969 hr = session->RegisterNameSpace(&factory, CLSID_NULL, L"611", 0, 0, 0); 970 DLOG_IF(FATAL, FAILED(hr)) << "Failed to register namespace"; 971 if (hr != S_OK) 972 return; 973 974 do { 975 base::win::ScopedComPtr<IMoniker> mk; 976 base::win::ScopedComPtr<IBindCtx> bc; 977 base::win::ScopedComPtr<IStream> stream; 978 hr = ::CreateAsyncBindCtxEx(0, 0, 0, 0, bc.Receive(), 0); 979 DLOG_IF(FATAL, FAILED(hr)) << "CreateAsyncBindCtxEx failed " << hr; 980 if (hr != S_OK) 981 break; 982 983 hr = ::CreateURLMoniker(NULL, L"611://512", mk.Receive()); 984 DLOG_IF(FATAL, FAILED(hr)) << "CreateURLMoniker failed " << hr; 985 if (hr != S_OK) 986 break; 987 988 hr = mk->BindToStorage(bc, NULL, IID_IStream, 989 reinterpret_cast<void**>(stream.Receive())); 990 DLOG_IF(FATAL, hr != INET_E_INVALID_CERTIFICATE) << 991 "BindToStorage failed " << hr; 992 } while (0); 993 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(); 1000 } 1001 } 1002 1003 void TransactionHooks::RevertHooks() { 1004 vtable_patch::UnpatchInterfaceMethods(CTransaction_PatchInfo); 1005 vtable_patch::UnpatchInterfaceMethods(CTransaction2_PatchInfo); 1006 } 1007