| // 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/browser/sync/glue/http_bridge.h" |
| |
| #include "base/message_loop.h" |
| #include "base/message_loop_proxy.h" |
| #include "base/string_number_conversions.h" |
| #include "content/browser/browser_thread.h" |
| #include "net/base/cookie_monster.h" |
| #include "net/base/host_resolver.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/net_errors.h" |
| #include "net/http/http_cache.h" |
| #include "net/http/http_network_layer.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/proxy/proxy_service.h" |
| #include "net/url_request/url_request_context.h" |
| #include "net/url_request/url_request_status.h" |
| #include "webkit/glue/webkit_glue.h" |
| |
| namespace browser_sync { |
| |
| HttpBridge::RequestContextGetter::RequestContextGetter( |
| net::URLRequestContextGetter* baseline_context_getter) |
| : baseline_context_getter_(baseline_context_getter) { |
| } |
| |
| net::URLRequestContext* |
| HttpBridge::RequestContextGetter::GetURLRequestContext() { |
| // Lazily create the context. |
| if (!context_) { |
| net::URLRequestContext* baseline_context = |
| baseline_context_getter_->GetURLRequestContext(); |
| context_ = new RequestContext(baseline_context); |
| baseline_context_getter_ = NULL; |
| } |
| |
| // Apply the user agent which was set earlier. |
| if (is_user_agent_set()) |
| context_->set_user_agent(user_agent_); |
| |
| return context_; |
| } |
| |
| scoped_refptr<base::MessageLoopProxy> |
| HttpBridge::RequestContextGetter::GetIOMessageLoopProxy() const { |
| return BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO); |
| } |
| |
| HttpBridgeFactory::HttpBridgeFactory( |
| net::URLRequestContextGetter* baseline_context_getter) { |
| DCHECK(baseline_context_getter != NULL); |
| request_context_getter_ = |
| new HttpBridge::RequestContextGetter(baseline_context_getter); |
| } |
| |
| HttpBridgeFactory::~HttpBridgeFactory() { |
| } |
| |
| sync_api::HttpPostProviderInterface* HttpBridgeFactory::Create() { |
| HttpBridge* http = new HttpBridge(request_context_getter_); |
| http->AddRef(); |
| return http; |
| } |
| |
| void HttpBridgeFactory::Destroy(sync_api::HttpPostProviderInterface* http) { |
| static_cast<HttpBridge*>(http)->Release(); |
| } |
| |
| HttpBridge::RequestContext::RequestContext( |
| net::URLRequestContext* baseline_context) |
| : baseline_context_(baseline_context) { |
| |
| // Create empty, in-memory cookie store. |
| set_cookie_store(new net::CookieMonster(NULL, NULL)); |
| |
| // We don't use a cache for bridged loads, but we do want to share proxy info. |
| set_host_resolver(baseline_context->host_resolver()); |
| set_proxy_service(baseline_context->proxy_service()); |
| set_ssl_config_service(baseline_context->ssl_config_service()); |
| |
| // We want to share the HTTP session data with the network layer factory, |
| // which includes auth_cache for proxies. |
| // Session is not refcounted so we need to be careful to not lose the parent |
| // context. |
| net::HttpNetworkSession* session = |
| baseline_context->http_transaction_factory()->GetSession(); |
| DCHECK(session); |
| set_http_transaction_factory(new net::HttpNetworkLayer(session)); |
| |
| // TODO(timsteele): We don't currently listen for pref changes of these |
| // fields or CookiePolicy; I'm not sure we want to strictly follow the |
| // default settings, since for example if the user chooses to block all |
| // cookies, sync will start failing. Also it seems like accept_lang/charset |
| // should be tied to whatever the sync servers expect (if anything). These |
| // fields should probably just be settable by sync backend; though we should |
| // figure out if we need to give the user explicit control over policies etc. |
| set_accept_language(baseline_context->accept_language()); |
| set_accept_charset(baseline_context->accept_charset()); |
| |
| // We default to the browser's user agent. This can (and should) be overridden |
| // with set_user_agent. |
| set_user_agent(webkit_glue::GetUserAgent(GURL())); |
| |
| set_net_log(baseline_context->net_log()); |
| } |
| |
| HttpBridge::RequestContext::~RequestContext() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| delete http_transaction_factory(); |
| } |
| |
| HttpBridge::URLFetchState::URLFetchState() : url_poster(NULL), |
| aborted(false), |
| request_completed(false), |
| request_succeeded(false), |
| http_response_code(-1), |
| os_error_code(-1) {} |
| HttpBridge::URLFetchState::~URLFetchState() {} |
| |
| HttpBridge::HttpBridge(HttpBridge::RequestContextGetter* context_getter) |
| : context_getter_for_request_(context_getter), |
| created_on_loop_(MessageLoop::current()), |
| http_post_completed_(false, false) { |
| } |
| |
| HttpBridge::~HttpBridge() { |
| } |
| |
| void HttpBridge::SetUserAgent(const char* user_agent) { |
| DCHECK_EQ(MessageLoop::current(), created_on_loop_); |
| if (DCHECK_IS_ON()) { |
| base::AutoLock lock(fetch_state_lock_); |
| DCHECK(!fetch_state_.request_completed); |
| } |
| context_getter_for_request_->set_user_agent(user_agent); |
| } |
| |
| void HttpBridge::SetExtraRequestHeaders(const char * headers) { |
| DCHECK(extra_headers_.empty()) |
| << "HttpBridge::SetExtraRequestHeaders called twice."; |
| extra_headers_.assign(headers); |
| } |
| |
| void HttpBridge::SetURL(const char* url, int port) { |
| DCHECK_EQ(MessageLoop::current(), created_on_loop_); |
| if (DCHECK_IS_ON()) { |
| base::AutoLock lock(fetch_state_lock_); |
| DCHECK(!fetch_state_.request_completed); |
| } |
| DCHECK(url_for_request_.is_empty()) |
| << "HttpBridge::SetURL called more than once?!"; |
| GURL temp(url); |
| GURL::Replacements replacements; |
| std::string port_str = base::IntToString(port); |
| replacements.SetPort(port_str.c_str(), |
| url_parse::Component(0, port_str.length())); |
| url_for_request_ = temp.ReplaceComponents(replacements); |
| } |
| |
| void HttpBridge::SetPostPayload(const char* content_type, |
| int content_length, |
| const char* content) { |
| DCHECK_EQ(MessageLoop::current(), created_on_loop_); |
| if (DCHECK_IS_ON()) { |
| base::AutoLock lock(fetch_state_lock_); |
| DCHECK(!fetch_state_.request_completed); |
| } |
| DCHECK(content_type_.empty()) << "Bridge payload already set."; |
| DCHECK_GE(content_length, 0) << "Content length < 0"; |
| content_type_ = content_type; |
| if (!content || (content_length == 0)) { |
| DCHECK_EQ(content_length, 0); |
| request_content_ = " "; // TODO(timsteele): URLFetcher requires non-empty |
| // content for POSTs whereas CURL does not, for now |
| // we hack this to support the sync backend. |
| } else { |
| request_content_.assign(content, content_length); |
| } |
| } |
| |
| bool HttpBridge::MakeSynchronousPost(int* os_error_code, int* response_code) { |
| DCHECK_EQ(MessageLoop::current(), created_on_loop_); |
| if (DCHECK_IS_ON()) { |
| base::AutoLock lock(fetch_state_lock_); |
| DCHECK(!fetch_state_.request_completed); |
| } |
| DCHECK(url_for_request_.is_valid()) << "Invalid URL for request"; |
| DCHECK(!content_type_.empty()) << "Payload not set"; |
| |
| if (!BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| NewRunnableMethod(this, &HttpBridge::CallMakeAsynchronousPost))) { |
| // This usually happens when we're in a unit test. |
| LOG(WARNING) << "Could not post CallMakeAsynchronousPost task"; |
| return false; |
| } |
| |
| if (!http_post_completed_.Wait()) // Block until network request completes |
| NOTREACHED(); // or is aborted. See OnURLFetchComplete |
| // and Abort. |
| |
| base::AutoLock lock(fetch_state_lock_); |
| DCHECK(fetch_state_.request_completed || fetch_state_.aborted); |
| *os_error_code = fetch_state_.os_error_code; |
| *response_code = fetch_state_.http_response_code; |
| return fetch_state_.request_succeeded; |
| } |
| |
| void HttpBridge::MakeAsynchronousPost() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| base::AutoLock lock(fetch_state_lock_); |
| DCHECK(!fetch_state_.request_completed); |
| if (fetch_state_.aborted) |
| return; |
| |
| fetch_state_.url_poster = new URLFetcher(url_for_request_, |
| URLFetcher::POST, this); |
| fetch_state_.url_poster->set_request_context(context_getter_for_request_); |
| fetch_state_.url_poster->set_upload_data(content_type_, request_content_); |
| fetch_state_.url_poster->set_extra_request_headers(extra_headers_); |
| fetch_state_.url_poster->set_load_flags(net::LOAD_DO_NOT_SEND_COOKIES); |
| fetch_state_.url_poster->Start(); |
| } |
| |
| int HttpBridge::GetResponseContentLength() const { |
| DCHECK_EQ(MessageLoop::current(), created_on_loop_); |
| base::AutoLock lock(fetch_state_lock_); |
| DCHECK(fetch_state_.request_completed); |
| return fetch_state_.response_content.size(); |
| } |
| |
| const char* HttpBridge::GetResponseContent() const { |
| DCHECK_EQ(MessageLoop::current(), created_on_loop_); |
| base::AutoLock lock(fetch_state_lock_); |
| DCHECK(fetch_state_.request_completed); |
| return fetch_state_.response_content.data(); |
| } |
| |
| const std::string HttpBridge::GetResponseHeaderValue( |
| const std::string& name) const { |
| |
| DCHECK_EQ(MessageLoop::current(), created_on_loop_); |
| base::AutoLock lock(fetch_state_lock_); |
| DCHECK(fetch_state_.request_completed); |
| |
| std::string value; |
| fetch_state_.response_headers->EnumerateHeader(NULL, name, &value); |
| return value; |
| } |
| |
| void HttpBridge::Abort() { |
| base::AutoLock lock(fetch_state_lock_); |
| DCHECK(!fetch_state_.aborted); |
| if (fetch_state_.aborted || fetch_state_.request_completed) |
| return; |
| |
| fetch_state_.aborted = true; |
| BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE, |
| fetch_state_.url_poster); |
| fetch_state_.url_poster = NULL; |
| fetch_state_.os_error_code = net::ERR_ABORTED; |
| http_post_completed_.Signal(); |
| } |
| |
| void HttpBridge::OnURLFetchComplete(const URLFetcher *source, |
| const GURL &url, |
| const net::URLRequestStatus &status, |
| int response_code, |
| const ResponseCookies &cookies, |
| const std::string &data) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| base::AutoLock lock(fetch_state_lock_); |
| if (fetch_state_.aborted) |
| return; |
| |
| fetch_state_.request_completed = true; |
| fetch_state_.request_succeeded = |
| (net::URLRequestStatus::SUCCESS == status.status()); |
| fetch_state_.http_response_code = response_code; |
| fetch_state_.os_error_code = status.os_error(); |
| |
| fetch_state_.response_content = data; |
| fetch_state_.response_headers = source->response_headers(); |
| |
| // End of the line for url_poster_. It lives only on the IO loop. |
| // We defer deletion because we're inside a callback from a component of the |
| // URLFetcher, so it seems most natural / "polite" to let the stack unwind. |
| MessageLoop::current()->DeleteSoon(FROM_HERE, fetch_state_.url_poster); |
| fetch_state_.url_poster = NULL; |
| |
| // Wake the blocked syncer thread in MakeSynchronousPost. |
| // WARNING: DONT DO ANYTHING AFTER THIS CALL! |this| may be deleted! |
| http_post_completed_.Signal(); |
| } |
| |
| } // namespace browser_sync |