blob: 45634b7c62c45f7a58d63059780e6b6128638dcf [file] [log] [blame]
[email protected]f7817822009-09-24 05:11:581// Copyright (c) 2009 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 <wininet.h>
8
9#include "base/scoped_ptr.h"
10#include "base/string_util.h"
11#include "base/logging.h"
[email protected]5778de6e2009-10-24 01:29:2012#include "base/message_loop.h"
13#include "chrome_frame/chrome_frame_activex_base.h"
[email protected]f7817822009-09-24 05:11:5814#include "chrome_frame/urlmon_upload_data_stream.h"
[email protected]9aff1a62009-09-28 22:45:2915#include "chrome_frame/utils.h"
[email protected]f7817822009-09-24 05:11:5816#include "net/http/http_util.h"
17#include "net/http/http_response_headers.h"
18
19static const LARGE_INTEGER kZero = {0};
20static const ULARGE_INTEGER kUnsignedZero = {0};
21int UrlmonUrlRequest::instance_count_ = 0;
22const char kXFrameOptionsHeader[] = "X-Frame-Options";
23
24UrlmonUrlRequest::UrlmonUrlRequest()
25 : pending_read_size_(0),
26 status_(URLRequestStatus::FAILED, net::ERR_FAILED),
27 thread_(PlatformThread::CurrentId()),
[email protected]f7817822009-09-24 05:11:5828 post_data_len_(0),
29 redirect_status_(0),
[email protected]5778de6e2009-10-24 01:29:2030 parent_window_(NULL),
31 worker_thread_(NULL),
32 task_marshaller_(NULL) {
[email protected]f7817822009-09-24 05:11:5833 DLOG(INFO) << StringPrintf("Created request. Obj: %X", this)
34 << " Count: " << ++instance_count_;
35}
36
37UrlmonUrlRequest::~UrlmonUrlRequest() {
38 DLOG(INFO) << StringPrintf("Deleted request. Obj: %X", this)
39 << " Count: " << --instance_count_;
40}
41
42bool UrlmonUrlRequest::Start() {
43 DCHECK_EQ(PlatformThread::CurrentId(), thread_);
44
[email protected]5778de6e2009-10-24 01:29:2045 if (!worker_thread_) {
46 NOTREACHED() << __FUNCTION__ << " Urlmon request thread not initialized";
47 return false;
48 }
49
50 worker_thread_->message_loop()->PostTask(
51 FROM_HERE, NewRunnableMethod(this, &UrlmonUrlRequest::StartAsync));
52
53 // Take a self reference to maintain COM lifetime. This will be released
54 // in EndRequest
55 AddRef();
56 request_handler()->AddRequest(this);
57 return true;
58}
59
60void UrlmonUrlRequest::Stop() {
61 DCHECK_EQ(PlatformThread::CurrentId(), thread_);
62
63 if (!worker_thread_) {
64 NOTREACHED() << __FUNCTION__ << " Urlmon request thread not initialized";
65 return;
66 }
67
68 worker_thread_->message_loop()->PostTask(
69 FROM_HERE, NewRunnableMethod(this, &UrlmonUrlRequest::StopAsync));
70}
71
72void UrlmonUrlRequest::StartAsync() {
73 DCHECK(worker_thread_ != NULL);
74
[email protected]f7817822009-09-24 05:11:5875 status_.set_status(URLRequestStatus::IO_PENDING);
76 HRESULT hr = StartAsyncDownload();
77 if (FAILED(hr)) {
78 // Do not call EndRequest() here since it will attempt to free references
79 // that have not been established.
80 status_.set_os_error(HresultToNetError(hr));
81 status_.set_status(URLRequestStatus::FAILED);
82 DLOG(ERROR) << "StartAsyncDownload failed";
[email protected]5778de6e2009-10-24 01:29:2083 EndRequest();
84 return;
[email protected]f7817822009-09-24 05:11:5885 }
[email protected]f7817822009-09-24 05:11:5886}
87
[email protected]5778de6e2009-10-24 01:29:2088void UrlmonUrlRequest::StopAsync() {
89 DCHECK(worker_thread_ != NULL);
[email protected]f7817822009-09-24 05:11:5890
91 if (binding_) {
92 binding_->Abort();
93 } else {
94 status_.set_status(URLRequestStatus::CANCELED);
95 status_.set_os_error(net::ERR_FAILED);
96 EndRequest();
97 }
98}
99
100bool UrlmonUrlRequest::Read(int bytes_to_read) {
101 DCHECK_EQ(PlatformThread::CurrentId(), thread_);
102
103 DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this);
104
[email protected]5778de6e2009-10-24 01:29:20105 if (!worker_thread_) {
106 NOTREACHED() << __FUNCTION__ << " Urlmon request thread not initialized";
107 return false;
108 }
109
110 worker_thread_->message_loop()->PostTask(
111 FROM_HERE, NewRunnableMethod(this, &UrlmonUrlRequest::ReadAsync,
112 bytes_to_read));
113 return true;
114}
115
116void UrlmonUrlRequest::ReadAsync(int bytes_to_read) {
[email protected]f7817822009-09-24 05:11:58117 // Send cached data if available.
118 CComObjectStackEx<SendStream> send_stream;
119 send_stream.Initialize(this);
[email protected]5778de6e2009-10-24 01:29:20120
[email protected]f7817822009-09-24 05:11:58121 size_t bytes_copied = 0;
122 if (cached_data_.is_valid() && cached_data_.Read(&send_stream, bytes_to_read,
123 &bytes_copied)) {
124 DLOG(INFO) << StringPrintf("URL: %s Obj: %X - bytes read from cache: %d",
125 url().c_str(), this, bytes_copied);
[email protected]5778de6e2009-10-24 01:29:20126 return;
[email protected]f7817822009-09-24 05:11:58127 }
128
129 // if the request is finished or there's nothing more to read
130 // then end the request
131 if (!status_.is_io_pending() || !binding_) {
132 DLOG(INFO) << StringPrintf("URL: %s Obj: %X. Response finished. Status: %d",
133 url().c_str(), this, status_.status());
134 EndRequest();
[email protected]5778de6e2009-10-24 01:29:20135 return;
[email protected]f7817822009-09-24 05:11:58136 }
137
138 pending_read_size_ = bytes_to_read;
139 DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) <<
140 "- Read pending for: " << bytes_to_read;
[email protected]f7817822009-09-24 05:11:58141}
142
143STDMETHODIMP UrlmonUrlRequest::OnStartBinding(
144 DWORD reserved, IBinding *binding) {
145 binding_ = binding;
146 return S_OK;
147}
148
149STDMETHODIMP UrlmonUrlRequest::GetPriority(LONG *priority) {
150 if (!priority)
151 return E_POINTER;
152 *priority = THREAD_PRIORITY_NORMAL;
153 return S_OK;
154}
155
156STDMETHODIMP UrlmonUrlRequest::OnLowResource(DWORD reserved) {
157 return S_OK;
158}
159
160STDMETHODIMP UrlmonUrlRequest::OnProgress(ULONG progress, ULONG max_progress,
161 ULONG status_code, LPCWSTR status_text) {
162 switch (status_code) {
163 case BINDSTATUS_REDIRECTING:
164 DCHECK(status_text != NULL);
165 DLOG(INFO) << "URL: " << url() << " redirected to "
166 << status_text;
167 redirect_url_ = status_text;
168 // Fetch the redirect status as they aren't all equal (307 in particular
169 // retains the HTTP request verb).
170 redirect_status_ = GetHttpResponseStatus();
[email protected]885362ca2009-10-27 21:50:07171 // NOTE: Even though RFC 2616 says to preserve the request method when
172 // following a 302 redirect, normal browsers don't do that. Instead they
173 // all convert a POST into a GET in response to a 302 and so shall we.
174 // For 307 redirects, browsers preserve the method. The RFC says to
175 // prompt the user to confirm the generation of a new POST request, but
176 // IE omits this prompt and so shall we.
177 if (redirect_status_ != 307 &&
178 LowerCaseEqualsASCII(method(), "post")) {
179 set_method("get");
180 post_data_len_ = 0;
181 }
[email protected]f7817822009-09-24 05:11:58182 break;
183
184 default:
185 DLOG(INFO) << " Obj: " << std::hex << this << " OnProgress(" << url()
186 << StringPrintf(L") code: %i status: %ls", status_code, status_text);
187 break;
188 }
189
190 return S_OK;
191}
192
193STDMETHODIMP UrlmonUrlRequest::OnStopBinding(HRESULT result, LPCWSTR error) {
[email protected]5778de6e2009-10-24 01:29:20194 DCHECK(worker_thread_ != NULL);
195 DCHECK_EQ(PlatformThread::CurrentId(), worker_thread_->thread_id());
[email protected]f7817822009-09-24 05:11:58196
197 DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) <<
198 " - Request stopped, Result: " << std::hex << result <<
199 " Status: " << status_.status();
200 if (FAILED(result)) {
201 status_.set_status(URLRequestStatus::FAILED);
202 status_.set_os_error(HresultToNetError(result));
203 EndRequest();
204 } else {
205 status_.set_status(URLRequestStatus::SUCCESS);
206 status_.set_os_error(0);
207 }
208
209 // Release these variables after reporting EndRequest since we might need to
210 // access their state.
211 binding_ = NULL;
212 bind_context_ = NULL;
213
214 return S_OK;
215}
216
217STDMETHODIMP UrlmonUrlRequest::GetBindInfo(DWORD* bind_flags,
218 BINDINFO *bind_info) {
[email protected]5778de6e2009-10-24 01:29:20219 DCHECK(worker_thread_ != NULL);
220 DCHECK_EQ(PlatformThread::CurrentId(), worker_thread_->thread_id());
[email protected]f7817822009-09-24 05:11:58221
222 if ((bind_info == NULL) || (bind_info->cbSize == 0) || (bind_flags == NULL))
223 return E_INVALIDARG;
224
225 *bind_flags = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA;
226 if (LowerCaseEqualsASCII(method(), "get")) {
227 bind_info->dwBindVerb = BINDVERB_GET;
228 } else if (LowerCaseEqualsASCII(method(), "post")) {
229 bind_info->dwBindVerb = BINDVERB_POST;
230
231 // Bypass caching proxies on POSTs and avoid writing responses to POST
232 // requests to the browser's cache.
233 *bind_flags |= BINDF_GETNEWESTVERSION | BINDF_NOWRITECACHE |
234 BINDF_PRAGMA_NO_CACHE;
235
236 // Initialize the STGMEDIUM.
237 memset(&bind_info->stgmedData, 0, sizeof(STGMEDIUM));
238 bind_info->grfBindInfoF = 0;
239 bind_info->szCustomVerb = NULL;
240
241 scoped_refptr<net::UploadData> upload_data(upload_data());
242 post_data_len_ = upload_data.get() ? upload_data->GetContentLength() : 0;
243 if (post_data_len_) {
244 DLOG(INFO) << " Obj: " << std::hex << this << " POST request with "
245 << Int64ToString(post_data_len_) << " bytes";
246 CComObject<UrlmonUploadDataStream>* upload_stream = NULL;
247 HRESULT hr =
248 CComObject<UrlmonUploadDataStream>::CreateInstance(&upload_stream);
249 if (FAILED(hr)) {
250 NOTREACHED();
251 return hr;
252 }
253 upload_stream->Initialize(upload_data.get());
254
255 // Fill the STGMEDIUM with the data to post
256 bind_info->stgmedData.tymed = TYMED_ISTREAM;
257 bind_info->stgmedData.pstm = static_cast<IStream*>(upload_stream);
258 bind_info->stgmedData.pstm->AddRef();
259 } else {
260 DLOG(INFO) << " Obj: " << std::hex << this
261 << "POST request with no data!";
262 }
263 } else {
264 NOTREACHED() << "Unknown HTTP method.";
265 status_.set_status(URLRequestStatus::FAILED);
266 status_.set_os_error(net::ERR_INVALID_URL);
267 EndRequest();
268 return E_FAIL;
269 }
270
271 return S_OK;
272}
273
274STDMETHODIMP UrlmonUrlRequest::OnDataAvailable(DWORD flags, DWORD size,
275 FORMATETC* formatetc,
276 STGMEDIUM* storage) {
[email protected]5778de6e2009-10-24 01:29:20277 DCHECK(worker_thread_ != NULL);
278 DCHECK_EQ(PlatformThread::CurrentId(), worker_thread_->thread_id());
279
[email protected]f7817822009-09-24 05:11:58280 DLOG(INFO) << StringPrintf("URL: %s Obj: %X - Bytes available: %d",
281 url().c_str(), this, size);
282
283 if (!storage || (storage->tymed != TYMED_ISTREAM)) {
284 NOTREACHED();
285 return E_INVALIDARG;
286 }
287
288 IStream* read_stream = storage->pstm;
289 if (!read_stream) {
290 NOTREACHED();
291 return E_UNEXPECTED;
292 }
293
294 HRESULT hr = S_OK;
295 if (BSCF_FIRSTDATANOTIFICATION & flags) {
296 DCHECK(!cached_data_.is_valid());
297 cached_data_.Create();
298 }
299
300 // Always read data into cache. We have to read all the data here at this
301 // time or it won't be available later. Since the size of the data could
302 // be more than pending read size, it's not straightforward (or might even
303 // be impossible) to implement a true data pull model.
304 size_t bytes_available = 0;
305 cached_data_.Append(read_stream, &bytes_available);
306 DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) <<
307 " - Bytes read into cache: " << bytes_available;
308
309 if (pending_read_size_) {
310 CComObjectStackEx<SendStream> send_stream;
311 send_stream.Initialize(this);
312 cached_data_.Read(&send_stream, pending_read_size_, &pending_read_size_);
313 DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) <<
314 " - size read: " << pending_read_size_;
315 pending_read_size_ = 0;
316 } else {
317 DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) <<
318 " - waiting for remote read";
319 }
320
321 if (BSCF_LASTDATANOTIFICATION & flags) {
322 status_.set_status(URLRequestStatus::SUCCESS);
323 DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) <<
324 " - end of data.";
325 }
326
327 return S_OK;
328}
329
330STDMETHODIMP UrlmonUrlRequest::OnObjectAvailable(REFIID iid, IUnknown *object) {
331 // We are calling BindToStorage on the moniker we should always get called
332 // back on OnDataAvailable and should never get OnObjectAvailable
333 NOTREACHED();
334 return E_NOTIMPL;
335}
336
337STDMETHODIMP UrlmonUrlRequest::BeginningTransaction(const wchar_t* url,
338 const wchar_t* current_headers, DWORD reserved,
339 wchar_t** additional_headers) {
[email protected]5778de6e2009-10-24 01:29:20340 DCHECK(worker_thread_ != NULL);
341 DCHECK_EQ(PlatformThread::CurrentId(), worker_thread_->thread_id());
342
[email protected]f7817822009-09-24 05:11:58343 if (!additional_headers) {
344 NOTREACHED();
345 return E_POINTER;
346 }
347
348 DLOG(INFO) << "URL: " << url << " Obj: " << std::hex << this <<
349 " - Request headers: \n" << current_headers;
350
351 HRESULT hr = S_OK;
352
353 std::string new_headers;
354 if (post_data_len_ > 0) {
355 // Tack on the Content-Length header since when using an IStream type
356 // STGMEDIUM, it looks like it doesn't get set for us :(
357 new_headers = StringPrintf("Content-Length: %s\r\n",
358 Int64ToString(post_data_len_).c_str());
359 }
360
361 if (!extra_headers().empty()) {
362 // TODO(robertshield): We may need to sanitize headers on POST here.
363 new_headers += extra_headers();
364 }
365
366 if (!referrer().empty()) {
367 // Referrer is famously misspelled in HTTP:
368 new_headers += StringPrintf("Referer: %s\r\n", referrer().c_str());
369 }
370
371 if (!new_headers.empty()) {
372 *additional_headers = reinterpret_cast<wchar_t*>(
373 CoTaskMemAlloc((new_headers.size() + 1) * sizeof(wchar_t)));
374
375 if (*additional_headers == NULL) {
376 NOTREACHED();
377 hr = E_OUTOFMEMORY;
378 } else {
379 lstrcpynW(*additional_headers, ASCIIToWide(new_headers).c_str(),
380 new_headers.size());
381 }
382 }
383
384 return hr;
385}
386
387STDMETHODIMP UrlmonUrlRequest::OnResponse(DWORD dwResponseCode,
388 const wchar_t* response_headers, const wchar_t* request_headers,
389 wchar_t** additional_headers) {
[email protected]5778de6e2009-10-24 01:29:20390 DCHECK(worker_thread_ != NULL);
391 DCHECK_EQ(PlatformThread::CurrentId(), worker_thread_->thread_id());
[email protected]f7817822009-09-24 05:11:58392
393 std::string raw_headers = WideToUTF8(response_headers);
394
395 // Security check for frame busting headers. We don't honor the headers
[email protected]00cc7402009-09-28 21:15:36396 // as-such, but instead simply kill requests which we've been asked to
397 // look for. This puts the onus on the user of the UrlRequest to specify
398 // whether or not requests should be inspected. For ActiveDocuments, the
399 // answer is "no", since WebKit's detection/handling is sufficient and since
400 // ActiveDocuments cannot be hosted as iframes. For NPAPI and ActiveX
401 // documents, the Initialize() function of the PluginUrlRequest object
402 // allows them to specify how they'd like requests handled. Both should
[email protected]f7817822009-09-24 05:11:58403 // set enable_frame_busting_ to true to avoid CSRF attacks.
[email protected]00cc7402009-09-28 21:15:36404 // Should WebKit's handling of this ever change, we will need to re-visit
405 // how and when frames are killed to better mirror a policy which may
[email protected]f7817822009-09-24 05:11:58406 // do something other than kill the sub-document outright.
407
[email protected]00cc7402009-09-28 21:15:36408 // NOTE(slightlyoff): We don't use net::HttpResponseHeaders here because
[email protected]f7817822009-09-24 05:11:58409 // of lingering ICU/base_noicu issues.
410 if (frame_busting_enabled_ &&
411 net::HttpUtil::HasHeader(raw_headers, kXFrameOptionsHeader)) {
412 DLOG(ERROR) << "X-Frame-Options header detected, navigation canceled";
413 return E_FAIL;
414 }
415
416 std::wstring url_for_persistent_cookies =
417 redirect_url_.empty() ? UTF8ToWide(url()) : redirect_url_;
418
419 std::string persistent_cookies;
420
421 DWORD cookie_size = 0; // NOLINT
422 InternetGetCookie(url_for_persistent_cookies.c_str(), NULL, NULL,
423 &cookie_size);
424 if (cookie_size) {
[email protected]1e11b172009-10-23 00:03:45425 scoped_array<wchar_t> cookies(new wchar_t[cookie_size + 1]);
[email protected]f7817822009-09-24 05:11:58426 if (!InternetGetCookie(url_for_persistent_cookies.c_str(), NULL,
427 cookies.get(), &cookie_size)) {
428 NOTREACHED() << "InternetGetCookie failed. Error: " << GetLastError();
429 } else {
430 persistent_cookies = WideToUTF8(cookies.get());
431 }
432 }
433
434 OnResponseStarted("",
435 raw_headers.c_str(),
436 0,
437 base::Time(),
438 persistent_cookies,
439 redirect_url_.empty() ? std::string() :
440 WideToUTF8(redirect_url_),
441 redirect_status_);
442
443 return S_OK;
444}
445
446STDMETHODIMP UrlmonUrlRequest::GetWindow(const GUID& guid_reason,
447 HWND* parent_window) {
448 if (!parent_window) {
449 return E_INVALIDARG;
450 }
451
452#ifndef NDEBUG
453 wchar_t guid[40] = {0};
454 ::StringFromGUID2(guid_reason, guid, arraysize(guid));
455
456 DLOG(INFO) << " Obj: " << std::hex << this << " GetWindow: " <<
457 (guid_reason == IID_IAuthenticate ? L" - IAuthenticate" :
458 (guid_reason == IID_IHttpSecurity ? L"IHttpSecurity" :
459 (guid_reason == IID_IWindowForBindingUI ? L"IWindowForBindingUI" :
460 guid)));
461#endif
462
463 // TODO(iyengar): This hits when running the URL request tests.
464 DLOG_IF(ERROR, !::IsWindow(parent_window_))
465 << "UrlmonUrlRequest::GetWindow - no window!";
466 *parent_window = parent_window_;
467 return S_OK;
468}
469
470STDMETHODIMP UrlmonUrlRequest::Authenticate(HWND* parent_window,
471 LPWSTR* user_name,
472 LPWSTR* password) {
473 if (!parent_window) {
474 return E_INVALIDARG;
475 }
476
477 DCHECK(::IsWindow(parent_window_));
478 *parent_window = parent_window_;
479 return S_OK;
480}
481
482STDMETHODIMP UrlmonUrlRequest::OnSecurityProblem(DWORD problem) {
483 // Urlmon notifies the client of authentication problems, certificate
484 // errors, etc by querying the object implementing the IBindStatusCallback
485 // interface for the IHttpSecurity interface. If this interface is not
486 // implemented then Urlmon checks for the problem codes defined below
487 // and performs actions as defined below:-
488 // It invokes the ReportProgress method of the protocol sink with
489 // these problem codes and eventually invokes the ReportResult method
490 // on the protocol sink which ends up in a call to the OnStopBinding
491 // method of the IBindStatusCallBack interface.
492
493 // MSHTML's implementation of the IBindStatusCallback interface does not
494 // implement the IHttpSecurity interface. However it handles the
495 // OnStopBinding call with a HRESULT of 0x800c0019 and navigates to
496 // an interstitial page which presents the user with a choice of whether
497 // to abort the navigation.
498
499 // In our OnStopBinding implementation we stop the navigation and inform
500 // Chrome about the result. Ideally Chrome should behave in a manner similar
501 // to IE, i.e. display the SSL error interstitial page and if the user
502 // decides to proceed anyway we would turn off SSL warnings for that
503 // particular navigation and allow IE to download the content.
504 // We would need to return the certificate information to Chrome for display
505 // purposes. Currently we only return a dummy certificate to Chrome.
506 // At this point we decided that it is a lot of work at this point and
507 // decided to go with the easier option of implementing the IHttpSecurity
508 // interface and replicating the checks performed by Urlmon. This
509 // causes Urlmon to display a dialog box on the same lines as IE6.
510 DLOG(INFO) << __FUNCTION__ << " Security problem : " << problem;
511
[email protected]00cc7402009-09-28 21:15:36512 // On IE6 the default IBindStatusCallback interface does not implement the
513 // IHttpSecurity interface and thus causes IE to put up a certificate error
514 // dialog box. We need to emulate this behavior for sites with mismatched
515 // certificates to work.
516 if (GetIEVersion() == IE_6)
517 return S_FALSE;
518
[email protected]f7817822009-09-24 05:11:58519 HRESULT hr = E_ABORT;
520
521 switch (problem) {
522 case ERROR_INTERNET_SEC_CERT_REV_FAILED: {
523 hr = RPC_E_RETRY;
524 break;
525 }
526
527 case ERROR_INTERNET_SEC_CERT_DATE_INVALID:
528 case ERROR_INTERNET_SEC_CERT_CN_INVALID:
529 case ERROR_INTERNET_INVALID_CA: {
530 hr = S_FALSE;
531 break;
532 }
533
534 default: {
535 NOTREACHED() << "Unhandled security problem : " << problem;
536 break;
537 }
538 }
539 return hr;
540}
541
542HRESULT UrlmonUrlRequest::ConnectToExistingMoniker(IMoniker* moniker,
543 IBindCtx* context,
544 const std::wstring& url) {
545 if (!moniker || url.empty()) {
546 NOTREACHED() << "Invalid arguments";
547 return E_INVALIDARG;
548 }
549
550 DCHECK(moniker_.get() == NULL);
551 DCHECK(bind_context_.get() == NULL);
552
553 moniker_ = moniker;
554 bind_context_ = context;
555 set_url(WideToUTF8(url));
556 return S_OK;
557}
558
559HRESULT UrlmonUrlRequest::StartAsyncDownload() {
560 HRESULT hr = E_FAIL;
561 if (moniker_.get() == NULL) {
562 std::wstring wide_url = UTF8ToWide(url());
563 hr = CreateURLMonikerEx(NULL, wide_url.c_str(), moniker_.Receive(),
564 URL_MK_UNIFORM);
565 if (FAILED(hr)) {
566 NOTREACHED() << "CreateURLMonikerEx failed. Error: " << hr;
567 } else {
568 hr = CreateAsyncBindCtx(0, this, NULL, bind_context_.Receive());
569 DCHECK(SUCCEEDED(hr)) << "CreateAsyncBindCtx failed. Error: " << hr;
570 }
571 } else {
572 DCHECK(bind_context_.get() != NULL);
573 hr = RegisterBindStatusCallback(bind_context_, this, NULL, 0);
574 }
575
576 if (SUCCEEDED(hr)) {
577 ScopedComPtr<IStream> stream;
578 hr = moniker_->BindToStorage(bind_context_, NULL, __uuidof(IStream),
579 reinterpret_cast<void**>(stream.Receive()));
580 if (FAILED(hr)) {
581 // TODO(joshia): Look into. This currently fails for:
582 // http://user2:secret@localhost:1337/auth-basic?set-cookie-if-challenged
583 // when running the UrlRequest unit tests.
584 DLOG(ERROR) <<
585 StringPrintf("IUrlMoniker::BindToStorage failed. Error: 0x%08X.", hr)
586 << std::endl << url();
587 DCHECK(hr == MK_E_SYNTAX);
588 }
589 }
590
591 DLOG_IF(ERROR, FAILED(hr))
592 << StringPrintf(L"StartAsyncDownload failed: 0x%08X", hr);
593
594 return hr;
595}
596
597void UrlmonUrlRequest::EndRequest() {
598 DLOG(INFO) << __FUNCTION__;
599 // Special case. If the last request was a redirect and the current OS
600 // error value is E_ACCESSDENIED, that means an unsafe redirect was attempted.
601 // In that case, correct the OS error value to be the more specific
602 // ERR_UNSAFE_REDIRECT error value.
603 if (!status_.is_success() && status_.os_error() == net::ERR_ACCESS_DENIED) {
604 int status = GetHttpResponseStatus();
605 if (status >= 300 && status < 400) {
606 redirect_status_ = status; // store the latest redirect status value.
607 status_.set_os_error(net::ERR_UNSAFE_REDIRECT);
608 }
609 }
[email protected]5778de6e2009-10-24 01:29:20610
[email protected]f7817822009-09-24 05:11:58611 OnResponseEnd(status_);
612
[email protected]5778de6e2009-10-24 01:29:20613 DCHECK(task_marshaller_ != NULL);
614
615 // Remove the request mapping and release the outstanding reference to us in
616 // the context of the UI thread.
617 task_marshaller_->PostTask(
618 FROM_HERE, NewRunnableMethod(this, &UrlmonUrlRequest::EndRequestInternal));
619}
620
621void UrlmonUrlRequest::EndRequestInternal() {
622 request_handler()->RemoveRequest(this);
623 // Release the outstanding reference in the context of the UI thread to
624 // ensure that our instance gets deleted in the same thread which created it.
625 Release();
[email protected]f7817822009-09-24 05:11:58626}
627
628int UrlmonUrlRequest::GetHttpResponseStatus() const {
629 if (binding_ == NULL) {
630 DLOG(WARNING) << "GetHttpResponseStatus - no binding_";
631 return 0;
632 }
633
634 int http_status = 0;
635
636 ScopedComPtr<IWinInetHttpInfo> info;
637 if (SUCCEEDED(info.QueryFrom(binding_))) {
638 char status[10] = {0};
639 DWORD buf_size = sizeof(status);
640 if (SUCCEEDED(info->QueryInfo(HTTP_QUERY_STATUS_CODE, status, &buf_size,
641 0, NULL))) {
642 http_status = StringToInt(status);
643 } else {
644 NOTREACHED() << "Failed to get HTTP status";
645 }
646 } else {
647 NOTREACHED() << "failed to get IWinInetHttpInfo from binding_";
648 }
649
650 return http_status;
651}
652
653//
654// UrlmonUrlRequest::Cache implementation.
655//
656
657size_t UrlmonUrlRequest::Cache::Size() {
658 size_t size = 0;
659 if (stream_) {
660 STATSTG cache_stat = {0};
661 stream_->Stat(&cache_stat, STATFLAG_NONAME);
662
663 DCHECK_EQ(0, cache_stat.cbSize.HighPart);
664 size = cache_stat.cbSize.LowPart;
665 }
666
667 return size;
668}
669
670size_t UrlmonUrlRequest::Cache::CurrentPos() {
671 size_t pos = 0;
672 if (stream_) {
673 ULARGE_INTEGER current_index = {0};
674 stream_->Seek(kZero, STREAM_SEEK_CUR, &current_index);
675
676 DCHECK_EQ(0, current_index.HighPart);
677 pos = current_index.LowPart;
678 }
679
680 return pos;
681}
682
683size_t UrlmonUrlRequest::Cache::SizeRemaining() {
684 size_t size = Size();
685 size_t pos = CurrentPos();
686 size_t size_remaining = 0;
687
688 if (size) {
689 DCHECK(pos <= size);
690 size_remaining = size - pos;
691 }
692 return size_remaining;
693}
694
695void UrlmonUrlRequest::Cache::Clear() {
696 if (!stream_) {
697 NOTREACHED();
698 return;
699 }
700
701 HRESULT hr = stream_->SetSize(kUnsignedZero);
702 DCHECK(SUCCEEDED(hr));
703}
704
705bool UrlmonUrlRequest::Cache::Read(IStream* dest, size_t size,
706 size_t* bytes_copied) {
707 if (!dest || !size) {
708 NOTREACHED();
709 return false;
710 }
711
712 // Copy the data and clear cache if there is no more data to copy.
713 ULARGE_INTEGER size_to_copy = {size, 0};
714 ULARGE_INTEGER size_written = {0};
715 stream_->CopyTo(dest, size_to_copy, NULL, &size_written);
716
717 if (size_written.LowPart && bytes_copied)
718 *bytes_copied = size_written.LowPart;
719
720 if (!SizeRemaining()) {
721 Clear();
722 stream_->Seek(kZero, STREAM_SEEK_SET, NULL);
723 }
724
725 return (size_written.LowPart != 0);
726}
727
728bool UrlmonUrlRequest::Cache::Append(IStream* source,
729 size_t* bytes_copied) {
730 if (!source) {
731 NOTREACHED();
732 return false;
733 }
734
735 size_t current_pos = CurrentPos();
736 stream_->Seek(kZero, STREAM_SEEK_END, NULL);
737
738 HRESULT hr = S_OK;
739 while (SUCCEEDED(hr)) {
740 DWORD chunk_read = 0; // NOLINT
741 hr = source->Read(read_buffer_, sizeof(read_buffer_), &chunk_read);
[email protected]5778de6e2009-10-24 01:29:20742
[email protected]f7817822009-09-24 05:11:58743 if (!chunk_read)
744 break;
745
746 DWORD chunk_written = 0; // NOLINT
747 stream_->Write(read_buffer_, chunk_read, &chunk_written);
748 DCHECK_EQ(chunk_read, chunk_written);
749
750 if (bytes_copied)
751 *bytes_copied += chunk_written;
752 }
753
754 LARGE_INTEGER last_read_position = {current_pos, 0};
755 stream_->Seek(last_read_position, STREAM_SEEK_SET, NULL);
756 return SUCCEEDED(hr);
757}
758
759bool UrlmonUrlRequest::Cache::Create() {
760 DCHECK(stream_ == NULL);
761 bool ret = SUCCEEDED(CreateStreamOnHGlobal(NULL, TRUE, stream_.Receive()));
762 DCHECK(ret && stream_);
763 return ret;
764}
765
766net::Error UrlmonUrlRequest::HresultToNetError(HRESULT hr) {
767 // Useful reference:
768 // http://msdn.microsoft.com/en-us/library/ms775145(VS.85).aspx
769
770 net::Error ret = net::ERR_UNEXPECTED;
771
772 switch (hr) {
773 case S_OK:
774 ret = net::OK;
775 break;
776
777 case MK_E_SYNTAX:
778 ret = net::ERR_INVALID_URL;
779 break;
780
781 case INET_E_CANNOT_CONNECT:
782 ret = net::ERR_CONNECTION_FAILED;
783 break;
784
785 case INET_E_DOWNLOAD_FAILURE:
786 case INET_E_CONNECTION_TIMEOUT:
787 case E_ABORT:
788 ret = net::ERR_CONNECTION_ABORTED;
789 break;
790
791 case INET_E_DATA_NOT_AVAILABLE:
792 ret = net::ERR_EMPTY_RESPONSE;
793 break;
794
795 case INET_E_RESOURCE_NOT_FOUND:
[email protected]00cc7402009-09-28 21:15:36796 // To behave more closely to the chrome network stack, we translate this
797 // error value as tunnel connection failed. This error value is tested
[email protected]f7817822009-09-24 05:11:58798 // in the ProxyTunnelRedirectTest and UnexpectedServerAuthTest tests.
799 ret = net::ERR_TUNNEL_CONNECTION_FAILED;
800 break;
801
802 case INET_E_INVALID_URL:
803 case INET_E_UNKNOWN_PROTOCOL:
804 case INET_E_REDIRECT_FAILED:
805 ret = net::ERR_INVALID_URL;
806 break;
807
808 case INET_E_INVALID_CERTIFICATE:
809 ret = net::ERR_CERT_INVALID;
810 break;
811
812 case E_ACCESSDENIED:
813 ret = net::ERR_ACCESS_DENIED;
814 break;
815
816 default:
817 DLOG(WARNING)
818 << StringPrintf("TODO: translate HRESULT 0x%08X to net::Error", hr);
819 break;
820 }
821 return ret;
822}