
raw_scoped_refptr_mismatch_checker.h ref_counted.cc ref_counted.h ref_counted_memory.cc ref_counted_memory.h ref_counted_unittest.cc scoped_callback_factory.h scoped_comptr_win.h scoped_handle.h scoped_native_library.cc scoped_native_library.h scoped_native_library_unittest.cc scoped_nsobject.h scoped_open_process.h scoped_ptr.h scoped_ptr_unittest.cc scoped_temp_dir.cc scoped_temp_dir.h scoped_temp_dir_unittest.cc scoped_vector.h singleton.h singleton_objc.h singleton_unittest.cc linked_ptr.h linked_ptr_unittest.cc weak_ptr.cc weak_ptr.h weak_ptr_unittest.cc BUG=None TEST=Compile Review URL: http://codereview.chromium.org/6714032 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@79524 0039d316-1c4b-4281-b951-d872f2087c98
1267 lines
43 KiB
C++
1267 lines
43 KiB
C++
// Copyright (c) 2011 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 <urlmon.h>
|
|
|
|
#include "base/logging.h"
|
|
#include "base/memory/scoped_ptr.h"
|
|
#include "base/message_loop.h"
|
|
#include "base/string_number_conversions.h"
|
|
#include "base/stringprintf.h"
|
|
#include "base/threading/platform_thread.h"
|
|
#include "base/utf_string_conversions.h"
|
|
#include "chrome_frame/bind_context_info.h"
|
|
#include "chrome_frame/chrome_frame_activex_base.h"
|
|
#include "chrome_frame/extra_system_apis.h"
|
|
#include "chrome_frame/html_utils.h"
|
|
#include "chrome_frame/urlmon_url_request_private.h"
|
|
#include "chrome_frame/urlmon_upload_data_stream.h"
|
|
#include "chrome_frame/utils.h"
|
|
#include "chrome/common/automation_messages.h"
|
|
#include "net/base/load_flags.h"
|
|
#include "net/http/http_response_headers.h"
|
|
#include "net/http/http_util.h"
|
|
|
|
UrlmonUrlRequest::UrlmonUrlRequest()
|
|
: pending_read_size_(0),
|
|
headers_received_(false),
|
|
calling_delegate_(0),
|
|
thread_(NULL),
|
|
parent_window_(NULL),
|
|
privileged_mode_(false),
|
|
pending_(false),
|
|
is_expecting_download_(true),
|
|
cleanup_transaction_(false) {
|
|
DVLOG(1) << __FUNCTION__ << me();
|
|
}
|
|
|
|
UrlmonUrlRequest::~UrlmonUrlRequest() {
|
|
DVLOG(1) << __FUNCTION__ << me();
|
|
}
|
|
|
|
std::string UrlmonUrlRequest::me() const {
|
|
return base::StringPrintf(" id: %i Obj: %X ", id(), this);
|
|
}
|
|
|
|
bool UrlmonUrlRequest::Start() {
|
|
DVLOG(1) << __FUNCTION__ << me() << url();
|
|
DCHECK(thread_ == 0 || thread_ == base::PlatformThread::CurrentId());
|
|
thread_ = base::PlatformThread::CurrentId();
|
|
status_.Start();
|
|
// Initialize the net::HostPortPair structure from the url initially. We may
|
|
// not receive the ip address of the host if the request is satisfied from
|
|
// the cache.
|
|
socket_address_ = net::HostPortPair::FromURL(GURL(url()));
|
|
// The UrlmonUrlRequest instance can get destroyed in the context of
|
|
// StartAsyncDownload if BindToStorage finishes synchronously with an error.
|
|
// Grab a reference to protect against this.
|
|
scoped_refptr<UrlmonUrlRequest> ref(this);
|
|
HRESULT hr = StartAsyncDownload();
|
|
if (FAILED(hr) && status_.get_state() != UrlmonUrlRequest::Status::DONE) {
|
|
status_.Done();
|
|
status_.set_result(net::URLRequestStatus::FAILED, HresultToNetError(hr));
|
|
NotifyDelegateAndDie();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void UrlmonUrlRequest::Stop() {
|
|
DCHECK_EQ(thread_, base::PlatformThread::CurrentId());
|
|
DCHECK((status_.get_state() != Status::DONE) == (binding_ != NULL));
|
|
Status::State state = status_.get_state();
|
|
delegate_ = NULL;
|
|
|
|
// If DownloadInHost is already requested, we will quit soon anyway.
|
|
if (terminate_requested())
|
|
return;
|
|
|
|
switch (state) {
|
|
case Status::WORKING:
|
|
status_.Cancel();
|
|
if (binding_)
|
|
binding_->Abort();
|
|
break;
|
|
|
|
case Status::ABORTING:
|
|
status_.Cancel();
|
|
break;
|
|
|
|
case Status::DONE:
|
|
status_.Cancel();
|
|
NotifyDelegateAndDie();
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool UrlmonUrlRequest::Read(int bytes_to_read) {
|
|
DCHECK_EQ(thread_, base::PlatformThread::CurrentId());
|
|
DCHECK_GE(bytes_to_read, 0);
|
|
DCHECK_EQ(0, calling_delegate_);
|
|
DVLOG(1) << __FUNCTION__ << me();
|
|
|
|
is_expecting_download_ = false;
|
|
|
|
// Re-entrancy check. Thou shall not call Read() while process OnReadComplete!
|
|
DCHECK_EQ(0u, pending_read_size_);
|
|
if (pending_read_size_ != 0)
|
|
return false;
|
|
|
|
DCHECK((status_.get_state() != Status::DONE) == (binding_ != NULL));
|
|
if (status_.get_state() == Status::ABORTING)
|
|
return true;
|
|
|
|
// Send data if available.
|
|
size_t bytes_copied = 0;
|
|
if ((bytes_copied = SendDataToDelegate(bytes_to_read))) {
|
|
DVLOG(1) << __FUNCTION__ << me() << " bytes read: " << bytes_copied;
|
|
return true;
|
|
}
|
|
|
|
if (status_.get_state() == Status::WORKING) {
|
|
DVLOG(1) << __FUNCTION__ << me() << " pending: " << bytes_to_read;
|
|
pending_read_size_ = bytes_to_read;
|
|
} else {
|
|
DVLOG(1) << __FUNCTION__ << me() << " Response finished.";
|
|
NotifyDelegateAndDie();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
HRESULT UrlmonUrlRequest::InitPending(const GURL& url, IMoniker* moniker,
|
|
IBindCtx* bind_context,
|
|
bool enable_frame_busting,
|
|
bool privileged_mode,
|
|
HWND notification_window,
|
|
IStream* cache) {
|
|
DVLOG(1) << __FUNCTION__ << me() << url.spec();
|
|
DCHECK(bind_context_ == NULL);
|
|
DCHECK(moniker_ == NULL);
|
|
DCHECK(cache_ == NULL);
|
|
DCHECK(thread_ == 0 || thread_ == base::PlatformThread::CurrentId());
|
|
thread_ = base::PlatformThread::CurrentId();
|
|
bind_context_ = bind_context;
|
|
moniker_ = moniker;
|
|
enable_frame_busting_ = enable_frame_busting;
|
|
privileged_mode_ = privileged_mode;
|
|
parent_window_ = notification_window;
|
|
cache_ = cache;
|
|
set_url(url.spec());
|
|
set_pending(true);
|
|
|
|
// Request has already started and data is fetched. We will get the
|
|
// GetBindInfo call as per contract but the return values are
|
|
// ignored. So just set "get" as a method to make our GetBindInfo
|
|
// implementation happy.
|
|
method_ = "get";
|
|
return S_OK;
|
|
}
|
|
|
|
void UrlmonUrlRequest::TerminateBind(TerminateBindCallback* callback) {
|
|
DCHECK_EQ(thread_, base::PlatformThread::CurrentId());
|
|
DVLOG(1) << __FUNCTION__ << me();
|
|
cleanup_transaction_ = false;
|
|
if (status_.get_state() == Status::DONE) {
|
|
// Binding is stopped. Note result could be an error.
|
|
callback->Run(moniker_, bind_context_, upload_data_,
|
|
request_headers_.c_str());
|
|
delete callback;
|
|
} else {
|
|
// WORKING (ABORTING?). Save the callback.
|
|
// Now we will return INET_TERMINATE_BIND from ::OnDataAvailable() and in
|
|
// ::OnStopBinding will invoke the callback passing our moniker and
|
|
// bind context.
|
|
terminate_bind_callback_.reset(callback);
|
|
if (pending_data_) {
|
|
// For downloads to work correctly, we must induce a call to
|
|
// OnDataAvailable so that we can download INET_E_TERMINATED_BIND and
|
|
// get IE into the correct state.
|
|
// To accomplish this we read everything that's readily available in
|
|
// the current stream. Once we've reached the end of the stream we
|
|
// should get E_PENDING back and then later we'll get that call
|
|
// to OnDataAvailable.
|
|
std::string data;
|
|
ScopedComPtr<IStream> read_stream(pending_data_);
|
|
HRESULT hr;
|
|
while ((hr = ReadStream(read_stream, 0xffff, &data)) == S_OK) {
|
|
// Just drop the data.
|
|
}
|
|
DLOG_IF(WARNING, hr != E_PENDING) << __FUNCTION__ <<
|
|
base::StringPrintf(" expected E_PENDING but got 0x%08X", hr);
|
|
}
|
|
}
|
|
}
|
|
|
|
size_t UrlmonUrlRequest::SendDataToDelegate(size_t bytes_to_read) {
|
|
DCHECK_EQ(thread_, base::PlatformThread::CurrentId());
|
|
DCHECK_NE(id(), -1);
|
|
DCHECK_GT(bytes_to_read, 0U);
|
|
size_t bytes_copied = 0;
|
|
if (delegate_) {
|
|
std::string read_data;
|
|
if (cache_) {
|
|
HRESULT hr = ReadStream(cache_, bytes_to_read, &read_data);
|
|
if (hr == S_FALSE || read_data.length() < bytes_to_read) {
|
|
DVLOG(1) << __FUNCTION__ << me() << "all cached data read";
|
|
cache_.Release();
|
|
}
|
|
}
|
|
|
|
if (read_data.empty() && pending_data_) {
|
|
size_t pending_data_read_save = pending_read_size_;
|
|
pending_read_size_ = 0;
|
|
|
|
// AddRef the stream while we call Read to avoid a potential issue
|
|
// where we can get a call to OnDataAvailable while inside Read and
|
|
// in our OnDataAvailable call, we can release the stream object
|
|
// while still using it.
|
|
ScopedComPtr<IStream> pending(pending_data_);
|
|
HRESULT hr = ReadStream(pending, bytes_to_read, &read_data);
|
|
if (read_data.empty())
|
|
pending_read_size_ = pending_data_read_save;
|
|
// If we received S_FALSE it indicates that there is no more data in the
|
|
// stream. Clear it to ensure that OnStopBinding correctly sends over the
|
|
// response end notification to chrome.
|
|
if (hr == S_FALSE)
|
|
pending_data_.Release();
|
|
}
|
|
|
|
bytes_copied = read_data.length();
|
|
|
|
if (bytes_copied) {
|
|
++calling_delegate_;
|
|
DCHECK_NE(id(), -1);
|
|
// The delegate can go away in the middle of ReadStream
|
|
if (delegate_)
|
|
delegate_->OnReadComplete(id(), read_data);
|
|
--calling_delegate_;
|
|
}
|
|
} else {
|
|
DLOG(ERROR) << __FUNCTION__ << me() << "no delegate";
|
|
}
|
|
|
|
return bytes_copied;
|
|
}
|
|
|
|
STDMETHODIMP UrlmonUrlRequest::OnStartBinding(DWORD reserved,
|
|
IBinding* binding) {
|
|
DCHECK_EQ(thread_, base::PlatformThread::CurrentId());
|
|
binding_ = binding;
|
|
if (pending_) {
|
|
response_headers_ = GetHttpHeadersFromBinding(binding_);
|
|
DCHECK(!response_headers_.empty());
|
|
}
|
|
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) {
|
|
DCHECK_EQ(thread_, base::PlatformThread::CurrentId());
|
|
|
|
if (status_.get_state() != Status::WORKING)
|
|
return S_OK;
|
|
|
|
// Ignore any notifications received while we are in the pending state
|
|
// waiting for the request to be initiated by Chrome.
|
|
if (pending_ && status_code != BINDSTATUS_REDIRECTING)
|
|
return S_OK;
|
|
|
|
if (!delegate_) {
|
|
DVLOG(1) << "Invalid delegate";
|
|
return S_OK;
|
|
}
|
|
|
|
switch (status_code) {
|
|
case BINDSTATUS_CONNECTING: {
|
|
if (status_text) {
|
|
socket_address_.set_host(WideToUTF8(status_text));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case BINDSTATUS_REDIRECTING: {
|
|
// If we receive a redirect for the initial pending request initiated
|
|
// when our document loads we should stash it away and inform Chrome
|
|
// accordingly when it requests data for the original URL.
|
|
ScopedComPtr<BindContextInfo> info;
|
|
BindContextInfo::FromBindContext(bind_context_, info.Receive());
|
|
DCHECK(info);
|
|
GURL previously_redirected(info ? info->GetUrl() : std::wstring());
|
|
if (GURL(status_text) != previously_redirected) {
|
|
DVLOG(1) << __FUNCTION__ << me() << "redirect from " << url()
|
|
<< " to " << status_text;
|
|
// Fetch the redirect status as they aren't all equal (307 in particular
|
|
// retains the HTTP request verb).
|
|
int http_code = GetHttpResponseStatusFromBinding(binding_);
|
|
status_.SetRedirected(http_code, WideToUTF8(status_text));
|
|
// Abort. We will inform Chrome in OnStopBinding callback.
|
|
binding_->Abort();
|
|
return E_ABORT;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case BINDSTATUS_COOKIE_SENT:
|
|
delegate_->AddPrivacyDataForUrl(url(), "", COOKIEACTION_READ);
|
|
break;
|
|
|
|
case BINDSTATUS_COOKIE_SUPPRESSED:
|
|
delegate_->AddPrivacyDataForUrl(url(), "", COOKIEACTION_SUPPRESS);
|
|
break;
|
|
|
|
case BINDSTATUS_COOKIE_STATE_ACCEPT:
|
|
delegate_->AddPrivacyDataForUrl(url(), "", COOKIEACTION_ACCEPT);
|
|
break;
|
|
|
|
case BINDSTATUS_COOKIE_STATE_REJECT:
|
|
delegate_->AddPrivacyDataForUrl(url(), "", COOKIEACTION_REJECT);
|
|
break;
|
|
|
|
case BINDSTATUS_COOKIE_STATE_LEASH:
|
|
delegate_->AddPrivacyDataForUrl(url(), "", COOKIEACTION_LEASH);
|
|
break;
|
|
|
|
case BINDSTATUS_COOKIE_STATE_DOWNGRADE:
|
|
delegate_->AddPrivacyDataForUrl(url(), "", COOKIEACTION_DOWNGRADE);
|
|
break;
|
|
|
|
case BINDSTATUS_COOKIE_STATE_UNKNOWN:
|
|
NOTREACHED() << L"Unknown cookie state received";
|
|
break;
|
|
|
|
default:
|
|
DVLOG(1) << __FUNCTION__ << me()
|
|
<< base::StringPrintf(L"code: %i status: %ls", status_code,
|
|
status_text);
|
|
break;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP UrlmonUrlRequest::OnStopBinding(HRESULT result, LPCWSTR error) {
|
|
DCHECK_EQ(thread_, base::PlatformThread::CurrentId());
|
|
DVLOG(1) << __FUNCTION__ << me()
|
|
<< "- Request stopped, Result: " << std::hex << result;
|
|
DCHECK(status_.get_state() == Status::WORKING ||
|
|
status_.get_state() == Status::ABORTING);
|
|
|
|
Status::State state = status_.get_state();
|
|
|
|
// Mark we a are done.
|
|
status_.Done();
|
|
|
|
if (result == INET_E_TERMINATED_BIND) {
|
|
if (terminate_requested()) {
|
|
terminate_bind_callback_->Run(moniker_, bind_context_, upload_data_,
|
|
request_headers_.c_str());
|
|
} else {
|
|
cleanup_transaction_ = true;
|
|
}
|
|
// We may have returned INET_E_TERMINATED_BIND from OnDataAvailable.
|
|
result = S_OK;
|
|
}
|
|
|
|
if (state == Status::WORKING) {
|
|
status_.set_result(result);
|
|
|
|
// 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 (result == E_ACCESSDENIED) {
|
|
int http_code = GetHttpResponseStatusFromBinding(binding_);
|
|
if (300 <= http_code && http_code < 400) {
|
|
status_.set_result(net::URLRequestStatus::FAILED,
|
|
net::ERR_UNSAFE_REDIRECT);
|
|
}
|
|
}
|
|
|
|
// The code below seems easy but it is not. :)
|
|
// The network policy in Chrome network is that error code/end_of_stream
|
|
// should be returned only as a result of read (or start) request.
|
|
// Here are the possible cases:
|
|
// pending_data_|pending_read
|
|
// FALSE |FALSE => EndRequest if no headers, otherwise wait for Read.
|
|
// FALSE |TRUE => EndRequest.
|
|
// TRUE |FALSE => Wait for Read.
|
|
// TRUE |TRUE => Something went wrong!!
|
|
|
|
if (pending_data_) {
|
|
DCHECK_EQ(pending_read_size_, 0UL);
|
|
ReleaseBindings();
|
|
return S_OK;
|
|
}
|
|
|
|
if (headers_received_ && pending_read_size_ == 0) {
|
|
ReleaseBindings();
|
|
return S_OK;
|
|
}
|
|
|
|
// No headers or there is a pending read from Chrome.
|
|
NotifyDelegateAndDie();
|
|
return S_OK;
|
|
}
|
|
|
|
// Status::ABORTING
|
|
if (status_.was_redirected()) {
|
|
// Just release bindings here. Chrome will issue EndRequest(request_id)
|
|
// after processing headers we had provided.
|
|
if (!pending_) {
|
|
std::string headers = GetHttpHeadersFromBinding(binding_);
|
|
OnResponse(0, UTF8ToWide(headers).c_str(), NULL, NULL);
|
|
}
|
|
ReleaseBindings();
|
|
return S_OK;
|
|
}
|
|
|
|
// Stop invoked.
|
|
NotifyDelegateAndDie();
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP UrlmonUrlRequest::GetBindInfo(DWORD* bind_flags,
|
|
BINDINFO* bind_info) {
|
|
if ((bind_info == NULL) || (bind_info->cbSize == 0) || (bind_flags == NULL))
|
|
return E_INVALIDARG;
|
|
|
|
*bind_flags = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA;
|
|
|
|
// TODO(ananta)
|
|
// Look into whether the other load flags need to be supported in chrome
|
|
// frame.
|
|
if (load_flags_ & net::LOAD_VALIDATE_CACHE)
|
|
*bind_flags |= BINDF_RESYNCHRONIZE;
|
|
|
|
if (load_flags_ & net::LOAD_BYPASS_CACHE)
|
|
*bind_flags |= BINDF_GETNEWESTVERSION;
|
|
|
|
if (LowerCaseEqualsASCII(method(), "get")) {
|
|
bind_info->dwBindVerb = BINDVERB_GET;
|
|
} else if (LowerCaseEqualsASCII(method(), "post")) {
|
|
bind_info->dwBindVerb = BINDVERB_POST;
|
|
} else if (LowerCaseEqualsASCII(method(), "put")) {
|
|
bind_info->dwBindVerb = BINDVERB_PUT;
|
|
} else {
|
|
std::wstring verb(ASCIIToWide(StringToUpperASCII(method())));
|
|
bind_info->dwBindVerb = BINDVERB_CUSTOM;
|
|
bind_info->szCustomVerb = reinterpret_cast<wchar_t*>(
|
|
::CoTaskMemAlloc((verb.length() + 1) * sizeof(wchar_t)));
|
|
lstrcpyW(bind_info->szCustomVerb, verb.c_str());
|
|
}
|
|
|
|
if (post_data_len()) {
|
|
// Bypass caching proxies on upload requests and avoid writing responses to
|
|
// the browser's cache.
|
|
*bind_flags |= BINDF_GETNEWESTVERSION | BINDF_PRAGMA_NO_CACHE;
|
|
|
|
// Attempt to avoid storing the response for upload requests.
|
|
// See http://crbug.com/55918
|
|
if (resource_type_ != ResourceType::MAIN_FRAME)
|
|
*bind_flags |= BINDF_NOWRITECACHE;
|
|
|
|
// Initialize the STGMEDIUM.
|
|
memset(&bind_info->stgmedData, 0, sizeof(STGMEDIUM));
|
|
bind_info->grfBindInfoF = 0;
|
|
|
|
if (bind_info->dwBindVerb != BINDVERB_CUSTOM)
|
|
bind_info->szCustomVerb = NULL;
|
|
|
|
if (get_upload_data(&bind_info->stgmedData.pstm) == S_OK) {
|
|
bind_info->stgmedData.tymed = TYMED_ISTREAM;
|
|
#pragma warning(disable:4244)
|
|
bind_info->cbstgmedData = post_data_len();
|
|
#pragma warning(default:4244)
|
|
DVLOG(1) << __FUNCTION__ << me() << method()
|
|
<< " request with " << base::Int64ToString(post_data_len())
|
|
<< " bytes. url=" << url();
|
|
} else {
|
|
DVLOG(1) << __FUNCTION__ << me() << "POST request with no data!";
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP UrlmonUrlRequest::OnDataAvailable(DWORD flags, DWORD size,
|
|
FORMATETC* formatetc,
|
|
STGMEDIUM* storage) {
|
|
DCHECK_EQ(thread_, base::PlatformThread::CurrentId());
|
|
DVLOG(1) << __FUNCTION__ << me() << "bytes available: " << size;
|
|
|
|
if (terminate_requested()) {
|
|
DVLOG(1) << " Download requested. INET_E_TERMINATED_BIND returned";
|
|
return INET_E_TERMINATED_BIND;
|
|
}
|
|
|
|
if (!storage || (storage->tymed != TYMED_ISTREAM)) {
|
|
NOTREACHED();
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
IStream* read_stream = storage->pstm;
|
|
if (!read_stream) {
|
|
NOTREACHED();
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
// Some requests such as HEAD have zero data.
|
|
if (size > 0)
|
|
pending_data_ = read_stream;
|
|
|
|
if (pending_read_size_) {
|
|
size_t bytes_copied = SendDataToDelegate(pending_read_size_);
|
|
DVLOG(1) << __FUNCTION__ << me() << "size read: " << bytes_copied;
|
|
} else {
|
|
DVLOG(1) << __FUNCTION__ << me() << "- waiting for remote read";
|
|
}
|
|
|
|
if (BSCF_LASTDATANOTIFICATION & flags) {
|
|
if (!is_expecting_download_ || pending()) {
|
|
DVLOG(1) << __FUNCTION__ << me() << "EOF";
|
|
return S_OK;
|
|
}
|
|
// Always return INET_E_TERMINATED_BIND to allow bind context reuse
|
|
// if DownloadToHost is suddenly requested.
|
|
DVLOG(1) << __FUNCTION__ << " EOF: INET_E_TERMINATED_BIND returned";
|
|
return INET_E_TERMINATED_BIND;
|
|
}
|
|
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(thread_, base::PlatformThread::CurrentId());
|
|
if (!additional_headers) {
|
|
NOTREACHED();
|
|
return E_POINTER;
|
|
}
|
|
|
|
DVLOG(1) << __FUNCTION__ << me() << "headers: \n" << current_headers;
|
|
|
|
if (status_.get_state() == Status::ABORTING) {
|
|
// At times the BINDSTATUS_REDIRECTING notification which is sent to the
|
|
// IBindStatusCallback interface does not have an accompanying HTTP
|
|
// redirect status code, i.e. the attempt to query the HTTP status code
|
|
// from the binding returns 0, 200, etc which are invalid redirect codes.
|
|
// We don't want urlmon to follow redirects. We return E_ABORT in our
|
|
// IBindStatusCallback::OnProgress function and also abort the binding.
|
|
// However urlmon still tries to establish a transaction with the
|
|
// redirected URL which confuses the web server.
|
|
// Fix is to abort the attempted transaction.
|
|
DLOG(WARNING) << __FUNCTION__ << me()
|
|
<< ": Aborting connection to URL:"
|
|
<< url
|
|
<< " as the binding has been aborted";
|
|
return E_ABORT;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
std::string new_headers;
|
|
if (post_data_len() > 0) {
|
|
if (is_chunked_upload()) {
|
|
new_headers = base::StringPrintf("Transfer-Encoding: chunked\r\n");
|
|
}
|
|
}
|
|
|
|
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 += base::StringPrintf("Referer: %s\r\n", referrer().c_str());
|
|
}
|
|
|
|
// In the rare case if "User-Agent" string is already in |current_headers|.
|
|
// We send Chrome's user agent in requests initiated within ChromeFrame to
|
|
// enable third party content in pages rendered in ChromeFrame to correctly
|
|
// send content for Chrome as the third party content may not be equipped to
|
|
// identify chromeframe as the user agent. This also ensures that the user
|
|
// agent reported in scripts in chrome frame is consistent with that sent
|
|
// in outgoing requests.
|
|
std::string user_agent = http_utils::AddChromeFrameToUserAgentValue(
|
|
http_utils::GetChromeUserAgent());
|
|
new_headers += ReplaceOrAddUserAgent(current_headers, user_agent);
|
|
|
|
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());
|
|
}
|
|
}
|
|
request_headers_ = new_headers;
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP UrlmonUrlRequest::OnResponse(DWORD dwResponseCode,
|
|
const wchar_t* response_headers, const wchar_t* request_headers,
|
|
wchar_t** additional_headers) {
|
|
DCHECK_EQ(thread_, base::PlatformThread::CurrentId());
|
|
DVLOG(1) << __FUNCTION__ << me() << "headers: \n" << response_headers;
|
|
|
|
if (!delegate_) {
|
|
DLOG(WARNING) << "Invalid delegate";
|
|
return S_OK;
|
|
}
|
|
|
|
std::string raw_headers = WideToUTF8(response_headers);
|
|
|
|
delegate_->AddPrivacyDataForUrl(url(), "", 0);
|
|
|
|
// 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 if they specify a value for "X-Frame-Options" other than
|
|
// "ALLOWALL" (the others are "deny" and "sameorigin"). 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 (enable_frame_busting_) {
|
|
if (http_utils::HasFrameBustingHeader(raw_headers)) {
|
|
DLOG(ERROR) << "X-Frame-Options header other than ALLOWALL " <<
|
|
"detected, navigation canceled";
|
|
return E_FAIL;
|
|
}
|
|
}
|
|
|
|
DVLOG(1) << __FUNCTION__ << me() << "Calling OnResponseStarted";
|
|
|
|
// Inform the delegate.
|
|
headers_received_ = true;
|
|
DCHECK_NE(id(), -1);
|
|
delegate_->OnResponseStarted(id(),
|
|
"", // mime_type
|
|
raw_headers.c_str(), // headers
|
|
0, // size
|
|
base::Time(), // last_modified
|
|
status_.get_redirection().utf8_url,
|
|
status_.get_redirection().http_code,
|
|
socket_address_);
|
|
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));
|
|
const wchar_t* str = guid;
|
|
if (guid_reason == IID_IAuthenticate)
|
|
str = L"IAuthenticate";
|
|
else if (guid_reason == IID_IHttpSecurity)
|
|
str = L"IHttpSecurity";
|
|
else if (guid_reason == IID_IWindowForBindingUI)
|
|
str = L"IWindowForBindingUI";
|
|
DVLOG(1) << __FUNCTION__ << me() << "GetWindow: " << str;
|
|
#endif
|
|
// We should return a non-NULL HWND as parent. Otherwise no dialog is shown.
|
|
// TODO(iyengar): This hits when running the URL request tests.
|
|
DLOG_IF(WARNING, !::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;
|
|
|
|
if (privileged_mode_)
|
|
return E_ACCESSDENIED;
|
|
|
|
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.
|
|
DVLOG(1) << __FUNCTION__ << me() << "Security problem : " << problem;
|
|
|
|
// On IE6 the default IBindStatusCallback interface does not implement the
|
|
// IHttpSecurity interface and thus causes IE to put up a certificate error
|
|
// dialog box. We need to emulate this behavior for sites with mismatched
|
|
// certificates to work.
|
|
if (GetIEVersion() == IE_6)
|
|
return S_FALSE;
|
|
|
|
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::StartAsyncDownload() {
|
|
DVLOG(1) << __FUNCTION__ << me() << url();
|
|
HRESULT hr = E_FAIL;
|
|
DCHECK((moniker_ && bind_context_) || (!moniker_ && !bind_context_));
|
|
|
|
if (!moniker_.get()) {
|
|
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;
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
if (bind_context_.get() == NULL) {
|
|
hr = ::CreateAsyncBindCtxEx(NULL, 0, this, NULL,
|
|
bind_context_.Receive(), 0);
|
|
DCHECK(SUCCEEDED(hr)) << "CreateAsyncBindCtxEx failed. Error: " << hr;
|
|
} else {
|
|
// Use existing bind context.
|
|
hr = ::RegisterBindStatusCallback(bind_context_, this, NULL, 0);
|
|
DCHECK(SUCCEEDED(hr)) << "RegisterBindStatusCallback failed. Error: " << hr;
|
|
}
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
ScopedComPtr<IStream> stream;
|
|
|
|
// BindToStorage may complete synchronously.
|
|
// We still get all the callbacks - OnStart/StopBinding, this may result
|
|
// in destruction of our object. It's fine but we access some members
|
|
// below for debug info. :)
|
|
ScopedComPtr<IHttpSecurity> self(this);
|
|
|
|
// Inform our moniker patch this binding should not be tortured.
|
|
ScopedComPtr<BindContextInfo> info;
|
|
BindContextInfo::FromBindContext(bind_context_, info.Receive());
|
|
DCHECK(info);
|
|
if (info)
|
|
info->set_chrome_request(true);
|
|
|
|
hr = moniker_->BindToStorage(bind_context_, NULL, __uuidof(IStream),
|
|
reinterpret_cast<void**>(stream.Receive()));
|
|
if (hr == S_OK)
|
|
DCHECK(binding_ != NULL || status_.get_state() == Status::DONE);
|
|
|
|
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) << __FUNCTION__ << me() <<
|
|
base::StringPrintf("IUrlMoniker::BindToStorage failed 0x%08X.", hr);
|
|
// In most cases we'll get a MK_E_SYNTAX error here but if we abort
|
|
// the navigation ourselves such as in the case of seeing something
|
|
// else than ALLOWALL in X-Frame-Options.
|
|
}
|
|
}
|
|
|
|
DLOG_IF(ERROR, FAILED(hr)) << me() <<
|
|
base::StringPrintf(L"StartAsyncDownload failed: 0x%08X", hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
void UrlmonUrlRequest::NotifyDelegateAndDie() {
|
|
DCHECK_EQ(thread_, base::PlatformThread::CurrentId());
|
|
DVLOG(1) << __FUNCTION__ << me();
|
|
|
|
PluginUrlRequestDelegate* delegate = delegate_;
|
|
delegate_ = NULL;
|
|
ReleaseBindings();
|
|
TerminateTransaction();
|
|
if (delegate && id() != -1) {
|
|
net::URLRequestStatus result = status_.get_result();
|
|
delegate->OnResponseEnd(id(), result);
|
|
} else {
|
|
DLOG(WARNING) << __FUNCTION__ << me() << "no delegate";
|
|
}
|
|
}
|
|
|
|
void UrlmonUrlRequest::TerminateTransaction() {
|
|
if (cleanup_transaction_ && bind_context_ && moniker_) {
|
|
// We return INET_E_TERMINATED_BIND from our OnDataAvailable implementation
|
|
// to ensure that the transaction stays around if Chrome decides to issue
|
|
// a download request when it finishes inspecting the headers received in
|
|
// OnResponse. However this causes the urlmon transaction object to leak.
|
|
// To workaround this we save away the IInternetProtocol interface which is
|
|
// implemented by the urlmon CTransaction object in our BindContextInfo
|
|
// instance which is maintained per bind context. Invoking Terminate
|
|
// on this with the special flags 0x2000000 cleanly releases the
|
|
// transaction.
|
|
static const int kUrlmonTerminateTransactionFlags = 0x2000000;
|
|
ScopedComPtr<BindContextInfo> info;
|
|
BindContextInfo::FromBindContext(bind_context_, info.Receive());
|
|
DCHECK(info);
|
|
if (info && info->protocol()) {
|
|
info->protocol()->Terminate(kUrlmonTerminateTransactionFlags);
|
|
}
|
|
}
|
|
bind_context_.Release();
|
|
}
|
|
|
|
void UrlmonUrlRequest::ReleaseBindings() {
|
|
binding_.Release();
|
|
// Do not release bind_context here!
|
|
// We may get DownloadToHost request and therefore we want the bind_context
|
|
// to be available.
|
|
if (bind_context_)
|
|
::RevokeBindStatusCallback(bind_context_, this);
|
|
}
|
|
|
|
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)
|
|
<< base::StringPrintf("TODO: translate HRESULT 0x%08X to net::Error",
|
|
hr);
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
PluginUrlRequestManager::ThreadSafeFlags
|
|
UrlmonUrlRequestManager::GetThreadSafeFlags() {
|
|
return PluginUrlRequestManager::NOT_THREADSAFE;
|
|
}
|
|
|
|
void UrlmonUrlRequestManager::SetInfoForUrl(const std::wstring& url,
|
|
IMoniker* moniker, LPBC bind_ctx) {
|
|
CComObject<UrlmonUrlRequest>* new_request = NULL;
|
|
CComObject<UrlmonUrlRequest>::CreateInstance(&new_request);
|
|
if (new_request) {
|
|
GURL start_url(url);
|
|
DCHECK(start_url.is_valid());
|
|
DCHECK(pending_request_ == NULL);
|
|
|
|
ScopedComPtr<BindContextInfo> info;
|
|
BindContextInfo::FromBindContext(bind_ctx, info.Receive());
|
|
DCHECK(info);
|
|
IStream* cache = info ? info->cache() : NULL;
|
|
pending_request_ = new_request;
|
|
pending_request_->InitPending(start_url, moniker, bind_ctx,
|
|
enable_frame_busting_, privileged_mode_,
|
|
notification_window_, cache);
|
|
// Start the request
|
|
bool is_started = pending_request_->Start();
|
|
DCHECK(is_started);
|
|
}
|
|
}
|
|
|
|
void UrlmonUrlRequestManager::StartRequest(int request_id,
|
|
const AutomationURLRequest& request_info) {
|
|
DVLOG(1) << __FUNCTION__ << " id: " << request_id;
|
|
DCHECK_EQ(0, calling_delegate_);
|
|
|
|
if (stopping_) {
|
|
DLOG(WARNING) << __FUNCTION__ << " request not started (stopping)";
|
|
return;
|
|
}
|
|
|
|
DCHECK(request_map_.find(request_id) == request_map_.end());
|
|
DCHECK(GURL(request_info.url).is_valid());
|
|
|
|
scoped_refptr<UrlmonUrlRequest> new_request;
|
|
bool is_started = false;
|
|
if (pending_request_) {
|
|
if (pending_request_->url() != request_info.url) {
|
|
DLOG(INFO) << __FUNCTION__
|
|
<< "Received url request for url:"
|
|
<< request_info.url
|
|
<< ". Stopping pending url request for url:"
|
|
<< pending_request_->url();
|
|
pending_request_->Stop();
|
|
pending_request_ = NULL;
|
|
} else {
|
|
new_request.swap(pending_request_);
|
|
is_started = true;
|
|
DVLOG(1) << __FUNCTION__ << new_request->me()
|
|
<< " assigned id " << request_id;
|
|
}
|
|
}
|
|
|
|
if (!is_started) {
|
|
CComObject<UrlmonUrlRequest>* created_request = NULL;
|
|
CComObject<UrlmonUrlRequest>::CreateInstance(&created_request);
|
|
new_request = created_request;
|
|
}
|
|
|
|
new_request->Initialize(static_cast<PluginUrlRequestDelegate*>(this),
|
|
request_id,
|
|
request_info.url,
|
|
request_info.method,
|
|
request_info.referrer,
|
|
request_info.extra_request_headers,
|
|
request_info.upload_data,
|
|
static_cast<ResourceType::Type>(request_info.resource_type),
|
|
enable_frame_busting_,
|
|
request_info.load_flags);
|
|
new_request->set_parent_window(notification_window_);
|
|
new_request->set_privileged_mode(privileged_mode_);
|
|
|
|
request_map_[request_id] = new_request;
|
|
|
|
if (!is_started) {
|
|
// Freshly created, start now.
|
|
new_request->Start();
|
|
} else {
|
|
// Request is already underway, call OnResponse so that the
|
|
// other side can start reading.
|
|
DCHECK(!new_request->response_headers().empty());
|
|
new_request->OnResponse(
|
|
0, UTF8ToWide(new_request->response_headers()).c_str(), NULL, NULL);
|
|
}
|
|
}
|
|
|
|
void UrlmonUrlRequestManager::ReadRequest(int request_id, int bytes_to_read) {
|
|
DVLOG(1) << __FUNCTION__ << " id: " << request_id;
|
|
DCHECK_EQ(0, calling_delegate_);
|
|
scoped_refptr<UrlmonUrlRequest> request = LookupRequest(request_id);
|
|
// if zero, it may just have had network error.
|
|
if (request)
|
|
request->Read(bytes_to_read);
|
|
}
|
|
|
|
void UrlmonUrlRequestManager::DownloadRequestInHost(int request_id) {
|
|
DVLOG(1) << __FUNCTION__ << " " << request_id;
|
|
if (IsWindow(notification_window_)) {
|
|
scoped_refptr<UrlmonUrlRequest> request(LookupRequest(request_id));
|
|
if (request) {
|
|
UrlmonUrlRequest::TerminateBindCallback* callback = NewCallback(this,
|
|
&UrlmonUrlRequestManager::BindTerminated);
|
|
request->TerminateBind(callback);
|
|
} else {
|
|
NOTREACHED();
|
|
}
|
|
} else {
|
|
NOTREACHED() << "Cannot handle download if we don't have anyone to hand it "
|
|
"to.";
|
|
}
|
|
}
|
|
|
|
void UrlmonUrlRequestManager::BindTerminated(IMoniker* moniker,
|
|
IBindCtx* bind_ctx,
|
|
IStream* post_data,
|
|
const char* request_headers) {
|
|
DownloadInHostParams* download_params = new DownloadInHostParams;
|
|
download_params->bind_ctx = bind_ctx;
|
|
download_params->moniker = moniker;
|
|
download_params->post_data = post_data;
|
|
if (request_headers) {
|
|
download_params->request_headers = request_headers;
|
|
}
|
|
::PostMessage(notification_window_, WM_DOWNLOAD_IN_HOST,
|
|
reinterpret_cast<WPARAM>(download_params), 0);
|
|
}
|
|
|
|
void UrlmonUrlRequestManager::GetCookiesForUrl(const GURL& url, int cookie_id) {
|
|
DWORD cookie_size = 0;
|
|
bool success = true;
|
|
std::string cookie_string;
|
|
|
|
int32 cookie_action = COOKIEACTION_READ;
|
|
BOOL result = InternetGetCookieA(url.spec().c_str(), NULL, NULL,
|
|
&cookie_size);
|
|
DWORD error = 0;
|
|
if (cookie_size) {
|
|
scoped_array<char> cookies(new char[cookie_size + 1]);
|
|
if (!InternetGetCookieA(url.spec().c_str(), NULL, cookies.get(),
|
|
&cookie_size)) {
|
|
success = false;
|
|
error = GetLastError();
|
|
NOTREACHED() << "InternetGetCookie failed. Error: " << error;
|
|
} else {
|
|
cookie_string = cookies.get();
|
|
}
|
|
} else {
|
|
success = false;
|
|
error = GetLastError();
|
|
DVLOG(1) << "InternetGetCookie failed. Error: " << error;
|
|
}
|
|
|
|
OnCookiesRetrieved(success, url, cookie_string, cookie_id);
|
|
if (!success && !error)
|
|
cookie_action = COOKIEACTION_SUPPRESS;
|
|
|
|
AddPrivacyDataForUrl(url.spec(), "", cookie_action);
|
|
}
|
|
|
|
void UrlmonUrlRequestManager::SetCookiesForUrl(const GURL& url,
|
|
const std::string& cookie) {
|
|
DCHECK(container_);
|
|
// Grab a reference on the container to ensure that we don't get destroyed in
|
|
// case the InternetSetCookie call below puts up a dialog box, which can
|
|
// happen if the cookie policy is set to prompt.
|
|
if (container_) {
|
|
container_->AddRef();
|
|
}
|
|
|
|
InternetCookieState cookie_state = static_cast<InternetCookieState>(
|
|
InternetSetCookieExA(url.spec().c_str(), NULL, cookie.c_str(),
|
|
INTERNET_COOKIE_EVALUATE_P3P, NULL));
|
|
|
|
int32 cookie_action = MapCookieStateToCookieAction(cookie_state);
|
|
AddPrivacyDataForUrl(url.spec(), "", cookie_action);
|
|
|
|
if (container_) {
|
|
container_->Release();
|
|
}
|
|
}
|
|
|
|
void UrlmonUrlRequestManager::EndRequest(int request_id) {
|
|
DVLOG(1) << __FUNCTION__ << " id: " << request_id;
|
|
DCHECK_EQ(0, calling_delegate_);
|
|
scoped_refptr<UrlmonUrlRequest> request = LookupRequest(request_id);
|
|
if (request) {
|
|
request_map_.erase(request_id);
|
|
request->Stop();
|
|
} else {
|
|
DLOG(ERROR) << __FUNCTION__ << " no request found for " << request_id;
|
|
}
|
|
}
|
|
|
|
void UrlmonUrlRequestManager::StopAll() {
|
|
DVLOG(1) << __FUNCTION__;
|
|
if (stopping_)
|
|
return;
|
|
|
|
stopping_ = true;
|
|
|
|
DVLOG(1) << __FUNCTION__ << " stopping " << request_map_.size()
|
|
<< " requests";
|
|
|
|
for (RequestMap::iterator it = request_map_.begin();
|
|
it != request_map_.end(); ++it) {
|
|
DCHECK(it->second != NULL);
|
|
it->second->Stop();
|
|
}
|
|
|
|
request_map_.clear();
|
|
}
|
|
|
|
void UrlmonUrlRequestManager::OnResponseStarted(int request_id,
|
|
const char* mime_type, const char* headers, int size,
|
|
base::Time last_modified, const std::string& redirect_url,
|
|
int redirect_status, const net::HostPortPair& socket_address) {
|
|
DCHECK_NE(request_id, -1);
|
|
DVLOG(1) << __FUNCTION__;
|
|
DCHECK(LookupRequest(request_id) != NULL);
|
|
++calling_delegate_;
|
|
delegate_->OnResponseStarted(request_id, mime_type, headers, size,
|
|
last_modified, redirect_url, redirect_status, socket_address);
|
|
--calling_delegate_;
|
|
}
|
|
|
|
void UrlmonUrlRequestManager::OnReadComplete(int request_id,
|
|
const std::string& data) {
|
|
DCHECK_NE(request_id, -1);
|
|
DVLOG(1) << __FUNCTION__ << " id: " << request_id;
|
|
DCHECK(LookupRequest(request_id) != NULL);
|
|
++calling_delegate_;
|
|
delegate_->OnReadComplete(request_id, data);
|
|
--calling_delegate_;
|
|
DVLOG(1) << __FUNCTION__ << " done id: " << request_id;
|
|
}
|
|
|
|
void UrlmonUrlRequestManager::OnResponseEnd(
|
|
int request_id,
|
|
const net::URLRequestStatus& status) {
|
|
DCHECK_NE(request_id, -1);
|
|
DVLOG(1) << __FUNCTION__;
|
|
DCHECK(status.status() != net::URLRequestStatus::CANCELED);
|
|
RequestMap::size_type n = request_map_.erase(request_id);
|
|
if (n != 1u) {
|
|
DLOG(WARNING) << __FUNCTION__
|
|
<< " Failed to find request id:"
|
|
<< request_id;
|
|
}
|
|
++calling_delegate_;
|
|
delegate_->OnResponseEnd(request_id, status);
|
|
--calling_delegate_;
|
|
}
|
|
|
|
void UrlmonUrlRequestManager::OnCookiesRetrieved(bool success, const GURL& url,
|
|
const std::string& cookie_string, int cookie_id) {
|
|
DCHECK(url.is_valid());
|
|
delegate_->OnCookiesRetrieved(success, url, cookie_string, cookie_id);
|
|
}
|
|
|
|
scoped_refptr<UrlmonUrlRequest> UrlmonUrlRequestManager::LookupRequest(
|
|
int request_id) {
|
|
RequestMap::iterator it = request_map_.find(request_id);
|
|
if (request_map_.end() != it)
|
|
return it->second;
|
|
DLOG(ERROR) << __FUNCTION__ << " no request found for " << request_id;
|
|
return NULL;
|
|
}
|
|
|
|
UrlmonUrlRequestManager::UrlmonUrlRequestManager()
|
|
: stopping_(false), calling_delegate_(0), notification_window_(NULL),
|
|
privileged_mode_(false),
|
|
container_(NULL) {
|
|
}
|
|
|
|
UrlmonUrlRequestManager::~UrlmonUrlRequestManager() {
|
|
StopAll();
|
|
}
|
|
|
|
void UrlmonUrlRequestManager::AddPrivacyDataForUrl(
|
|
const std::string& url, const std::string& policy_ref,
|
|
int32 flags) {
|
|
DCHECK(!url.empty());
|
|
|
|
bool fire_privacy_event = false;
|
|
|
|
if (privacy_info_.privacy_records.empty())
|
|
flags |= PRIVACY_URLISTOPLEVEL;
|
|
|
|
if (!privacy_info_.privacy_impacted) {
|
|
if (flags & (COOKIEACTION_ACCEPT | COOKIEACTION_REJECT |
|
|
COOKIEACTION_DOWNGRADE)) {
|
|
privacy_info_.privacy_impacted = true;
|
|
fire_privacy_event = true;
|
|
}
|
|
}
|
|
|
|
PrivacyInfo::PrivacyEntry& privacy_entry =
|
|
privacy_info_.privacy_records[UTF8ToWide(url)];
|
|
|
|
privacy_entry.flags |= flags;
|
|
privacy_entry.policy_ref = UTF8ToWide(policy_ref);
|
|
|
|
if (fire_privacy_event && IsWindow(notification_window_)) {
|
|
PostMessage(notification_window_, WM_FIRE_PRIVACY_CHANGE_NOTIFICATION, 1,
|
|
0);
|
|
}
|
|
}
|