diff options
Diffstat (limited to 'chrome_frame/urlmon_url_request.cc')
-rw-r--r-- | chrome_frame/urlmon_url_request.cc | 751 |
1 files changed, 751 insertions, 0 deletions
diff --git a/chrome_frame/urlmon_url_request.cc b/chrome_frame/urlmon_url_request.cc new file mode 100644 index 0000000..f51da418 --- /dev/null +++ b/chrome_frame/urlmon_url_request.cc @@ -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; +} |