OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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 "chrome_frame/urlmon_url_request.h" | |
6 | |
7 #include <urlmon.h> | |
8 #include <wininet.h> | |
9 | |
10 #include "base/bind.h" | |
11 #include "base/bind_helpers.h" | |
12 #include "base/logging.h" | |
13 #include "base/memory/scoped_ptr.h" | |
14 #include "base/message_loop/message_loop.h" | |
15 #include "base/strings/string_number_conversions.h" | |
16 #include "base/strings/stringprintf.h" | |
17 #include "base/strings/utf_string_conversions.h" | |
18 #include "base/threading/platform_thread.h" | |
19 #include "base/threading/thread.h" | |
20 #include "chrome/common/automation_messages.h" | |
21 #include "chrome_frame/bind_context_info.h" | |
22 #include "chrome_frame/chrome_frame_activex_base.h" | |
23 #include "chrome_frame/extra_system_apis.h" | |
24 #include "chrome_frame/html_utils.h" | |
25 #include "chrome_frame/urlmon_upload_data_stream.h" | |
26 #include "chrome_frame/urlmon_url_request_private.h" | |
27 #include "chrome_frame/utils.h" | |
28 #include "net/base/load_flags.h" | |
29 #include "net/http/http_response_headers.h" | |
30 #include "net/http/http_util.h" | |
31 | |
32 #define IS_HTTP_SUCCESS_CODE(code) (code >= 200 && code <= 299) | |
33 | |
34 UrlmonUrlRequest::UrlmonUrlRequest() | |
35 : pending_read_size_(0), | |
36 headers_received_(false), | |
37 calling_delegate_(0), | |
38 thread_(NULL), | |
39 parent_window_(NULL), | |
40 privileged_mode_(false), | |
41 pending_(false), | |
42 is_expecting_download_(true), | |
43 cleanup_transaction_(false) { | |
44 DVLOG(1) << __FUNCTION__ << me(); | |
45 } | |
46 | |
47 UrlmonUrlRequest::~UrlmonUrlRequest() { | |
48 DVLOG(1) << __FUNCTION__ << me(); | |
49 } | |
50 | |
51 std::string UrlmonUrlRequest::me() const { | |
52 return base::StringPrintf(" id: %i Obj: %X ", id(), this); | |
53 } | |
54 | |
55 bool UrlmonUrlRequest::Start() { | |
56 DVLOG(1) << __FUNCTION__ << me() << url(); | |
57 DCHECK(thread_ == 0 || thread_ == base::PlatformThread::CurrentId()); | |
58 thread_ = base::PlatformThread::CurrentId(); | |
59 status_.Start(); | |
60 // Initialize the net::HostPortPair structure from the url initially. We may | |
61 // not receive the ip address of the host if the request is satisfied from | |
62 // the cache. | |
63 socket_address_ = net::HostPortPair::FromURL(GURL(url())); | |
64 // The UrlmonUrlRequest instance can get destroyed in the context of | |
65 // StartAsyncDownload if BindToStorage finishes synchronously with an error. | |
66 // Grab a reference to protect against this. | |
67 scoped_refptr<UrlmonUrlRequest> ref(this); | |
68 HRESULT hr = StartAsyncDownload(); | |
69 if (FAILED(hr) && status_.get_state() != UrlmonUrlRequest::Status::DONE) { | |
70 status_.Done(); | |
71 status_.set_result(net::URLRequestStatus::FAILED, HresultToNetError(hr)); | |
72 NotifyDelegateAndDie(); | |
73 } | |
74 return true; | |
75 } | |
76 | |
77 void UrlmonUrlRequest::Stop() { | |
78 DCHECK_EQ(thread_, base::PlatformThread::CurrentId()); | |
79 DCHECK((status_.get_state() != Status::DONE) == (binding_ != NULL)); | |
80 Status::State state = status_.get_state(); | |
81 delegate_ = NULL; | |
82 | |
83 // If DownloadInHost is already requested, we will quit soon anyway. | |
84 if (terminate_requested()) | |
85 return; | |
86 | |
87 switch (state) { | |
88 case Status::WORKING: | |
89 status_.Cancel(); | |
90 if (binding_) | |
91 binding_->Abort(); | |
92 break; | |
93 | |
94 case Status::ABORTING: | |
95 status_.Cancel(); | |
96 break; | |
97 | |
98 case Status::DONE: | |
99 status_.Cancel(); | |
100 NotifyDelegateAndDie(); | |
101 break; | |
102 } | |
103 } | |
104 | |
105 bool UrlmonUrlRequest::Read(int bytes_to_read) { | |
106 DCHECK_EQ(thread_, base::PlatformThread::CurrentId()); | |
107 DCHECK_GE(bytes_to_read, 0); | |
108 DCHECK_EQ(0, calling_delegate_); | |
109 DVLOG(1) << __FUNCTION__ << me(); | |
110 | |
111 is_expecting_download_ = false; | |
112 | |
113 // Re-entrancy check. Thou shall not call Read() while process OnReadComplete! | |
114 DCHECK_EQ(0u, pending_read_size_); | |
115 if (pending_read_size_ != 0) | |
116 return false; | |
117 | |
118 DCHECK((status_.get_state() != Status::DONE) == (binding_ != NULL)); | |
119 if (status_.get_state() == Status::ABORTING) | |
120 return true; | |
121 | |
122 // Send data if available. | |
123 size_t bytes_copied = 0; | |
124 if ((bytes_copied = SendDataToDelegate(bytes_to_read))) { | |
125 DVLOG(1) << __FUNCTION__ << me() << " bytes read: " << bytes_copied; | |
126 return true; | |
127 } | |
128 | |
129 if (status_.get_state() == Status::WORKING) { | |
130 DVLOG(1) << __FUNCTION__ << me() << " pending: " << bytes_to_read; | |
131 pending_read_size_ = bytes_to_read; | |
132 } else { | |
133 DVLOG(1) << __FUNCTION__ << me() << " Response finished."; | |
134 NotifyDelegateAndDie(); | |
135 } | |
136 | |
137 return true; | |
138 } | |
139 | |
140 HRESULT UrlmonUrlRequest::InitPending(const GURL& url, IMoniker* moniker, | |
141 IBindCtx* bind_context, | |
142 bool enable_frame_busting, | |
143 bool privileged_mode, | |
144 HWND notification_window, | |
145 IStream* cache) { | |
146 DVLOG(1) << __FUNCTION__ << me() << url.spec(); | |
147 DCHECK(bind_context_ == NULL); | |
148 DCHECK(moniker_ == NULL); | |
149 DCHECK(cache_ == NULL); | |
150 DCHECK(thread_ == 0 || thread_ == base::PlatformThread::CurrentId()); | |
151 thread_ = base::PlatformThread::CurrentId(); | |
152 bind_context_ = bind_context; | |
153 moniker_ = moniker; | |
154 enable_frame_busting_ = enable_frame_busting; | |
155 privileged_mode_ = privileged_mode; | |
156 parent_window_ = notification_window; | |
157 cache_ = cache; | |
158 set_url(url.spec()); | |
159 set_pending(true); | |
160 | |
161 // Request has already started and data is fetched. We will get the | |
162 // GetBindInfo call as per contract but the return values are | |
163 // ignored. So just set "get" as a method to make our GetBindInfo | |
164 // implementation happy. | |
165 method_ = "get"; | |
166 return S_OK; | |
167 } | |
168 | |
169 void UrlmonUrlRequest::TerminateBind(const TerminateBindCallback& callback) { | |
170 DCHECK_EQ(thread_, base::PlatformThread::CurrentId()); | |
171 DVLOG(1) << __FUNCTION__ << me(); | |
172 cleanup_transaction_ = false; | |
173 if (status_.get_state() == Status::DONE) { | |
174 // Binding is stopped. Note result could be an error. | |
175 callback.Run(moniker_, bind_context_, upload_data_, | |
176 request_headers_.c_str()); | |
177 } else { | |
178 // WORKING (ABORTING?). Save the callback. | |
179 // Now we will return INET_TERMINATE_BIND from ::OnDataAvailable() and in | |
180 // ::OnStopBinding will invoke the callback passing our moniker and | |
181 // bind context. | |
182 terminate_bind_callback_ = callback; | |
183 if (pending_data_) { | |
184 // For downloads to work correctly, we must induce a call to | |
185 // OnDataAvailable so that we can download INET_E_TERMINATED_BIND and | |
186 // get IE into the correct state. | |
187 // To accomplish this we read everything that's readily available in | |
188 // the current stream. Once we've reached the end of the stream we | |
189 // should get E_PENDING back and then later we'll get that call | |
190 // to OnDataAvailable. | |
191 std::string data; | |
192 base::win::ScopedComPtr<IStream> read_stream(pending_data_); | |
193 HRESULT hr; | |
194 while ((hr = ReadStream(read_stream, 0xffff, &data)) == S_OK) { | |
195 // Just drop the data. | |
196 } | |
197 DLOG_IF(WARNING, hr != E_PENDING) << __FUNCTION__ << | |
198 base::StringPrintf(" expected E_PENDING but got 0x%08X", hr); | |
199 } | |
200 } | |
201 } | |
202 | |
203 size_t UrlmonUrlRequest::SendDataToDelegate(size_t bytes_to_read) { | |
204 return 0; | |
205 } | |
206 | |
207 STDMETHODIMP UrlmonUrlRequest::OnStartBinding(DWORD reserved, | |
208 IBinding* binding) { | |
209 DCHECK_EQ(thread_, base::PlatformThread::CurrentId()); | |
210 binding_ = binding; | |
211 if (pending_) { | |
212 response_headers_ = GetHttpHeadersFromBinding(binding_); | |
213 DCHECK(!response_headers_.empty()); | |
214 } | |
215 return S_OK; | |
216 } | |
217 | |
218 STDMETHODIMP UrlmonUrlRequest::GetPriority(LONG *priority) { | |
219 if (!priority) | |
220 return E_POINTER; | |
221 *priority = THREAD_PRIORITY_NORMAL; | |
222 return S_OK; | |
223 } | |
224 | |
225 STDMETHODIMP UrlmonUrlRequest::OnLowResource(DWORD reserved) { | |
226 return S_OK; | |
227 } | |
228 | |
229 STDMETHODIMP UrlmonUrlRequest::OnProgress(ULONG progress, ULONG max_progress, | |
230 ULONG status_code, LPCWSTR status_text) { | |
231 DCHECK_EQ(thread_, base::PlatformThread::CurrentId()); | |
232 | |
233 if (status_.get_state() != Status::WORKING) | |
234 return S_OK; | |
235 | |
236 // Ignore any notifications received while we are in the pending state | |
237 // waiting for the request to be initiated by Chrome. | |
238 if (pending_ && status_code != BINDSTATUS_REDIRECTING) | |
239 return S_OK; | |
240 | |
241 if (!delegate_) { | |
242 DVLOG(1) << "Invalid delegate"; | |
243 return S_OK; | |
244 } | |
245 | |
246 switch (status_code) { | |
247 case BINDSTATUS_CONNECTING: { | |
248 if (status_text) { | |
249 socket_address_.set_host(base::WideToUTF8(status_text)); | |
250 } | |
251 break; | |
252 } | |
253 | |
254 case BINDSTATUS_REDIRECTING: { | |
255 // If we receive a redirect for the initial pending request initiated | |
256 // when our document loads we should stash it away and inform Chrome | |
257 // accordingly when it requests data for the original URL. | |
258 base::win::ScopedComPtr<BindContextInfo> info; | |
259 BindContextInfo::FromBindContext(bind_context_, info.Receive()); | |
260 DCHECK(info); | |
261 GURL previously_redirected(info ? info->GetUrl() : std::wstring()); | |
262 if (GURL(status_text) != previously_redirected) { | |
263 DVLOG(1) << __FUNCTION__ << me() << "redirect from " << url() | |
264 << " to " << status_text; | |
265 // Fetch the redirect status as they aren't all equal (307 in particular | |
266 // retains the HTTP request verb). | |
267 int http_code = GetHttpResponseStatusFromBinding(binding_); | |
268 status_.SetRedirected(http_code, base::WideToUTF8(status_text)); | |
269 // Abort. We will inform Chrome in OnStopBinding callback. | |
270 binding_->Abort(); | |
271 return E_ABORT; | |
272 } | |
273 break; | |
274 } | |
275 | |
276 case BINDSTATUS_COOKIE_SENT: | |
277 delegate_->AddPrivacyDataForUrl(url(), "", COOKIEACTION_READ); | |
278 break; | |
279 | |
280 case BINDSTATUS_COOKIE_SUPPRESSED: | |
281 delegate_->AddPrivacyDataForUrl(url(), "", COOKIEACTION_SUPPRESS); | |
282 break; | |
283 | |
284 case BINDSTATUS_COOKIE_STATE_ACCEPT: | |
285 delegate_->AddPrivacyDataForUrl(url(), "", COOKIEACTION_ACCEPT); | |
286 break; | |
287 | |
288 case BINDSTATUS_COOKIE_STATE_REJECT: | |
289 delegate_->AddPrivacyDataForUrl(url(), "", COOKIEACTION_REJECT); | |
290 break; | |
291 | |
292 case BINDSTATUS_COOKIE_STATE_LEASH: | |
293 delegate_->AddPrivacyDataForUrl(url(), "", COOKIEACTION_LEASH); | |
294 break; | |
295 | |
296 case BINDSTATUS_COOKIE_STATE_DOWNGRADE: | |
297 delegate_->AddPrivacyDataForUrl(url(), "", COOKIEACTION_DOWNGRADE); | |
298 break; | |
299 | |
300 case BINDSTATUS_COOKIE_STATE_UNKNOWN: | |
301 NOTREACHED() << L"Unknown cookie state received"; | |
302 break; | |
303 | |
304 default: | |
305 DVLOG(1) << __FUNCTION__ << me() | |
306 << base::StringPrintf(L"code: %i status: %ls", status_code, | |
307 status_text); | |
308 break; | |
309 } | |
310 | |
311 return S_OK; | |
312 } | |
313 | |
314 STDMETHODIMP UrlmonUrlRequest::OnStopBinding(HRESULT result, LPCWSTR error) { | |
315 DCHECK_EQ(thread_, base::PlatformThread::CurrentId()); | |
316 DVLOG(1) << __FUNCTION__ << me() | |
317 << "- Request stopped, Result: " << std::hex << result; | |
318 DCHECK(status_.get_state() == Status::WORKING || | |
319 status_.get_state() == Status::ABORTING); | |
320 | |
321 Status::State state = status_.get_state(); | |
322 | |
323 // Mark we a are done. | |
324 status_.Done(); | |
325 | |
326 if (result == INET_E_TERMINATED_BIND) { | |
327 if (terminate_requested()) { | |
328 terminate_bind_callback_.Run(moniker_, bind_context_, upload_data_, | |
329 request_headers_.c_str()); | |
330 } else { | |
331 cleanup_transaction_ = true; | |
332 } | |
333 // We may have returned INET_E_TERMINATED_BIND from OnDataAvailable. | |
334 result = S_OK; | |
335 } | |
336 | |
337 if (state == Status::WORKING) { | |
338 status_.set_result(result); | |
339 | |
340 if (FAILED(result)) { | |
341 int http_code = GetHttpResponseStatusFromBinding(binding_); | |
342 // For certain requests like empty POST requests the server can return | |
343 // back a HTTP success code in the range 200 to 299. We need to flag | |
344 // these requests as succeeded. | |
345 if (IS_HTTP_SUCCESS_CODE(http_code)) { | |
346 // If this DCHECK fires it means that the server returned a HTTP | |
347 // success code outside the standard range 200-206. We need to confirm | |
348 // if the following code path is correct. | |
349 DCHECK_LE(http_code, 206); | |
350 status_.set_result(S_OK); | |
351 std::string headers = GetHttpHeadersFromBinding(binding_); | |
352 OnResponse(0, base::UTF8ToWide(headers).c_str(), NULL, NULL); | |
353 } else if (net::HttpResponseHeaders::IsRedirectResponseCode(http_code) && | |
354 result == E_ACCESSDENIED) { | |
355 // Special case. If the last request was a redirect and the current OS | |
356 // error value is E_ACCESSDENIED, that means an unsafe redirect was | |
357 // attempted. In that case, correct the OS error value to be the more | |
358 // specific ERR_UNSAFE_REDIRECT error value. | |
359 status_.set_result(net::URLRequestStatus::FAILED, | |
360 net::ERR_UNSAFE_REDIRECT); | |
361 } | |
362 } | |
363 | |
364 // The code below seems easy but it is not. :) | |
365 // The network policy in Chrome network is that error code/end_of_stream | |
366 // should be returned only as a result of read (or start) request. | |
367 // Here are the possible cases: | |
368 // pending_data_|pending_read | |
369 // FALSE |FALSE => EndRequest if no headers, otherwise wait for Read. | |
370 // FALSE |TRUE => EndRequest. | |
371 // TRUE |FALSE => Wait for Read. | |
372 // TRUE |TRUE => Something went wrong!! | |
373 | |
374 if (pending_data_) { | |
375 DCHECK_EQ(pending_read_size_, 0UL); | |
376 ReleaseBindings(); | |
377 return S_OK; | |
378 } | |
379 | |
380 if (headers_received_ && pending_read_size_ == 0) { | |
381 ReleaseBindings(); | |
382 return S_OK; | |
383 } | |
384 | |
385 // No headers or there is a pending read from Chrome. | |
386 NotifyDelegateAndDie(); | |
387 return S_OK; | |
388 } | |
389 | |
390 // Status::ABORTING | |
391 if (status_.was_redirected()) { | |
392 // Just release bindings here. Chrome will issue EndRequest(request_id) | |
393 // after processing headers we had provided. | |
394 if (!pending_) { | |
395 std::string headers = GetHttpHeadersFromBinding(binding_); | |
396 OnResponse(0, base::UTF8ToWide(headers).c_str(), NULL, NULL); | |
397 } | |
398 ReleaseBindings(); | |
399 return S_OK; | |
400 } | |
401 | |
402 // Stop invoked. | |
403 NotifyDelegateAndDie(); | |
404 return S_OK; | |
405 } | |
406 | |
407 STDMETHODIMP UrlmonUrlRequest::GetBindInfo(DWORD* bind_flags, | |
408 BINDINFO* bind_info) { | |
409 if ((bind_info == NULL) || (bind_info->cbSize == 0) || (bind_flags == NULL)) | |
410 return E_INVALIDARG; | |
411 | |
412 *bind_flags = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA; | |
413 | |
414 bind_info->dwOptionsFlags = INTERNET_FLAG_NO_AUTO_REDIRECT; | |
415 bind_info->dwOptions = BINDINFO_OPTIONS_WININETFLAG; | |
416 | |
417 // TODO(ananta) | |
418 // Look into whether the other load flags need to be supported in chrome | |
419 // frame. | |
420 if (load_flags_ & net::LOAD_VALIDATE_CACHE) | |
421 *bind_flags |= BINDF_RESYNCHRONIZE; | |
422 | |
423 if (load_flags_ & net::LOAD_BYPASS_CACHE) | |
424 *bind_flags |= BINDF_GETNEWESTVERSION; | |
425 | |
426 if (LowerCaseEqualsASCII(method(), "get")) { | |
427 bind_info->dwBindVerb = BINDVERB_GET; | |
428 } else if (LowerCaseEqualsASCII(method(), "post")) { | |
429 bind_info->dwBindVerb = BINDVERB_POST; | |
430 } else if (LowerCaseEqualsASCII(method(), "put")) { | |
431 bind_info->dwBindVerb = BINDVERB_PUT; | |
432 } else { | |
433 std::wstring verb(base::ASCIIToWide(StringToUpperASCII(method()))); | |
434 bind_info->dwBindVerb = BINDVERB_CUSTOM; | |
435 bind_info->szCustomVerb = reinterpret_cast<wchar_t*>( | |
436 ::CoTaskMemAlloc((verb.length() + 1) * sizeof(wchar_t))); | |
437 lstrcpyW(bind_info->szCustomVerb, verb.c_str()); | |
438 } | |
439 | |
440 if (bind_info->dwBindVerb == BINDVERB_POST || | |
441 bind_info->dwBindVerb == BINDVERB_PUT || | |
442 post_data_len() > 0) { | |
443 // Bypass caching proxies on upload requests and avoid writing responses to | |
444 // the browser's cache. | |
445 *bind_flags |= BINDF_GETNEWESTVERSION | BINDF_PRAGMA_NO_CACHE; | |
446 | |
447 // Attempt to avoid storing the response for upload requests. | |
448 // See http://crbug.com/55918 | |
449 if (resource_type_ != ResourceType::MAIN_FRAME) | |
450 *bind_flags |= BINDF_NOWRITECACHE; | |
451 | |
452 // Initialize the STGMEDIUM. | |
453 memset(&bind_info->stgmedData, 0, sizeof(STGMEDIUM)); | |
454 bind_info->grfBindInfoF = 0; | |
455 | |
456 if (bind_info->dwBindVerb != BINDVERB_CUSTOM) | |
457 bind_info->szCustomVerb = NULL; | |
458 | |
459 if ((post_data_len() || is_chunked_upload()) && | |
460 get_upload_data(&bind_info->stgmedData.pstm) == S_OK) { | |
461 bind_info->stgmedData.tymed = TYMED_ISTREAM; | |
462 if (!is_chunked_upload()) { | |
463 bind_info->cbstgmedData = static_cast<DWORD>(post_data_len()); | |
464 } | |
465 DVLOG(1) << __FUNCTION__ << me() << method() | |
466 << " request with " << base::Int64ToString(post_data_len()) | |
467 << " bytes. url=" << url(); | |
468 } else { | |
469 DVLOG(1) << __FUNCTION__ << me() << "POST request with no data!"; | |
470 } | |
471 } | |
472 return S_OK; | |
473 } | |
474 | |
475 STDMETHODIMP UrlmonUrlRequest::OnDataAvailable(DWORD flags, DWORD size, | |
476 FORMATETC* formatetc, | |
477 STGMEDIUM* storage) { | |
478 DCHECK_EQ(thread_, base::PlatformThread::CurrentId()); | |
479 DVLOG(1) << __FUNCTION__ << me() << "bytes available: " << size; | |
480 | |
481 if (terminate_requested()) { | |
482 DVLOG(1) << " Download requested. INET_E_TERMINATED_BIND returned"; | |
483 return INET_E_TERMINATED_BIND; | |
484 } | |
485 | |
486 if (!storage || (storage->tymed != TYMED_ISTREAM)) { | |
487 NOTREACHED(); | |
488 return E_INVALIDARG; | |
489 } | |
490 | |
491 IStream* read_stream = storage->pstm; | |
492 if (!read_stream) { | |
493 NOTREACHED(); | |
494 return E_UNEXPECTED; | |
495 } | |
496 | |
497 // Some requests such as HEAD have zero data. | |
498 if (size > 0) | |
499 pending_data_ = read_stream; | |
500 | |
501 if (pending_read_size_) { | |
502 size_t bytes_copied = SendDataToDelegate(pending_read_size_); | |
503 DVLOG(1) << __FUNCTION__ << me() << "size read: " << bytes_copied; | |
504 } else { | |
505 DVLOG(1) << __FUNCTION__ << me() << "- waiting for remote read"; | |
506 } | |
507 | |
508 if (BSCF_LASTDATANOTIFICATION & flags) { | |
509 if (!is_expecting_download_ || pending()) { | |
510 DVLOG(1) << __FUNCTION__ << me() << "EOF"; | |
511 return S_OK; | |
512 } | |
513 // Always return INET_E_TERMINATED_BIND to allow bind context reuse | |
514 // if DownloadToHost is suddenly requested. | |
515 DVLOG(1) << __FUNCTION__ << " EOF: INET_E_TERMINATED_BIND returned"; | |
516 return INET_E_TERMINATED_BIND; | |
517 } | |
518 return S_OK; | |
519 } | |
520 | |
521 STDMETHODIMP UrlmonUrlRequest::OnObjectAvailable(REFIID iid, IUnknown* object) { | |
522 // We are calling BindToStorage on the moniker we should always get called | |
523 // back on OnDataAvailable and should never get OnObjectAvailable | |
524 NOTREACHED(); | |
525 return E_NOTIMPL; | |
526 } | |
527 | |
528 STDMETHODIMP UrlmonUrlRequest::BeginningTransaction(const wchar_t* url, | |
529 const wchar_t* current_headers, DWORD reserved, | |
530 wchar_t** additional_headers) { | |
531 DCHECK_EQ(thread_, base::PlatformThread::CurrentId()); | |
532 if (!additional_headers) { | |
533 NOTREACHED(); | |
534 return E_POINTER; | |
535 } | |
536 | |
537 DVLOG(1) << __FUNCTION__ << me() << "headers: \n" << current_headers; | |
538 | |
539 if (status_.get_state() == Status::ABORTING) { | |
540 // At times the BINDSTATUS_REDIRECTING notification which is sent to the | |
541 // IBindStatusCallback interface does not have an accompanying HTTP | |
542 // redirect status code, i.e. the attempt to query the HTTP status code | |
543 // from the binding returns 0, 200, etc which are invalid redirect codes. | |
544 // We don't want urlmon to follow redirects. We return E_ABORT in our | |
545 // IBindStatusCallback::OnProgress function and also abort the binding. | |
546 // However urlmon still tries to establish a transaction with the | |
547 // redirected URL which confuses the web server. | |
548 // Fix is to abort the attempted transaction. | |
549 DLOG(WARNING) << __FUNCTION__ << me() | |
550 << ": Aborting connection to URL:" | |
551 << url | |
552 << " as the binding has been aborted"; | |
553 return E_ABORT; | |
554 } | |
555 | |
556 HRESULT hr = S_OK; | |
557 | |
558 std::string new_headers; | |
559 if (is_chunked_upload()) { | |
560 new_headers = base::StringPrintf("Transfer-Encoding: chunked\r\n"); | |
561 } | |
562 | |
563 if (!extra_headers().empty()) { | |
564 // TODO(robertshield): We may need to sanitize headers on POST here. | |
565 new_headers += extra_headers(); | |
566 } | |
567 | |
568 if (!referrer().empty()) { | |
569 // Referrer is famously misspelled in HTTP: | |
570 new_headers += base::StringPrintf("Referer: %s\r\n", referrer().c_str()); | |
571 } | |
572 | |
573 // In the rare case if "User-Agent" string is already in |current_headers|. | |
574 // We send Chrome's user agent in requests initiated within ChromeFrame to | |
575 // enable third party content in pages rendered in ChromeFrame to correctly | |
576 // send content for Chrome as the third party content may not be equipped to | |
577 // identify chromeframe as the user agent. This also ensures that the user | |
578 // agent reported in scripts in chrome frame is consistent with that sent | |
579 // in outgoing requests. | |
580 std::string user_agent = http_utils::AddChromeFrameToUserAgentValue( | |
581 http_utils::GetChromeUserAgent()); | |
582 new_headers += ReplaceOrAddUserAgent(current_headers, user_agent); | |
583 | |
584 if (!new_headers.empty()) { | |
585 *additional_headers = reinterpret_cast<wchar_t*>( | |
586 CoTaskMemAlloc((new_headers.size() + 1) * sizeof(wchar_t))); | |
587 | |
588 if (*additional_headers == NULL) { | |
589 NOTREACHED(); | |
590 hr = E_OUTOFMEMORY; | |
591 } else { | |
592 lstrcpynW(*additional_headers, base::ASCIIToWide(new_headers).c_str(), | |
593 new_headers.size()); | |
594 } | |
595 } | |
596 request_headers_ = new_headers; | |
597 return hr; | |
598 } | |
599 | |
600 STDMETHODIMP UrlmonUrlRequest::OnResponse(DWORD dwResponseCode, | |
601 const wchar_t* response_headers, const wchar_t* request_headers, | |
602 wchar_t** additional_headers) { | |
603 return S_OK; | |
604 } | |
605 | |
606 STDMETHODIMP UrlmonUrlRequest::GetWindow(const GUID& guid_reason, | |
607 HWND* parent_window) { | |
608 if (!parent_window) | |
609 return E_INVALIDARG; | |
610 | |
611 #ifndef NDEBUG | |
612 wchar_t guid[40] = {0}; | |
613 ::StringFromGUID2(guid_reason, guid, arraysize(guid)); | |
614 const wchar_t* str = guid; | |
615 if (guid_reason == IID_IAuthenticate) | |
616 str = L"IAuthenticate"; | |
617 else if (guid_reason == IID_IHttpSecurity) | |
618 str = L"IHttpSecurity"; | |
619 else if (guid_reason == IID_IWindowForBindingUI) | |
620 str = L"IWindowForBindingUI"; | |
621 DVLOG(1) << __FUNCTION__ << me() << "GetWindow: " << str; | |
622 #endif | |
623 // We should return a non-NULL HWND as parent. Otherwise no dialog is shown. | |
624 // TODO(iyengar): This hits when running the URL request tests. | |
625 DLOG_IF(WARNING, !::IsWindow(parent_window_)) | |
626 << "UrlmonUrlRequest::GetWindow - no window!"; | |
627 *parent_window = parent_window_; | |
628 return S_OK; | |
629 } | |
630 | |
631 STDMETHODIMP UrlmonUrlRequest::Authenticate(HWND* parent_window, | |
632 LPWSTR* user_name, | |
633 LPWSTR* password) { | |
634 if (!parent_window) | |
635 return E_INVALIDARG; | |
636 | |
637 if (privileged_mode_) | |
638 return E_ACCESSDENIED; | |
639 | |
640 DCHECK(::IsWindow(parent_window_)); | |
641 *parent_window = parent_window_; | |
642 return S_OK; | |
643 } | |
644 | |
645 STDMETHODIMP UrlmonUrlRequest::OnSecurityProblem(DWORD problem) { | |
646 // Urlmon notifies the client of authentication problems, certificate | |
647 // errors, etc by querying the object implementing the IBindStatusCallback | |
648 // interface for the IHttpSecurity interface. If this interface is not | |
649 // implemented then Urlmon checks for the problem codes defined below | |
650 // and performs actions as defined below:- | |
651 // It invokes the ReportProgress method of the protocol sink with | |
652 // these problem codes and eventually invokes the ReportResult method | |
653 // on the protocol sink which ends up in a call to the OnStopBinding | |
654 // method of the IBindStatusCallBack interface. | |
655 | |
656 // MSHTML's implementation of the IBindStatusCallback interface does not | |
657 // implement the IHttpSecurity interface. However it handles the | |
658 // OnStopBinding call with a HRESULT of 0x800c0019 and navigates to | |
659 // an interstitial page which presents the user with a choice of whether | |
660 // to abort the navigation. | |
661 | |
662 // In our OnStopBinding implementation we stop the navigation and inform | |
663 // Chrome about the result. Ideally Chrome should behave in a manner similar | |
664 // to IE, i.e. display the SSL error interstitial page and if the user | |
665 // decides to proceed anyway we would turn off SSL warnings for that | |
666 // particular navigation and allow IE to download the content. | |
667 // We would need to return the certificate information to Chrome for display | |
668 // purposes. Currently we only return a dummy certificate to Chrome. | |
669 // At this point we decided that it is a lot of work at this point and | |
670 // decided to go with the easier option of implementing the IHttpSecurity | |
671 // interface and replicating the checks performed by Urlmon. This | |
672 // causes Urlmon to display a dialog box on the same lines as IE6. | |
673 DVLOG(1) << __FUNCTION__ << me() << "Security problem : " << problem; | |
674 | |
675 // On IE6 the default IBindStatusCallback interface does not implement the | |
676 // IHttpSecurity interface and thus causes IE to put up a certificate error | |
677 // dialog box. We need to emulate this behavior for sites with mismatched | |
678 // certificates to work. | |
679 if (GetIEVersion() == IE_6) | |
680 return S_FALSE; | |
681 | |
682 HRESULT hr = E_ABORT; | |
683 | |
684 switch (problem) { | |
685 case ERROR_INTERNET_SEC_CERT_REV_FAILED: { | |
686 hr = RPC_E_RETRY; | |
687 break; | |
688 } | |
689 | |
690 case ERROR_INTERNET_SEC_CERT_DATE_INVALID: | |
691 case ERROR_INTERNET_SEC_CERT_CN_INVALID: | |
692 case ERROR_INTERNET_INVALID_CA: { | |
693 hr = S_FALSE; | |
694 break; | |
695 } | |
696 | |
697 default: { | |
698 NOTREACHED() << "Unhandled security problem : " << problem; | |
699 break; | |
700 } | |
701 } | |
702 return hr; | |
703 } | |
704 | |
705 HRESULT UrlmonUrlRequest::StartAsyncDownload() { | |
706 DVLOG(1) << __FUNCTION__ << me() << url(); | |
707 HRESULT hr = E_FAIL; | |
708 DCHECK((moniker_ && bind_context_) || (!moniker_ && !bind_context_)); | |
709 | |
710 if (!moniker_.get()) { | |
711 std::wstring wide_url = base::UTF8ToWide(url()); | |
712 hr = CreateURLMonikerEx(NULL, wide_url.c_str(), moniker_.Receive(), | |
713 URL_MK_UNIFORM); | |
714 if (FAILED(hr)) { | |
715 NOTREACHED() << "CreateURLMonikerEx failed. Error: " << hr; | |
716 return hr; | |
717 } | |
718 } | |
719 | |
720 if (bind_context_.get() == NULL) { | |
721 hr = ::CreateAsyncBindCtxEx(NULL, 0, this, NULL, | |
722 bind_context_.Receive(), 0); | |
723 DCHECK(SUCCEEDED(hr)) << "CreateAsyncBindCtxEx failed. Error: " << hr; | |
724 } else { | |
725 // Use existing bind context. | |
726 hr = ::RegisterBindStatusCallback(bind_context_, this, NULL, 0); | |
727 DCHECK(SUCCEEDED(hr)) << "RegisterBindStatusCallback failed. Error: " << hr; | |
728 } | |
729 | |
730 if (SUCCEEDED(hr)) { | |
731 base::win::ScopedComPtr<IStream> stream; | |
732 | |
733 // BindToStorage may complete synchronously. | |
734 // We still get all the callbacks - OnStart/StopBinding, this may result | |
735 // in destruction of our object. It's fine but we access some members | |
736 // below for debug info. :) | |
737 base::win::ScopedComPtr<IHttpSecurity> self(this); | |
738 | |
739 // Inform our moniker patch this binding should not be tortured. | |
740 base::win::ScopedComPtr<BindContextInfo> info; | |
741 BindContextInfo::FromBindContext(bind_context_, info.Receive()); | |
742 DCHECK(info); | |
743 if (info) | |
744 info->set_chrome_request(true); | |
745 | |
746 hr = moniker_->BindToStorage(bind_context_, NULL, __uuidof(IStream), | |
747 reinterpret_cast<void**>(stream.Receive())); | |
748 if (hr == S_OK) | |
749 DCHECK(binding_ != NULL || status_.get_state() == Status::DONE); | |
750 | |
751 if (FAILED(hr)) { | |
752 // TODO(joshia): Look into. This currently fails for: | |
753 // http://user2:secret@localhost:1337/auth-basic?set-cookie-if-challenged | |
754 // when running the UrlRequest unit tests. | |
755 DLOG(ERROR) << __FUNCTION__ << me() << | |
756 base::StringPrintf("IUrlMoniker::BindToStorage failed 0x%08X.", hr); | |
757 // In most cases we'll get a MK_E_SYNTAX error here but if we abort | |
758 // the navigation ourselves such as in the case of seeing something | |
759 // else than ALLOWALL in X-Frame-Options. | |
760 } | |
761 } | |
762 | |
763 DLOG_IF(ERROR, FAILED(hr)) << me() << | |
764 base::StringPrintf(L"StartAsyncDownload failed: 0x%08X", hr); | |
765 | |
766 return hr; | |
767 } | |
768 | |
769 void UrlmonUrlRequest::NotifyDelegateAndDie() { | |
770 } | |
771 | |
772 void UrlmonUrlRequest::TerminateTransaction() { | |
773 if (cleanup_transaction_ && bind_context_ && moniker_) { | |
774 // We return INET_E_TERMINATED_BIND from our OnDataAvailable implementation | |
775 // to ensure that the transaction stays around if Chrome decides to issue | |
776 // a download request when it finishes inspecting the headers received in | |
777 // OnResponse. However this causes the urlmon transaction object to leak. | |
778 // To workaround this we save away the IInternetProtocol interface which is | |
779 // implemented by the urlmon CTransaction object in our BindContextInfo | |
780 // instance which is maintained per bind context. Invoking Terminate | |
781 // on this with the special flags 0x2000000 cleanly releases the | |
782 // transaction. | |
783 static const int kUrlmonTerminateTransactionFlags = 0x2000000; | |
784 base::win::ScopedComPtr<BindContextInfo> info; | |
785 BindContextInfo::FromBindContext(bind_context_, info.Receive()); | |
786 DCHECK(info); | |
787 if (info && info->protocol()) { | |
788 info->protocol()->Terminate(kUrlmonTerminateTransactionFlags); | |
789 } | |
790 } | |
791 bind_context_.Release(); | |
792 } | |
793 | |
794 void UrlmonUrlRequest::ReleaseBindings() { | |
795 binding_.Release(); | |
796 // Do not release bind_context here! | |
797 // We may get DownloadToHost request and therefore we want the bind_context | |
798 // to be available. | |
799 if (bind_context_) | |
800 ::RevokeBindStatusCallback(bind_context_, this); | |
801 } | |
802 | |
803 net::Error UrlmonUrlRequest::HresultToNetError(HRESULT hr) { | |
804 const int kInvalidHostName = 0x8007007b; | |
805 // Useful reference: | |
806 // http://msdn.microsoft.com/en-us/library/ms775145(VS.85).aspx | |
807 | |
808 net::Error ret = net::ERR_UNEXPECTED; | |
809 | |
810 switch (hr) { | |
811 case S_OK: | |
812 ret = net::OK; | |
813 break; | |
814 | |
815 case MK_E_SYNTAX: | |
816 ret = net::ERR_INVALID_URL; | |
817 break; | |
818 | |
819 case INET_E_CANNOT_CONNECT: | |
820 ret = net::ERR_CONNECTION_FAILED; | |
821 break; | |
822 | |
823 case INET_E_DOWNLOAD_FAILURE: | |
824 case INET_E_CONNECTION_TIMEOUT: | |
825 case E_ABORT: | |
826 ret = net::ERR_CONNECTION_ABORTED; | |
827 break; | |
828 | |
829 case INET_E_DATA_NOT_AVAILABLE: | |
830 ret = net::ERR_EMPTY_RESPONSE; | |
831 break; | |
832 | |
833 case INET_E_RESOURCE_NOT_FOUND: | |
834 // To behave more closely to the chrome network stack, we translate this | |
835 // error value as tunnel connection failed. This error value is tested | |
836 // in the ProxyTunnelRedirectTest and UnexpectedServerAuthTest tests. | |
837 ret = net::ERR_TUNNEL_CONNECTION_FAILED; | |
838 break; | |
839 | |
840 // The following error codes can be returned while processing an invalid | |
841 // url. http://msdn.microsoft.com/en-us/library/bb250493(v=vs.85).aspx | |
842 case INET_E_INVALID_URL: | |
843 case INET_E_UNKNOWN_PROTOCOL: | |
844 case INET_E_REDIRECT_FAILED: | |
845 case INET_E_SECURITY_PROBLEM: | |
846 case kInvalidHostName: | |
847 case E_INVALIDARG: | |
848 case E_OUTOFMEMORY: | |
849 ret = net::ERR_INVALID_URL; | |
850 break; | |
851 | |
852 case INET_E_INVALID_CERTIFICATE: | |
853 ret = net::ERR_CERT_INVALID; | |
854 break; | |
855 | |
856 case E_ACCESSDENIED: | |
857 ret = net::ERR_ACCESS_DENIED; | |
858 break; | |
859 | |
860 default: | |
861 DLOG(WARNING) | |
862 << base::StringPrintf("TODO: translate HRESULT 0x%08X to net::Error", | |
863 hr); | |
864 break; | |
865 } | |
866 return ret; | |
867 } | |
868 | |
869 | |
870 PluginUrlRequestManager::ThreadSafeFlags | |
871 UrlmonUrlRequestManager::GetThreadSafeFlags() { | |
872 return PluginUrlRequestManager::NOT_THREADSAFE; | |
873 } | |
874 | |
875 void UrlmonUrlRequestManager::SetInfoForUrl(const std::wstring& url, | |
876 IMoniker* moniker, LPBC bind_ctx) { | |
877 CComObject<UrlmonUrlRequest>* new_request = NULL; | |
878 CComObject<UrlmonUrlRequest>::CreateInstance(&new_request); | |
879 if (new_request) { | |
880 GURL start_url(url); | |
881 DCHECK(start_url.is_valid()); | |
882 DCHECK(pending_request_ == NULL); | |
883 | |
884 base::win::ScopedComPtr<BindContextInfo> info; | |
885 BindContextInfo::FromBindContext(bind_ctx, info.Receive()); | |
886 DCHECK(info); | |
887 IStream* cache = info ? info->cache() : NULL; | |
888 pending_request_ = new_request; | |
889 pending_request_->InitPending(start_url, moniker, bind_ctx, | |
890 enable_frame_busting_, privileged_mode_, | |
891 notification_window_, cache); | |
892 // Start the request | |
893 bool is_started = pending_request_->Start(); | |
894 DCHECK(is_started); | |
895 } | |
896 } | |
897 | |
898 void UrlmonUrlRequestManager::BindTerminated(IMoniker* moniker, | |
899 IBindCtx* bind_ctx, | |
900 IStream* post_data, | |
901 const char* request_headers) { | |
902 } | |
903 | |
904 scoped_refptr<UrlmonUrlRequest> UrlmonUrlRequestManager::LookupRequest( | |
905 int request_id, RequestMap* request_map) { | |
906 RequestMap::iterator it = request_map->find(request_id); | |
907 if (request_map->end() != it) | |
908 return it->second; | |
909 return NULL; | |
910 } | |
911 | |
912 UrlmonUrlRequestManager::UrlmonUrlRequestManager() | |
913 : stopping_(false), notification_window_(NULL), | |
914 privileged_mode_(false), | |
915 container_(NULL), | |
916 background_worker_thread_enabled_(true) { | |
917 background_thread_.reset(new base::Thread("cf_iexplore_background_thread")); | |
918 background_thread_->init_com_with_mta(false); | |
919 background_worker_thread_enabled_ = | |
920 GetConfigBool(true, kUseBackgroundThreadForSubResources); | |
921 if (background_worker_thread_enabled_) { | |
922 base::Thread::Options options; | |
923 options.message_loop_type = base::MessageLoop::TYPE_UI; | |
924 background_thread_->StartWithOptions(options); | |
925 } | |
926 } | |
927 | |
928 UrlmonUrlRequestManager::~UrlmonUrlRequestManager() { | |
929 } | |
930 | |
931 void UrlmonUrlRequestManager::AddPrivacyDataForUrl( | |
932 const std::string& url, const std::string& policy_ref, | |
933 int32 flags) { | |
934 DCHECK(!url.empty()); | |
935 | |
936 bool fire_privacy_event = false; | |
937 | |
938 if (privacy_info_.privacy_records.empty()) | |
939 flags |= PRIVACY_URLISTOPLEVEL; | |
940 | |
941 if (!privacy_info_.privacy_impacted) { | |
942 if (flags & (COOKIEACTION_ACCEPT | COOKIEACTION_REJECT | | |
943 COOKIEACTION_DOWNGRADE)) { | |
944 privacy_info_.privacy_impacted = true; | |
945 fire_privacy_event = true; | |
946 } | |
947 } | |
948 | |
949 PrivacyInfo::PrivacyEntry& privacy_entry = | |
950 privacy_info_.privacy_records[base::UTF8ToWide(url)]; | |
951 | |
952 privacy_entry.flags |= flags; | |
953 privacy_entry.policy_ref = base::UTF8ToWide(policy_ref); | |
954 | |
955 if (fire_privacy_event && IsWindow(notification_window_)) { | |
956 PostMessage(notification_window_, WM_FIRE_PRIVACY_CHANGE_NOTIFICATION, 1, | |
957 0); | |
958 } | |
959 } | |
OLD | NEW |