root/chrome/browser/policy/cloud/test_request_interceptor.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. ErrorJobCallback
  2. BadRequestJobCallback
  3. FileJobCallback
  4. ValidRequest
  5. RegisterJobCallback
  6. io_task_runner_
  7. MaybeCreateJob
  8. GetPendingSize
  9. PushJobCallback
  10. io_task_runner_
  11. GetPendingSize
  12. PushJobCallback
  13. ErrorJob
  14. BadRequestJob
  15. RegisterJob
  16. FileJob
  17. PostToIOAndWait

// 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

/* [<][>][^][v][top][bottom][index][help] */