| // Copyright (c) 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 "chrome/browser/policy/cloud/test_request_interceptor.h" |
| |
| #include <limits> |
| #include <queue> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/sequenced_task_runner.h" |
| #include "content/test/net/url_request_mock_http_job.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/upload_bytes_element_reader.h" |
| #include "net/base/upload_data_stream.h" |
| #include "net/base/upload_element_reader.h" |
| #include "net/url_request/url_request_error_job.h" |
| #include "net/url_request/url_request_filter.h" |
| #include "net/url_request/url_request_job_factory.h" |
| #include "net/url_request/url_request_test_job.h" |
| #include "url/gurl.h" |
| |
| namespace em = enterprise_management; |
| |
| namespace policy { |
| |
| namespace { |
| |
| // Helper callback for jobs that should fail with a network |error|. |
| net::URLRequestJob* ErrorJobCallback(int error, |
| net::URLRequest* request, |
| net::NetworkDelegate* network_delegate) { |
| return new net::URLRequestErrorJob(request, network_delegate, error); |
| } |
| |
| // Helper callback for jobs that should fail with a 400 HTTP error. |
| net::URLRequestJob* BadRequestJobCallback( |
| net::URLRequest* request, |
| net::NetworkDelegate* network_delegate) { |
| static const char kBadHeaders[] = |
| "HTTP/1.1 400 Bad request\0" |
| "Content-type: application/protobuf\0" |
| "\0"; |
| std::string headers(kBadHeaders, arraysize(kBadHeaders)); |
| return new net::URLRequestTestJob( |
| request, network_delegate, headers, std::string(), true); |
| } |
| |
| net::URLRequestJob* FileJobCallback(const base::FilePath& file_path, |
| net::URLRequest* request, |
| net::NetworkDelegate* network_delegate) { |
| return new content::URLRequestMockHTTPJob( |
| request, |
| network_delegate, |
| file_path); |
| } |
| |
| // Parses the upload data in |request| into |request_msg|, and validates the |
| // request. The query string in the URL must contain the |expected_type| for |
| // the "request" parameter. Returns true if all checks succeeded, and the |
| // request data has been parsed into |request_msg|. |
| bool ValidRequest(net::URLRequest* request, |
| const std::string& expected_type, |
| em::DeviceManagementRequest* request_msg) { |
| if (request->method() != "POST") |
| return false; |
| std::string spec = request->url().spec(); |
| if (spec.find("request=" + expected_type) == std::string::npos) |
| return false; |
| |
| // This assumes that the payload data was set from a single string. In that |
| // case the UploadDataStream has a single UploadBytesElementReader with the |
| // data in memory. |
| const net::UploadDataStream* stream = request->get_upload(); |
| if (!stream) |
| return false; |
| const ScopedVector<net::UploadElementReader>& readers = |
| stream->element_readers(); |
| if (readers.size() != 1u) |
| return false; |
| const net::UploadBytesElementReader* reader = readers[0]->AsBytesReader(); |
| if (!reader) |
| return false; |
| std::string data(reader->bytes(), reader->length()); |
| if (!request_msg->ParseFromString(data)) |
| return false; |
| |
| return true; |
| } |
| |
| // Helper callback for register jobs that should suceed. Validates the request |
| // parameters and returns an appropriate response job. If |expect_reregister| |
| // is true then the reregister flag must be set in the DeviceRegisterRequest |
| // protobuf. |
| net::URLRequestJob* RegisterJobCallback( |
| em::DeviceRegisterRequest::Type expected_type, |
| bool expect_reregister, |
| net::URLRequest* request, |
| net::NetworkDelegate* network_delegate) { |
| em::DeviceManagementRequest request_msg; |
| if (!ValidRequest(request, "register", &request_msg)) |
| return BadRequestJobCallback(request, network_delegate); |
| |
| if (!request_msg.has_register_request() || |
| request_msg.has_unregister_request() || |
| request_msg.has_policy_request() || |
| request_msg.has_device_status_report_request() || |
| request_msg.has_session_status_report_request() || |
| request_msg.has_auto_enrollment_request()) { |
| return BadRequestJobCallback(request, network_delegate); |
| } |
| |
| const em::DeviceRegisterRequest& register_request = |
| request_msg.register_request(); |
| if (expect_reregister && |
| (!register_request.has_reregister() || !register_request.reregister())) { |
| return BadRequestJobCallback(request, network_delegate); |
| } else if (!expect_reregister && |
| register_request.has_reregister() && |
| register_request.reregister()) { |
| return BadRequestJobCallback(request, network_delegate); |
| } |
| |
| if (!register_request.has_type() || register_request.type() != expected_type) |
| return BadRequestJobCallback(request, network_delegate); |
| |
| em::DeviceManagementResponse response; |
| em::DeviceRegisterResponse* register_response = |
| response.mutable_register_response(); |
| register_response->set_device_management_token("s3cr3t70k3n"); |
| std::string data; |
| response.SerializeToString(&data); |
| |
| static const char kGoodHeaders[] = |
| "HTTP/1.1 200 OK\0" |
| "Content-type: application/protobuf\0" |
| "\0"; |
| std::string headers(kGoodHeaders, arraysize(kGoodHeaders)); |
| return new net::URLRequestTestJob( |
| request, network_delegate, headers, data, true); |
| } |
| |
| } // namespace |
| |
| class TestRequestInterceptor::Delegate |
| : public net::URLRequestJobFactory::ProtocolHandler { |
| public: |
| Delegate(const std::string& hostname, |
| scoped_refptr<base::SequencedTaskRunner> io_task_runner); |
| virtual ~Delegate(); |
| |
| // ProtocolHandler implementation: |
| virtual net::URLRequestJob* MaybeCreateJob( |
| net::URLRequest* request, |
| net::NetworkDelegate* network_delegate) const OVERRIDE; |
| |
| void GetPendingSize(size_t* pending_size) const; |
| void PushJobCallback(const JobCallback& callback); |
| |
| private: |
| const std::string hostname_; |
| scoped_refptr<base::SequencedTaskRunner> io_task_runner_; |
| |
| // The queue of pending callbacks. 'mutable' because MaybeCreateJob() is a |
| // const method; it can't reenter though, because it runs exclusively on |
| // the IO thread. |
| mutable std::queue<JobCallback> pending_job_callbacks_; |
| }; |
| |
| TestRequestInterceptor::Delegate::Delegate( |
| const std::string& hostname, |
| scoped_refptr<base::SequencedTaskRunner> io_task_runner) |
| : hostname_(hostname), io_task_runner_(io_task_runner) {} |
| |
| TestRequestInterceptor::Delegate::~Delegate() {} |
| |
| net::URLRequestJob* TestRequestInterceptor::Delegate::MaybeCreateJob( |
| net::URLRequest* request, |
| net::NetworkDelegate* network_delegate) const { |
| CHECK(io_task_runner_->RunsTasksOnCurrentThread()); |
| |
| if (request->url().host() != hostname_) { |
| // Reject requests to other servers. |
| return ErrorJobCallback( |
| net::ERR_CONNECTION_REFUSED, request, network_delegate); |
| } |
| |
| if (pending_job_callbacks_.empty()) { |
| // Reject dmserver requests by default. |
| return BadRequestJobCallback(request, network_delegate); |
| } |
| |
| JobCallback callback = pending_job_callbacks_.front(); |
| pending_job_callbacks_.pop(); |
| return callback.Run(request, network_delegate); |
| } |
| |
| void TestRequestInterceptor::Delegate::GetPendingSize( |
| size_t* pending_size) const { |
| CHECK(io_task_runner_->RunsTasksOnCurrentThread()); |
| *pending_size = pending_job_callbacks_.size(); |
| } |
| |
| void TestRequestInterceptor::Delegate::PushJobCallback( |
| const JobCallback& callback) { |
| CHECK(io_task_runner_->RunsTasksOnCurrentThread()); |
| pending_job_callbacks_.push(callback); |
| } |
| |
| TestRequestInterceptor::TestRequestInterceptor(const std::string& hostname, |
| scoped_refptr<base::SequencedTaskRunner> io_task_runner) |
| : hostname_(hostname), |
| io_task_runner_(io_task_runner) { |
| delegate_ = new Delegate(hostname_, io_task_runner_); |
| scoped_ptr<net::URLRequestJobFactory::ProtocolHandler> handler(delegate_); |
| PostToIOAndWait( |
| base::Bind(&net::URLRequestFilter::AddHostnameProtocolHandler, |
| base::Unretained(net::URLRequestFilter::GetInstance()), |
| "http", hostname_, base::Passed(&handler))); |
| } |
| |
| TestRequestInterceptor::~TestRequestInterceptor() { |
| // RemoveHostnameHandler() destroys the |delegate_|, which is owned by |
| // the URLRequestFilter. |
| delegate_ = NULL; |
| PostToIOAndWait( |
| base::Bind(&net::URLRequestFilter::RemoveHostnameHandler, |
| base::Unretained(net::URLRequestFilter::GetInstance()), |
| "http", hostname_)); |
| } |
| |
| size_t TestRequestInterceptor::GetPendingSize() { |
| size_t pending_size = std::numeric_limits<size_t>::max(); |
| PostToIOAndWait(base::Bind(&Delegate::GetPendingSize, |
| base::Unretained(delegate_), |
| &pending_size)); |
| return pending_size; |
| } |
| |
| void TestRequestInterceptor::PushJobCallback(const JobCallback& callback) { |
| PostToIOAndWait(base::Bind(&Delegate::PushJobCallback, |
| base::Unretained(delegate_), |
| callback)); |
| } |
| |
| // static |
| TestRequestInterceptor::JobCallback TestRequestInterceptor::ErrorJob( |
| int error) { |
| return base::Bind(&ErrorJobCallback, error); |
| } |
| |
| // static |
| TestRequestInterceptor::JobCallback TestRequestInterceptor::BadRequestJob() { |
| return base::Bind(&BadRequestJobCallback); |
| } |
| |
| // static |
| TestRequestInterceptor::JobCallback TestRequestInterceptor::RegisterJob( |
| em::DeviceRegisterRequest::Type expected_type, |
| bool expect_reregister) { |
| return base::Bind(&RegisterJobCallback, expected_type, expect_reregister); |
| } |
| |
| // static |
| TestRequestInterceptor::JobCallback TestRequestInterceptor::FileJob( |
| const base::FilePath& file_path) { |
| return base::Bind(&FileJobCallback, file_path); |
| } |
| |
| void TestRequestInterceptor::PostToIOAndWait(const base::Closure& task) { |
| io_task_runner_->PostTask(FROM_HERE, task); |
| base::RunLoop run_loop; |
| io_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind( |
| base::IgnoreResult(&base::MessageLoopProxy::PostTask), |
| base::MessageLoopProxy::current(), |
| FROM_HERE, |
| run_loop.QuitClosure())); |
| run_loop.Run(); |
| } |
| |
| } // namespace policy |