1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome_frame/http_negotiate.h"
11 #include "base/logging.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/string_util.h"
14 #include "base/stringprintf.h"
15 #include "base/utf_string_conversions.h"
16 #include "chrome_frame/bho.h"
17 #include "chrome_frame/exception_barrier.h"
18 #include "chrome_frame/html_utils.h"
19 #include "chrome_frame/urlmon_moniker.h"
20 #include "chrome_frame/urlmon_url_request.h"
21 #include "chrome_frame/utils.h"
22 #include "chrome_frame/vtable_patch_manager.h"
23 #include "net/http/http_response_headers.h"
24 #include "net/http/http_util.h"
26 bool HttpNegotiatePatch::modify_user_agent_
= true;
27 const char kUACompatibleHttpHeader
[] = "x-ua-compatible";
28 const char kLowerCaseUserAgent
[] = "user-agent";
30 // From the latest urlmon.h. Symbol name prepended with LOCAL_ to
31 // avoid conflict (and therefore build errors) for those building with
32 // a newer Windows SDK.
33 // TODO(robertshield): Remove this once we update our SDK version.
34 const int LOCAL_BINDSTATUS_SERVER_MIMETYPEAVAILABLE
= 54;
36 static const int kHttpNegotiateBeginningTransactionIndex
= 3;
38 BEGIN_VTABLE_PATCHES(IHttpNegotiate
)
39 VTABLE_PATCH_ENTRY(kHttpNegotiateBeginningTransactionIndex
,
40 HttpNegotiatePatch::BeginningTransaction
)
45 class SimpleBindStatusCallback
: public CComObjectRootEx
<CComSingleThreadModel
>,
46 public IBindStatusCallback
{
48 BEGIN_COM_MAP(SimpleBindStatusCallback
)
49 COM_INTERFACE_ENTRY(IBindStatusCallback
)
52 // IBindStatusCallback implementation
53 STDMETHOD(OnStartBinding
)(DWORD reserved
, IBinding
* binding
) {
57 STDMETHOD(GetPriority
)(LONG
* priority
) {
60 STDMETHOD(OnLowResource
)(DWORD reserved
) {
64 STDMETHOD(OnProgress
)(ULONG progress
, ULONG max_progress
,
65 ULONG status_code
, LPCWSTR status_text
) {
68 STDMETHOD(OnStopBinding
)(HRESULT result
, LPCWSTR error
) {
72 STDMETHOD(GetBindInfo
)(DWORD
* bind_flags
, BINDINFO
* bind_info
) {
76 STDMETHOD(OnDataAvailable
)(DWORD flags
, DWORD size
, FORMATETC
* formatetc
,
80 STDMETHOD(OnObjectAvailable
)(REFIID iid
, IUnknown
* object
) {
86 std::string
AppendCFUserAgentString(LPCWSTR headers
,
87 LPCWSTR additional_headers
) {
90 std::string ascii_headers
;
91 if (additional_headers
) {
92 ascii_headers
= WideToASCII(additional_headers
);
95 // Extract "User-Agent" from |additional_headers| or |headers|.
96 HttpUtil::HeadersIterator
headers_iterator(ascii_headers
.begin(),
97 ascii_headers
.end(), "\r\n");
98 std::string user_agent_value
;
99 if (headers_iterator
.AdvanceTo(kLowerCaseUserAgent
)) {
100 user_agent_value
= headers_iterator
.values();
101 } else if (headers
!= NULL
) {
102 // See if there's a user-agent header specified in the original headers.
103 std::string
original_headers(WideToASCII(headers
));
104 HttpUtil::HeadersIterator
original_it(original_headers
.begin(),
105 original_headers
.end(), "\r\n");
106 if (original_it
.AdvanceTo(kLowerCaseUserAgent
))
107 user_agent_value
= original_it
.values();
110 // Use the default "User-Agent" if none was provided.
111 if (user_agent_value
.empty())
112 user_agent_value
= http_utils::GetDefaultUserAgent();
114 // Now add chromeframe to it.
115 user_agent_value
= http_utils::AddChromeFrameToUserAgentValue(
118 // Build new headers, skip the existing user agent value from
120 std::string new_headers
;
121 headers_iterator
.Reset();
122 while (headers_iterator
.GetNext()) {
123 std::string
name(headers_iterator
.name());
124 if (!LowerCaseEqualsASCII(name
, kLowerCaseUserAgent
)) {
125 new_headers
+= name
+ ": " + headers_iterator
.values() + "\r\n";
129 new_headers
+= "User-Agent: " + user_agent_value
;
130 new_headers
+= "\r\n";
134 std::string
ReplaceOrAddUserAgent(LPCWSTR headers
,
135 const std::string
& user_agent_value
) {
138 std::string new_headers
;
140 std::string
ascii_headers(WideToASCII(headers
));
142 // Extract "User-Agent" from the headers.
143 HttpUtil::HeadersIterator
headers_iterator(ascii_headers
.begin(),
144 ascii_headers
.end(), "\r\n");
146 // Build new headers, skip the existing user agent value from
148 while (headers_iterator
.GetNext()) {
149 std::string
name(headers_iterator
.name());
150 if (!LowerCaseEqualsASCII(name
, kLowerCaseUserAgent
)) {
151 new_headers
+= name
+ ": " + headers_iterator
.values() + "\r\n";
155 new_headers
+= "User-Agent: " + user_agent_value
;
156 new_headers
+= "\r\n";
160 HttpNegotiatePatch::HttpNegotiatePatch() {
163 HttpNegotiatePatch::~HttpNegotiatePatch() {
167 bool HttpNegotiatePatch::Initialize() {
168 if (IS_PATCHED(IHttpNegotiate
)) {
169 DLOG(WARNING
) << __FUNCTION__
<< " called more than once.";
172 // Use our SimpleBindStatusCallback class as we need a temporary object that
173 // implements IBindStatusCallback.
174 CComObjectStackEx
<SimpleBindStatusCallback
> request
;
175 base::win::ScopedComPtr
<IBindCtx
> bind_ctx
;
176 HRESULT hr
= CreateAsyncBindCtx(0, &request
, NULL
, bind_ctx
.Receive());
177 DCHECK(SUCCEEDED(hr
)) << "CreateAsyncBindCtx";
179 base::win::ScopedComPtr
<IUnknown
> bscb_holder
;
180 bind_ctx
->GetObjectParam(L
"_BSCB_Holder_", bscb_holder
.Receive());
182 hr
= PatchHttpNegotiate(bscb_holder
);
184 NOTREACHED() << "Failed to get _BSCB_Holder_";
190 return SUCCEEDED(hr
);
194 void HttpNegotiatePatch::Uninitialize() {
195 vtable_patch::UnpatchInterfaceMethods(IHttpNegotiate_PatchInfo
);
199 HRESULT
HttpNegotiatePatch::PatchHttpNegotiate(IUnknown
* to_patch
) {
201 DCHECK_IS_NOT_PATCHED(IHttpNegotiate
);
203 base::win::ScopedComPtr
<IHttpNegotiate
> http
;
204 HRESULT hr
= http
.QueryFrom(to_patch
);
206 hr
= DoQueryService(IID_IHttpNegotiate
, to_patch
, http
.Receive());
210 hr
= vtable_patch::PatchInterfaceMethods(http
, IHttpNegotiate_PatchInfo
);
211 DLOG_IF(ERROR
, FAILED(hr
))
212 << base::StringPrintf("HttpNegotiate patch failed 0x%08X", hr
);
215 << base::StringPrintf("IHttpNegotiate not supported 0x%08X", hr
);
221 HRESULT
HttpNegotiatePatch::BeginningTransaction(
222 IHttpNegotiate_BeginningTransaction_Fn original
, IHttpNegotiate
* me
,
223 LPCWSTR url
, LPCWSTR headers
, DWORD reserved
, LPWSTR
* additional_headers
) {
224 DVLOG(1) << __FUNCTION__
<< " " << url
<< " headers:\n" << headers
;
226 HRESULT hr
= original(me
, url
, headers
, reserved
, additional_headers
);
229 DLOG(WARNING
) << __FUNCTION__
<< " Delegate returned an error";
232 if (modify_user_agent_
) {
233 std::string updated_headers
;
234 if (IsGcfDefaultRenderer() &&
235 RendererTypeForUrl(url
) == RENDERER_TYPE_CHROME_DEFAULT_RENDERER
) {
236 // Replace the user-agent header with Chrome's.
237 updated_headers
= ReplaceOrAddUserAgent(*additional_headers
,
238 http_utils::GetChromeUserAgent());
240 updated_headers
= AppendCFUserAgentString(headers
, *additional_headers
);
242 *additional_headers
= reinterpret_cast<wchar_t*>(::CoTaskMemRealloc(
244 (updated_headers
.length() + 1) * sizeof(wchar_t)));
245 lstrcpyW(*additional_headers
, ASCIIToWide(updated_headers
).c_str());
247 // TODO(erikwright): Remove the user agent if it is present (i.e., because
248 // of PostPlatform setting in the registry).