| Index: chrome_frame/urlmon_url_request.cc
|
| ===================================================================
|
| --- chrome_frame/urlmon_url_request.cc (revision 0)
|
| +++ chrome_frame/urlmon_url_request.cc (revision 0)
|
| @@ -0,0 +1,751 @@
|
| +// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "chrome_frame/urlmon_url_request.h"
|
| +
|
| +#include <wininet.h>
|
| +
|
| +#include "base/scoped_ptr.h"
|
| +#include "base/string_util.h"
|
| +#include "base/logging.h"
|
| +#include "chrome_frame/urlmon_upload_data_stream.h"
|
| +#include "net/http/http_util.h"
|
| +#include "net/http/http_response_headers.h"
|
| +
|
| +static const LARGE_INTEGER kZero = {0};
|
| +static const ULARGE_INTEGER kUnsignedZero = {0};
|
| +int UrlmonUrlRequest::instance_count_ = 0;
|
| +const char kXFrameOptionsHeader[] = "X-Frame-Options";
|
| +
|
| +UrlmonUrlRequest::UrlmonUrlRequest()
|
| + : pending_read_size_(0),
|
| + status_(URLRequestStatus::FAILED, net::ERR_FAILED),
|
| + thread_(PlatformThread::CurrentId()),
|
| + is_request_started_(false),
|
| + post_data_len_(0),
|
| + redirect_status_(0),
|
| + parent_window_(NULL) {
|
| + DLOG(INFO) << StringPrintf("Created request. Obj: %X", this)
|
| + << " Count: " << ++instance_count_;
|
| +}
|
| +
|
| +UrlmonUrlRequest::~UrlmonUrlRequest() {
|
| + DLOG(INFO) << StringPrintf("Deleted request. Obj: %X", this)
|
| + << " Count: " << --instance_count_;
|
| +}
|
| +
|
| +bool UrlmonUrlRequest::Start() {
|
| + DCHECK_EQ(PlatformThread::CurrentId(), thread_);
|
| +
|
| + status_.set_status(URLRequestStatus::IO_PENDING);
|
| + HRESULT hr = StartAsyncDownload();
|
| + if (FAILED(hr)) {
|
| + // Do not call EndRequest() here since it will attempt to free references
|
| + // that have not been established.
|
| + status_.set_os_error(HresultToNetError(hr));
|
| + status_.set_status(URLRequestStatus::FAILED);
|
| + DLOG(ERROR) << "StartAsyncDownload failed";
|
| + OnResponseEnd(status_);
|
| + return false;
|
| + }
|
| +
|
| + // Take a self reference to maintain COM lifetime. This will be released
|
| + // in EndRequest. Set a flag indicating that we have an additional
|
| + // reference here
|
| + is_request_started_ = true;
|
| + AddRef();
|
| + request_handler()->AddRequest(this);
|
| + return true;
|
| +}
|
| +
|
| +void UrlmonUrlRequest::Stop() {
|
| + DCHECK_EQ(PlatformThread::CurrentId(), thread_);
|
| +
|
| + if (binding_) {
|
| + binding_->Abort();
|
| + } else {
|
| + status_.set_status(URLRequestStatus::CANCELED);
|
| + status_.set_os_error(net::ERR_FAILED);
|
| + EndRequest();
|
| + }
|
| +}
|
| +
|
| +bool UrlmonUrlRequest::Read(int bytes_to_read) {
|
| + DCHECK_EQ(PlatformThread::CurrentId(), thread_);
|
| +
|
| + DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this);
|
| +
|
| + // Send cached data if available.
|
| + CComObjectStackEx<SendStream> send_stream;
|
| + send_stream.Initialize(this);
|
| + size_t bytes_copied = 0;
|
| + if (cached_data_.is_valid() && cached_data_.Read(&send_stream, bytes_to_read,
|
| + &bytes_copied)) {
|
| + DLOG(INFO) << StringPrintf("URL: %s Obj: %X - bytes read from cache: %d",
|
| + url().c_str(), this, bytes_copied);
|
| + return true;
|
| + }
|
| +
|
| + // if the request is finished or there's nothing more to read
|
| + // then end the request
|
| + if (!status_.is_io_pending() || !binding_) {
|
| + DLOG(INFO) << StringPrintf("URL: %s Obj: %X. Response finished. Status: %d",
|
| + url().c_str(), this, status_.status());
|
| + EndRequest();
|
| + return true;
|
| + }
|
| +
|
| + pending_read_size_ = bytes_to_read;
|
| + DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) <<
|
| + "- Read pending for: " << bytes_to_read;
|
| + return true;
|
| +}
|
| +
|
| +STDMETHODIMP UrlmonUrlRequest::OnStartBinding(
|
| + DWORD reserved, IBinding *binding) {
|
| + binding_ = binding;
|
| + return S_OK;
|
| +}
|
| +
|
| +STDMETHODIMP UrlmonUrlRequest::GetPriority(LONG *priority) {
|
| + if (!priority)
|
| + return E_POINTER;
|
| + *priority = THREAD_PRIORITY_NORMAL;
|
| + return S_OK;
|
| +}
|
| +
|
| +STDMETHODIMP UrlmonUrlRequest::OnLowResource(DWORD reserved) {
|
| + return S_OK;
|
| +}
|
| +
|
| +STDMETHODIMP UrlmonUrlRequest::OnProgress(ULONG progress, ULONG max_progress,
|
| + ULONG status_code, LPCWSTR status_text) {
|
| + switch (status_code) {
|
| + case BINDSTATUS_REDIRECTING:
|
| + DCHECK(status_text != NULL);
|
| + DLOG(INFO) << "URL: " << url() << " redirected to "
|
| + << status_text;
|
| + redirect_url_ = status_text;
|
| + // Fetch the redirect status as they aren't all equal (307 in particular
|
| + // retains the HTTP request verb).
|
| + redirect_status_ = GetHttpResponseStatus();
|
| + break;
|
| +
|
| + default:
|
| + DLOG(INFO) << " Obj: " << std::hex << this << " OnProgress(" << url()
|
| + << StringPrintf(L") code: %i status: %ls", status_code, status_text);
|
| + break;
|
| + }
|
| +
|
| + return S_OK;
|
| +}
|
| +
|
| +STDMETHODIMP UrlmonUrlRequest::OnStopBinding(HRESULT result, LPCWSTR error) {
|
| + DCHECK_EQ(PlatformThread::CurrentId(), thread_);
|
| +
|
| + DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) <<
|
| + " - Request stopped, Result: " << std::hex << result <<
|
| + " Status: " << status_.status();
|
| + if (FAILED(result)) {
|
| + status_.set_status(URLRequestStatus::FAILED);
|
| + status_.set_os_error(HresultToNetError(result));
|
| + EndRequest();
|
| + } else {
|
| + status_.set_status(URLRequestStatus::SUCCESS);
|
| + status_.set_os_error(0);
|
| + }
|
| +
|
| + // Release these variables after reporting EndRequest since we might need to
|
| + // access their state.
|
| + binding_ = NULL;
|
| + bind_context_ = NULL;
|
| +
|
| + return S_OK;
|
| +}
|
| +
|
| +STDMETHODIMP UrlmonUrlRequest::GetBindInfo(DWORD* bind_flags,
|
| + BINDINFO *bind_info) {
|
| + DCHECK_EQ(PlatformThread::CurrentId(), thread_);
|
| +
|
| + if ((bind_info == NULL) || (bind_info->cbSize == 0) || (bind_flags == NULL))
|
| + return E_INVALIDARG;
|
| +
|
| + *bind_flags = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA;
|
| + if (LowerCaseEqualsASCII(method(), "get")) {
|
| + bind_info->dwBindVerb = BINDVERB_GET;
|
| + } else if (LowerCaseEqualsASCII(method(), "post")) {
|
| + bind_info->dwBindVerb = BINDVERB_POST;
|
| +
|
| + // Bypass caching proxies on POSTs and avoid writing responses to POST
|
| + // requests to the browser's cache.
|
| + *bind_flags |= BINDF_GETNEWESTVERSION | BINDF_NOWRITECACHE |
|
| + BINDF_PRAGMA_NO_CACHE;
|
| +
|
| + // Initialize the STGMEDIUM.
|
| + memset(&bind_info->stgmedData, 0, sizeof(STGMEDIUM));
|
| + bind_info->grfBindInfoF = 0;
|
| + bind_info->szCustomVerb = NULL;
|
| +
|
| + scoped_refptr<net::UploadData> upload_data(upload_data());
|
| + post_data_len_ = upload_data.get() ? upload_data->GetContentLength() : 0;
|
| + if (post_data_len_) {
|
| + DLOG(INFO) << " Obj: " << std::hex << this << " POST request with "
|
| + << Int64ToString(post_data_len_) << " bytes";
|
| + CComObject<UrlmonUploadDataStream>* upload_stream = NULL;
|
| + HRESULT hr =
|
| + CComObject<UrlmonUploadDataStream>::CreateInstance(&upload_stream);
|
| + if (FAILED(hr)) {
|
| + NOTREACHED();
|
| + return hr;
|
| + }
|
| + upload_stream->Initialize(upload_data.get());
|
| +
|
| + // Fill the STGMEDIUM with the data to post
|
| + bind_info->stgmedData.tymed = TYMED_ISTREAM;
|
| + bind_info->stgmedData.pstm = static_cast<IStream*>(upload_stream);
|
| + bind_info->stgmedData.pstm->AddRef();
|
| + } else {
|
| + DLOG(INFO) << " Obj: " << std::hex << this
|
| + << "POST request with no data!";
|
| + }
|
| + } else {
|
| + NOTREACHED() << "Unknown HTTP method.";
|
| + status_.set_status(URLRequestStatus::FAILED);
|
| + status_.set_os_error(net::ERR_INVALID_URL);
|
| + EndRequest();
|
| + return E_FAIL;
|
| + }
|
| +
|
| + return S_OK;
|
| +}
|
| +
|
| +STDMETHODIMP UrlmonUrlRequest::OnDataAvailable(DWORD flags, DWORD size,
|
| + FORMATETC* formatetc,
|
| + STGMEDIUM* storage) {
|
| + DCHECK_EQ(PlatformThread::CurrentId(), thread_);
|
| + DLOG(INFO) << StringPrintf("URL: %s Obj: %X - Bytes available: %d",
|
| + url().c_str(), this, size);
|
| +
|
| + if (!storage || (storage->tymed != TYMED_ISTREAM)) {
|
| + NOTREACHED();
|
| + return E_INVALIDARG;
|
| + }
|
| +
|
| + IStream* read_stream = storage->pstm;
|
| + if (!read_stream) {
|
| + NOTREACHED();
|
| + return E_UNEXPECTED;
|
| + }
|
| +
|
| + HRESULT hr = S_OK;
|
| + if (BSCF_FIRSTDATANOTIFICATION & flags) {
|
| + DCHECK(!cached_data_.is_valid());
|
| + cached_data_.Create();
|
| + }
|
| +
|
| + // Always read data into cache. We have to read all the data here at this
|
| + // time or it won't be available later. Since the size of the data could
|
| + // be more than pending read size, it's not straightforward (or might even
|
| + // be impossible) to implement a true data pull model.
|
| + size_t bytes_available = 0;
|
| + cached_data_.Append(read_stream, &bytes_available);
|
| + DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) <<
|
| + " - Bytes read into cache: " << bytes_available;
|
| +
|
| + if (pending_read_size_) {
|
| + CComObjectStackEx<SendStream> send_stream;
|
| + send_stream.Initialize(this);
|
| + cached_data_.Read(&send_stream, pending_read_size_, &pending_read_size_);
|
| + DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) <<
|
| + " - size read: " << pending_read_size_;
|
| + pending_read_size_ = 0;
|
| + } else {
|
| + DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) <<
|
| + " - waiting for remote read";
|
| + }
|
| +
|
| + if (BSCF_LASTDATANOTIFICATION & flags) {
|
| + status_.set_status(URLRequestStatus::SUCCESS);
|
| + DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) <<
|
| + " - end of data.";
|
| + }
|
| +
|
| + return S_OK;
|
| +}
|
| +
|
| +STDMETHODIMP UrlmonUrlRequest::OnObjectAvailable(REFIID iid, IUnknown *object) {
|
| + // We are calling BindToStorage on the moniker we should always get called
|
| + // back on OnDataAvailable and should never get OnObjectAvailable
|
| + NOTREACHED();
|
| + return E_NOTIMPL;
|
| +}
|
| +
|
| +STDMETHODIMP UrlmonUrlRequest::BeginningTransaction(const wchar_t* url,
|
| + const wchar_t* current_headers, DWORD reserved,
|
| + wchar_t** additional_headers) {
|
| + DCHECK_EQ(PlatformThread::CurrentId(), thread_);
|
| + if (!additional_headers) {
|
| + NOTREACHED();
|
| + return E_POINTER;
|
| + }
|
| +
|
| + DLOG(INFO) << "URL: " << url << " Obj: " << std::hex << this <<
|
| + " - Request headers: \n" << current_headers;
|
| +
|
| + HRESULT hr = S_OK;
|
| +
|
| + std::string new_headers;
|
| + if (post_data_len_ > 0) {
|
| + // Tack on the Content-Length header since when using an IStream type
|
| + // STGMEDIUM, it looks like it doesn't get set for us :(
|
| + new_headers = StringPrintf("Content-Length: %s\r\n",
|
| + Int64ToString(post_data_len_).c_str());
|
| + }
|
| +
|
| + if (!extra_headers().empty()) {
|
| + // TODO(robertshield): We may need to sanitize headers on POST here.
|
| + new_headers += extra_headers();
|
| + }
|
| +
|
| + if (!referrer().empty()) {
|
| + // Referrer is famously misspelled in HTTP:
|
| + new_headers += StringPrintf("Referer: %s\r\n", referrer().c_str());
|
| + }
|
| +
|
| + if (!new_headers.empty()) {
|
| + *additional_headers = reinterpret_cast<wchar_t*>(
|
| + CoTaskMemAlloc((new_headers.size() + 1) * sizeof(wchar_t)));
|
| +
|
| + if (*additional_headers == NULL) {
|
| + NOTREACHED();
|
| + hr = E_OUTOFMEMORY;
|
| + } else {
|
| + lstrcpynW(*additional_headers, ASCIIToWide(new_headers).c_str(),
|
| + new_headers.size());
|
| + }
|
| + }
|
| +
|
| + return hr;
|
| +}
|
| +
|
| +STDMETHODIMP UrlmonUrlRequest::OnResponse(DWORD dwResponseCode,
|
| + const wchar_t* response_headers, const wchar_t* request_headers,
|
| + wchar_t** additional_headers) {
|
| + DCHECK_EQ(PlatformThread::CurrentId(), thread_);
|
| +
|
| + std::string raw_headers = WideToUTF8(response_headers);
|
| +
|
| + // Security check for frame busting headers. We don't honor the headers
|
| + // as-such, but instead simply kill requests which we've been asked to
|
| + // look for. This puts the onus on the user of the UrlRequest to specify
|
| + // whether or not requests should be inspected. For ActiveDocuments, the
|
| + // answer is "no", since WebKit's detection/handling is sufficient and since
|
| + // ActiveDocuments cannot be hosted as iframes. For NPAPI and ActiveX
|
| + // documents, the Initialize() function of the PluginUrlRequest object
|
| + // allows them to specify how they'd like requests handled. Both should
|
| + // set enable_frame_busting_ to true to avoid CSRF attacks.
|
| + // Should WebKit's handling of this ever change, we will need to re-visit
|
| + // how and when frames are killed to better mirror a policy which may
|
| + // do something other than kill the sub-document outright.
|
| +
|
| + // NOTE(slightlyoff): We don't use net::HttpResponseHeaders here because
|
| + // of lingering ICU/base_noicu issues.
|
| + if (frame_busting_enabled_ &&
|
| + net::HttpUtil::HasHeader(raw_headers, kXFrameOptionsHeader)) {
|
| + DLOG(ERROR) << "X-Frame-Options header detected, navigation canceled";
|
| + return E_FAIL;
|
| + }
|
| +
|
| + std::wstring url_for_persistent_cookies =
|
| + redirect_url_.empty() ? UTF8ToWide(url()) : redirect_url_;
|
| +
|
| + std::string persistent_cookies;
|
| +
|
| + DWORD cookie_size = 0; // NOLINT
|
| + InternetGetCookie(url_for_persistent_cookies.c_str(), NULL, NULL,
|
| + &cookie_size);
|
| + if (cookie_size) {
|
| + scoped_ptr<wchar_t> cookies(new wchar_t[cookie_size + 1]);
|
| + if (!InternetGetCookie(url_for_persistent_cookies.c_str(), NULL,
|
| + cookies.get(), &cookie_size)) {
|
| + NOTREACHED() << "InternetGetCookie failed. Error: " << GetLastError();
|
| + } else {
|
| + persistent_cookies = WideToUTF8(cookies.get());
|
| + }
|
| + }
|
| +
|
| + OnResponseStarted("",
|
| + raw_headers.c_str(),
|
| + 0,
|
| + base::Time(),
|
| + persistent_cookies,
|
| + redirect_url_.empty() ? std::string() :
|
| + WideToUTF8(redirect_url_),
|
| + redirect_status_);
|
| +
|
| + return S_OK;
|
| +}
|
| +
|
| +STDMETHODIMP UrlmonUrlRequest::GetWindow(const GUID& guid_reason,
|
| + HWND* parent_window) {
|
| + if (!parent_window) {
|
| + return E_INVALIDARG;
|
| + }
|
| +
|
| +#ifndef NDEBUG
|
| + wchar_t guid[40] = {0};
|
| + ::StringFromGUID2(guid_reason, guid, arraysize(guid));
|
| +
|
| + DLOG(INFO) << " Obj: " << std::hex << this << " GetWindow: " <<
|
| + (guid_reason == IID_IAuthenticate ? L" - IAuthenticate" :
|
| + (guid_reason == IID_IHttpSecurity ? L"IHttpSecurity" :
|
| + (guid_reason == IID_IWindowForBindingUI ? L"IWindowForBindingUI" :
|
| + guid)));
|
| +#endif
|
| +
|
| + // TODO(iyengar): This hits when running the URL request tests.
|
| + DLOG_IF(ERROR, !::IsWindow(parent_window_))
|
| + << "UrlmonUrlRequest::GetWindow - no window!";
|
| + *parent_window = parent_window_;
|
| + return S_OK;
|
| +}
|
| +
|
| +STDMETHODIMP UrlmonUrlRequest::Authenticate(HWND* parent_window,
|
| + LPWSTR* user_name,
|
| + LPWSTR* password) {
|
| + if (!parent_window) {
|
| + return E_INVALIDARG;
|
| + }
|
| +
|
| + DCHECK(::IsWindow(parent_window_));
|
| + *parent_window = parent_window_;
|
| + return S_OK;
|
| +}
|
| +
|
| +STDMETHODIMP UrlmonUrlRequest::OnSecurityProblem(DWORD problem) {
|
| + // Urlmon notifies the client of authentication problems, certificate
|
| + // errors, etc by querying the object implementing the IBindStatusCallback
|
| + // interface for the IHttpSecurity interface. If this interface is not
|
| + // implemented then Urlmon checks for the problem codes defined below
|
| + // and performs actions as defined below:-
|
| + // It invokes the ReportProgress method of the protocol sink with
|
| + // these problem codes and eventually invokes the ReportResult method
|
| + // on the protocol sink which ends up in a call to the OnStopBinding
|
| + // method of the IBindStatusCallBack interface.
|
| +
|
| + // MSHTML's implementation of the IBindStatusCallback interface does not
|
| + // implement the IHttpSecurity interface. However it handles the
|
| + // OnStopBinding call with a HRESULT of 0x800c0019 and navigates to
|
| + // an interstitial page which presents the user with a choice of whether
|
| + // to abort the navigation.
|
| +
|
| + // In our OnStopBinding implementation we stop the navigation and inform
|
| + // Chrome about the result. Ideally Chrome should behave in a manner similar
|
| + // to IE, i.e. display the SSL error interstitial page and if the user
|
| + // decides to proceed anyway we would turn off SSL warnings for that
|
| + // particular navigation and allow IE to download the content.
|
| + // We would need to return the certificate information to Chrome for display
|
| + // purposes. Currently we only return a dummy certificate to Chrome.
|
| + // At this point we decided that it is a lot of work at this point and
|
| + // decided to go with the easier option of implementing the IHttpSecurity
|
| + // interface and replicating the checks performed by Urlmon. This
|
| + // causes Urlmon to display a dialog box on the same lines as IE6.
|
| + DLOG(INFO) << __FUNCTION__ << " Security problem : " << problem;
|
| +
|
| + HRESULT hr = E_ABORT;
|
| +
|
| + switch (problem) {
|
| + case ERROR_INTERNET_SEC_CERT_REV_FAILED: {
|
| + hr = RPC_E_RETRY;
|
| + break;
|
| + }
|
| +
|
| + case ERROR_INTERNET_SEC_CERT_DATE_INVALID:
|
| + case ERROR_INTERNET_SEC_CERT_CN_INVALID:
|
| + case ERROR_INTERNET_INVALID_CA: {
|
| + hr = S_FALSE;
|
| + break;
|
| + }
|
| +
|
| + default: {
|
| + NOTREACHED() << "Unhandled security problem : " << problem;
|
| + break;
|
| + }
|
| + }
|
| + return hr;
|
| +}
|
| +
|
| +HRESULT UrlmonUrlRequest::ConnectToExistingMoniker(IMoniker* moniker,
|
| + IBindCtx* context,
|
| + const std::wstring& url) {
|
| + if (!moniker || url.empty()) {
|
| + NOTREACHED() << "Invalid arguments";
|
| + return E_INVALIDARG;
|
| + }
|
| +
|
| + DCHECK(moniker_.get() == NULL);
|
| + DCHECK(bind_context_.get() == NULL);
|
| +
|
| + moniker_ = moniker;
|
| + bind_context_ = context;
|
| + set_url(WideToUTF8(url));
|
| + return S_OK;
|
| +}
|
| +
|
| +HRESULT UrlmonUrlRequest::StartAsyncDownload() {
|
| + HRESULT hr = E_FAIL;
|
| + if (moniker_.get() == NULL) {
|
| + std::wstring wide_url = UTF8ToWide(url());
|
| + hr = CreateURLMonikerEx(NULL, wide_url.c_str(), moniker_.Receive(),
|
| + URL_MK_UNIFORM);
|
| + if (FAILED(hr)) {
|
| + NOTREACHED() << "CreateURLMonikerEx failed. Error: " << hr;
|
| + } else {
|
| + hr = CreateAsyncBindCtx(0, this, NULL, bind_context_.Receive());
|
| + DCHECK(SUCCEEDED(hr)) << "CreateAsyncBindCtx failed. Error: " << hr;
|
| + }
|
| + } else {
|
| + DCHECK(bind_context_.get() != NULL);
|
| + hr = RegisterBindStatusCallback(bind_context_, this, NULL, 0);
|
| + }
|
| +
|
| + if (SUCCEEDED(hr)) {
|
| + ScopedComPtr<IStream> stream;
|
| + hr = moniker_->BindToStorage(bind_context_, NULL, __uuidof(IStream),
|
| + reinterpret_cast<void**>(stream.Receive()));
|
| + if (FAILED(hr)) {
|
| + // TODO(joshia): Look into. This currently fails for:
|
| + // http://user2:secret@localhost:1337/auth-basic?set-cookie-if-challenged
|
| + // when running the UrlRequest unit tests.
|
| + DLOG(ERROR) <<
|
| + StringPrintf("IUrlMoniker::BindToStorage failed. Error: 0x%08X.", hr)
|
| + << std::endl << url();
|
| + DCHECK(hr == MK_E_SYNTAX);
|
| + }
|
| + }
|
| +
|
| + DLOG_IF(ERROR, FAILED(hr))
|
| + << StringPrintf(L"StartAsyncDownload failed: 0x%08X", hr);
|
| +
|
| + return hr;
|
| +}
|
| +
|
| +void UrlmonUrlRequest::EndRequest() {
|
| + DLOG(INFO) << __FUNCTION__;
|
| + // Special case. If the last request was a redirect and the current OS
|
| + // error value is E_ACCESSDENIED, that means an unsafe redirect was attempted.
|
| + // In that case, correct the OS error value to be the more specific
|
| + // ERR_UNSAFE_REDIRECT error value.
|
| + if (!status_.is_success() && status_.os_error() == net::ERR_ACCESS_DENIED) {
|
| + int status = GetHttpResponseStatus();
|
| + if (status >= 300 && status < 400) {
|
| + redirect_status_ = status; // store the latest redirect status value.
|
| + status_.set_os_error(net::ERR_UNSAFE_REDIRECT);
|
| + }
|
| + }
|
| + request_handler()->RemoveRequest(this);
|
| + OnResponseEnd(status_);
|
| +
|
| + // If the request was started then we must have an additional reference on the
|
| + // request.
|
| + if (is_request_started_) {
|
| + is_request_started_ = false;
|
| + Release();
|
| + }
|
| +}
|
| +
|
| +int UrlmonUrlRequest::GetHttpResponseStatus() const {
|
| + if (binding_ == NULL) {
|
| + DLOG(WARNING) << "GetHttpResponseStatus - no binding_";
|
| + return 0;
|
| + }
|
| +
|
| + int http_status = 0;
|
| +
|
| + ScopedComPtr<IWinInetHttpInfo> info;
|
| + if (SUCCEEDED(info.QueryFrom(binding_))) {
|
| + char status[10] = {0};
|
| + DWORD buf_size = sizeof(status);
|
| + if (SUCCEEDED(info->QueryInfo(HTTP_QUERY_STATUS_CODE, status, &buf_size,
|
| + 0, NULL))) {
|
| + http_status = StringToInt(status);
|
| + } else {
|
| + NOTREACHED() << "Failed to get HTTP status";
|
| + }
|
| + } else {
|
| + NOTREACHED() << "failed to get IWinInetHttpInfo from binding_";
|
| + }
|
| +
|
| + return http_status;
|
| +}
|
| +
|
| +//
|
| +// UrlmonUrlRequest::Cache implementation.
|
| +//
|
| +
|
| +size_t UrlmonUrlRequest::Cache::Size() {
|
| + size_t size = 0;
|
| + if (stream_) {
|
| + STATSTG cache_stat = {0};
|
| + stream_->Stat(&cache_stat, STATFLAG_NONAME);
|
| +
|
| + DCHECK_EQ(0, cache_stat.cbSize.HighPart);
|
| + size = cache_stat.cbSize.LowPart;
|
| + }
|
| +
|
| + return size;
|
| +}
|
| +
|
| +size_t UrlmonUrlRequest::Cache::CurrentPos() {
|
| + size_t pos = 0;
|
| + if (stream_) {
|
| + ULARGE_INTEGER current_index = {0};
|
| + stream_->Seek(kZero, STREAM_SEEK_CUR, ¤t_index);
|
| +
|
| + DCHECK_EQ(0, current_index.HighPart);
|
| + pos = current_index.LowPart;
|
| + }
|
| +
|
| + return pos;
|
| +}
|
| +
|
| +size_t UrlmonUrlRequest::Cache::SizeRemaining() {
|
| + size_t size = Size();
|
| + size_t pos = CurrentPos();
|
| + size_t size_remaining = 0;
|
| +
|
| + if (size) {
|
| + DCHECK(pos <= size);
|
| + size_remaining = size - pos;
|
| + }
|
| + return size_remaining;
|
| +}
|
| +
|
| +void UrlmonUrlRequest::Cache::Clear() {
|
| + if (!stream_) {
|
| + NOTREACHED();
|
| + return;
|
| + }
|
| +
|
| + HRESULT hr = stream_->SetSize(kUnsignedZero);
|
| + DCHECK(SUCCEEDED(hr));
|
| +}
|
| +
|
| +bool UrlmonUrlRequest::Cache::Read(IStream* dest, size_t size,
|
| + size_t* bytes_copied) {
|
| + if (!dest || !size) {
|
| + NOTREACHED();
|
| + return false;
|
| + }
|
| +
|
| + // Copy the data and clear cache if there is no more data to copy.
|
| + ULARGE_INTEGER size_to_copy = {size, 0};
|
| + ULARGE_INTEGER size_written = {0};
|
| + stream_->CopyTo(dest, size_to_copy, NULL, &size_written);
|
| +
|
| + if (size_written.LowPart && bytes_copied)
|
| + *bytes_copied = size_written.LowPart;
|
| +
|
| + if (!SizeRemaining()) {
|
| + Clear();
|
| + stream_->Seek(kZero, STREAM_SEEK_SET, NULL);
|
| + }
|
| +
|
| + return (size_written.LowPart != 0);
|
| +}
|
| +
|
| +bool UrlmonUrlRequest::Cache::Append(IStream* source,
|
| + size_t* bytes_copied) {
|
| + if (!source) {
|
| + NOTREACHED();
|
| + return false;
|
| + }
|
| +
|
| + size_t current_pos = CurrentPos();
|
| + stream_->Seek(kZero, STREAM_SEEK_END, NULL);
|
| +
|
| + HRESULT hr = S_OK;
|
| + while (SUCCEEDED(hr)) {
|
| + DWORD chunk_read = 0; // NOLINT
|
| + hr = source->Read(read_buffer_, sizeof(read_buffer_), &chunk_read);
|
| + if (!chunk_read)
|
| + break;
|
| +
|
| + DWORD chunk_written = 0; // NOLINT
|
| + stream_->Write(read_buffer_, chunk_read, &chunk_written);
|
| + DCHECK_EQ(chunk_read, chunk_written);
|
| +
|
| + if (bytes_copied)
|
| + *bytes_copied += chunk_written;
|
| + }
|
| +
|
| + LARGE_INTEGER last_read_position = {current_pos, 0};
|
| + stream_->Seek(last_read_position, STREAM_SEEK_SET, NULL);
|
| + return SUCCEEDED(hr);
|
| +}
|
| +
|
| +bool UrlmonUrlRequest::Cache::Create() {
|
| + DCHECK(stream_ == NULL);
|
| + bool ret = SUCCEEDED(CreateStreamOnHGlobal(NULL, TRUE, stream_.Receive()));
|
| + DCHECK(ret && stream_);
|
| + return ret;
|
| +}
|
| +
|
| +net::Error UrlmonUrlRequest::HresultToNetError(HRESULT hr) {
|
| + // Useful reference:
|
| + // http://msdn.microsoft.com/en-us/library/ms775145(VS.85).aspx
|
| +
|
| + net::Error ret = net::ERR_UNEXPECTED;
|
| +
|
| + switch (hr) {
|
| + case S_OK:
|
| + ret = net::OK;
|
| + break;
|
| +
|
| + case MK_E_SYNTAX:
|
| + ret = net::ERR_INVALID_URL;
|
| + break;
|
| +
|
| + case INET_E_CANNOT_CONNECT:
|
| + ret = net::ERR_CONNECTION_FAILED;
|
| + break;
|
| +
|
| + case INET_E_DOWNLOAD_FAILURE:
|
| + case INET_E_CONNECTION_TIMEOUT:
|
| + case E_ABORT:
|
| + ret = net::ERR_CONNECTION_ABORTED;
|
| + break;
|
| +
|
| + case INET_E_DATA_NOT_AVAILABLE:
|
| + ret = net::ERR_EMPTY_RESPONSE;
|
| + break;
|
| +
|
| + case INET_E_RESOURCE_NOT_FOUND:
|
| + // To behave more closely to the chrome network stack, we translate this
|
| + // error value as tunnel connection failed. This error value is tested
|
| + // in the ProxyTunnelRedirectTest and UnexpectedServerAuthTest tests.
|
| + ret = net::ERR_TUNNEL_CONNECTION_FAILED;
|
| + break;
|
| +
|
| + case INET_E_INVALID_URL:
|
| + case INET_E_UNKNOWN_PROTOCOL:
|
| + case INET_E_REDIRECT_FAILED:
|
| + ret = net::ERR_INVALID_URL;
|
| + break;
|
| +
|
| + case INET_E_INVALID_CERTIFICATE:
|
| + ret = net::ERR_CERT_INVALID;
|
| + break;
|
| +
|
| + case E_ACCESSDENIED:
|
| + ret = net::ERR_ACCESS_DENIED;
|
| + break;
|
| +
|
| + default:
|
| + DLOG(WARNING)
|
| + << StringPrintf("TODO: translate HRESULT 0x%08X to net::Error", hr);
|
| + break;
|
| + }
|
| + return ret;
|
| +}
|
|
|