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