|  | // Copyright 2013 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 "components/update_client/url_request_post_interceptor.h" | 
|  |  | 
|  | #include <memory> | 
|  |  | 
|  | #include "base/files/file_util.h" | 
|  | #include "base/macros.h" | 
|  | #include "base/memory/ref_counted.h" | 
|  | #include "base/strings/stringprintf.h" | 
|  | #include "components/update_client/test_configurator.h" | 
|  | #include "net/base/upload_bytes_element_reader.h" | 
|  | #include "net/base/upload_data_stream.h" | 
|  | #include "net/http/http_response_headers.h" | 
|  | #include "net/http/http_util.h" | 
|  | #include "net/url_request/url_request.h" | 
|  | #include "net/url_request/url_request_filter.h" | 
|  | #include "net/url_request/url_request_interceptor.h" | 
|  | #include "net/url_request/url_request_simple_job.h" | 
|  | #include "net/url_request/url_request_test_util.h" | 
|  |  | 
|  | namespace update_client { | 
|  |  | 
|  | // Returns a canned response. | 
|  | class URLRequestMockJob : public net::URLRequestSimpleJob { | 
|  | public: | 
|  | URLRequestMockJob(net::URLRequest* request, | 
|  | net::NetworkDelegate* network_delegate, | 
|  | int response_code, | 
|  | const std::string& response_body) | 
|  | : net::URLRequestSimpleJob(request, network_delegate), | 
|  | response_code_(response_code), | 
|  | response_body_(response_body) {} | 
|  |  | 
|  | protected: | 
|  | void GetResponseInfo(net::HttpResponseInfo* info) override { | 
|  | const std::string headers = | 
|  | base::StringPrintf("HTTP/1.1 %i OK\r\n\r\n", response_code_); | 
|  | info->headers = base::MakeRefCounted<net::HttpResponseHeaders>( | 
|  | net::HttpUtil::AssembleRawHeaders(headers.c_str(), headers.length())); | 
|  | } | 
|  |  | 
|  | int GetData(std::string* mime_type, | 
|  | std::string* charset, | 
|  | std::string* data, | 
|  | const net::CompletionCallback& callback) const override { | 
|  | mime_type->assign("text/plain"); | 
|  | charset->assign("US-ASCII"); | 
|  | data->assign(response_body_); | 
|  | return net::OK; | 
|  | } | 
|  |  | 
|  | private: | 
|  | ~URLRequestMockJob() override {} | 
|  |  | 
|  | int response_code_; | 
|  | std::string response_body_; | 
|  | DISALLOW_COPY_AND_ASSIGN(URLRequestMockJob); | 
|  | }; | 
|  |  | 
|  | URLRequestPostInterceptor::URLRequestPostInterceptor( | 
|  | const GURL& url, | 
|  | const scoped_refptr<base::SequencedTaskRunner>& io_task_runner) | 
|  | : url_(url), io_task_runner_(io_task_runner), hit_count_(0) { | 
|  | } | 
|  |  | 
|  | URLRequestPostInterceptor::~URLRequestPostInterceptor() { | 
|  | DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); | 
|  | ClearExpectations(); | 
|  | } | 
|  |  | 
|  | void URLRequestPostInterceptor::ClearExpectations() { | 
|  | while (!expectations_.empty()) { | 
|  | Expectation expectation(expectations_.front()); | 
|  | delete expectation.first; | 
|  | expectations_.pop(); | 
|  | } | 
|  | } | 
|  |  | 
|  | GURL URLRequestPostInterceptor::GetUrl() const { | 
|  | return url_; | 
|  | } | 
|  |  | 
|  | bool URLRequestPostInterceptor::ExpectRequest( | 
|  | class RequestMatcher* request_matcher) { | 
|  | expectations_.push(std::make_pair(request_matcher, | 
|  | ExpectationResponse(kResponseCode200, ""))); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool URLRequestPostInterceptor::ExpectRequest( | 
|  | class RequestMatcher* request_matcher, | 
|  | int response_code) { | 
|  | expectations_.push( | 
|  | std::make_pair(request_matcher, ExpectationResponse(response_code, ""))); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool URLRequestPostInterceptor::ExpectRequest( | 
|  | class RequestMatcher* request_matcher, | 
|  | const base::FilePath& filepath) { | 
|  | std::string response; | 
|  | if (filepath.empty() || !base::ReadFileToString(filepath, &response)) | 
|  | return false; | 
|  |  | 
|  | expectations_.push(std::make_pair( | 
|  | request_matcher, ExpectationResponse(kResponseCode200, response))); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | int URLRequestPostInterceptor::GetHitCount() const { | 
|  | base::AutoLock auto_lock(interceptor_lock_); | 
|  | return hit_count_; | 
|  | } | 
|  |  | 
|  | int URLRequestPostInterceptor::GetCount() const { | 
|  | base::AutoLock auto_lock(interceptor_lock_); | 
|  | return static_cast<int>(requests_.size()); | 
|  | } | 
|  |  | 
|  | std::vector<std::string> URLRequestPostInterceptor::GetRequests() const { | 
|  | base::AutoLock auto_lock(interceptor_lock_); | 
|  | return requests_; | 
|  | } | 
|  |  | 
|  | std::string URLRequestPostInterceptor::GetRequestsAsString() const { | 
|  | std::vector<std::string> requests(GetRequests()); | 
|  |  | 
|  | std::string s = "Requests are:"; | 
|  |  | 
|  | int i = 0; | 
|  | for (std::vector<std::string>::const_iterator it = requests.begin(); | 
|  | it != requests.end(); ++it) { | 
|  | s.append(base::StringPrintf("\n  (%d): %s", ++i, it->c_str())); | 
|  | } | 
|  |  | 
|  | return s; | 
|  | } | 
|  |  | 
|  | void URLRequestPostInterceptor::Reset() { | 
|  | base::AutoLock auto_lock(interceptor_lock_); | 
|  | hit_count_ = 0; | 
|  | requests_.clear(); | 
|  | ClearExpectations(); | 
|  | } | 
|  |  | 
|  | class URLRequestPostInterceptor::Delegate : public net::URLRequestInterceptor { | 
|  | public: | 
|  | Delegate(const std::string& scheme, | 
|  | const std::string& hostname, | 
|  | const scoped_refptr<base::SequencedTaskRunner>& io_task_runner) | 
|  | : scheme_(scheme), hostname_(hostname), io_task_runner_(io_task_runner) {} | 
|  |  | 
|  | void Register() { | 
|  | DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); | 
|  | net::URLRequestFilter::GetInstance()->AddHostnameInterceptor( | 
|  | scheme_, hostname_, std::unique_ptr<net::URLRequestInterceptor>(this)); | 
|  | } | 
|  |  | 
|  | void Unregister() { | 
|  | DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); | 
|  | for (InterceptorMap::iterator it = interceptors_.begin(); | 
|  | it != interceptors_.end(); ++it) | 
|  | delete (*it).second; | 
|  | net::URLRequestFilter::GetInstance()->RemoveHostnameHandler(scheme_, | 
|  | hostname_); | 
|  | } | 
|  |  | 
|  | void OnCreateInterceptor(URLRequestPostInterceptor* interceptor) { | 
|  | DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); | 
|  | DCHECK(interceptors_.find(interceptor->GetUrl()) == interceptors_.end()); | 
|  |  | 
|  | interceptors_.insert(std::make_pair(interceptor->GetUrl(), interceptor)); | 
|  | } | 
|  |  | 
|  | private: | 
|  | ~Delegate() override {} | 
|  |  | 
|  | net::URLRequestJob* MaybeInterceptRequest( | 
|  | net::URLRequest* request, | 
|  | net::NetworkDelegate* network_delegate) const override { | 
|  | DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); | 
|  |  | 
|  | // Only intercepts POST. | 
|  | if (!request->has_upload()) | 
|  | return nullptr; | 
|  |  | 
|  | GURL url = request->url(); | 
|  | if (url.has_query()) { | 
|  | GURL::Replacements replacements; | 
|  | replacements.ClearQuery(); | 
|  | url = url.ReplaceComponents(replacements); | 
|  | } | 
|  |  | 
|  | InterceptorMap::const_iterator it(interceptors_.find(url)); | 
|  | if (it == interceptors_.end()) | 
|  | return nullptr; | 
|  |  | 
|  | // There is an interceptor hooked up for this url. Read the request body, | 
|  | // check the existing expectations, and handle the matching case by | 
|  | // popping the expectation off the queue, counting the match, and | 
|  | // returning a mock object to serve the canned response. | 
|  | URLRequestPostInterceptor* interceptor(it->second); | 
|  |  | 
|  | const net::UploadDataStream* stream = request->get_upload(); | 
|  | const net::UploadBytesElementReader* reader = | 
|  | (*stream->GetElementReaders())[0]->AsBytesReader(); | 
|  | const int size = reader->length(); | 
|  | scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(size)); | 
|  | const std::string request_body(reader->bytes()); | 
|  |  | 
|  | { | 
|  | base::AutoLock auto_lock(interceptor->interceptor_lock_); | 
|  | interceptor->requests_.push_back(request_body); | 
|  | if (interceptor->expectations_.empty()) | 
|  | return nullptr; | 
|  | const URLRequestPostInterceptor::Expectation& expectation( | 
|  | interceptor->expectations_.front()); | 
|  | if (expectation.first->Match(request_body)) { | 
|  | const int response_code(expectation.second.response_code); | 
|  | const std::string response_body(expectation.second.response_body); | 
|  | delete expectation.first; | 
|  | interceptor->expectations_.pop(); | 
|  | ++interceptor->hit_count_; | 
|  |  | 
|  | return new URLRequestMockJob(request, network_delegate, response_code, | 
|  | response_body); | 
|  | } | 
|  | } | 
|  |  | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | typedef std::map<GURL, URLRequestPostInterceptor*> InterceptorMap; | 
|  | InterceptorMap interceptors_; | 
|  |  | 
|  | const std::string scheme_; | 
|  | const std::string hostname_; | 
|  | scoped_refptr<base::SequencedTaskRunner> io_task_runner_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(Delegate); | 
|  | }; | 
|  |  | 
|  | URLRequestPostInterceptorFactory::URLRequestPostInterceptorFactory( | 
|  | const std::string& scheme, | 
|  | const std::string& hostname, | 
|  | const scoped_refptr<base::SequencedTaskRunner>& io_task_runner) | 
|  | : scheme_(scheme), | 
|  | hostname_(hostname), | 
|  | io_task_runner_(io_task_runner), | 
|  | delegate_(new URLRequestPostInterceptor::Delegate(scheme, | 
|  | hostname, | 
|  | io_task_runner)) { | 
|  | io_task_runner_->PostTask( | 
|  | FROM_HERE, base::BindOnce(&URLRequestPostInterceptor::Delegate::Register, | 
|  | base::Unretained(delegate_))); | 
|  | } | 
|  |  | 
|  | URLRequestPostInterceptorFactory::~URLRequestPostInterceptorFactory() { | 
|  | io_task_runner_->PostTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce(&URLRequestPostInterceptor::Delegate::Unregister, | 
|  | base::Unretained(delegate_))); | 
|  | } | 
|  |  | 
|  | URLRequestPostInterceptor* URLRequestPostInterceptorFactory::CreateInterceptor( | 
|  | const base::FilePath& filepath) { | 
|  | const GURL base_url( | 
|  | base::StringPrintf("%s://%s", scheme_.c_str(), hostname_.c_str())); | 
|  | GURL absolute_url(base_url.Resolve(filepath.MaybeAsASCII())); | 
|  | URLRequestPostInterceptor* interceptor( | 
|  | new URLRequestPostInterceptor(absolute_url, io_task_runner_)); | 
|  | bool res = io_task_runner_->PostTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce(&URLRequestPostInterceptor::Delegate::OnCreateInterceptor, | 
|  | base::Unretained(delegate_), | 
|  | base::Unretained(interceptor))); | 
|  | if (!res) { | 
|  | delete interceptor; | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | return interceptor; | 
|  | } | 
|  |  | 
|  | bool PartialMatch::Match(const std::string& actual) const { | 
|  | return actual.find(expected_) != std::string::npos; | 
|  | } | 
|  |  | 
|  | InterceptorFactory::InterceptorFactory( | 
|  | const scoped_refptr<base::SequencedTaskRunner>& io_task_runner) | 
|  | : URLRequestPostInterceptorFactory(POST_INTERCEPT_SCHEME, | 
|  | POST_INTERCEPT_HOSTNAME, | 
|  | io_task_runner) { | 
|  | } | 
|  |  | 
|  | InterceptorFactory::~InterceptorFactory() { | 
|  | } | 
|  |  | 
|  | URLRequestPostInterceptor* InterceptorFactory::CreateInterceptor() { | 
|  | return CreateInterceptorForPath(POST_INTERCEPT_PATH); | 
|  | } | 
|  |  | 
|  | URLRequestPostInterceptor* InterceptorFactory::CreateInterceptorForPath( | 
|  | const char* url_path) { | 
|  | return URLRequestPostInterceptorFactory::CreateInterceptor( | 
|  | base::FilePath::FromUTF8Unsafe(url_path)); | 
|  | } | 
|  |  | 
|  | }  // namespace update_client |