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