Merge branch 'merge-login_manager-rewrite' into merge-login_manager
BUG=chromium:330883
Change-Id: Ieaa53b7ebf96cd694b5b76c9750b440cd2384a43
diff --git a/buffet/HACKING b/buffet/HACKING
new file mode 100644
index 0000000..5bab79e
--- /dev/null
+++ b/buffet/HACKING
@@ -0,0 +1,24 @@
+Some common workflows for developing with buffet:
+
+# Tell portage that you'd like to make local changes to Buffet:
+cros_workon start --board=${BOARD} platform2
+
+# Edit files in platform2/buffet/
+vim ...
+
+# Compile and install those changes into the chroot:
+USE=buffet emerge-<board> platform2
+
+# Compile and run buffet unittests
+USE=buffet P2_TEST_FILTER="buffet::*" FEATURES=test emerge-<board> platform2
+
+# Deploy the most recently built version of buffet to a DUT:
+cros deploy --board=${BOARD} <remote host> platform2
+
+#To enable additional debug logging in buffet daemon, run it as:
+# buffet --v=<level>, where <level> is verbosity level of debug info:
+# 1 - enable additional tracing of internal object construction and destruction
+# 2 - add tracing of request and response data sent over HTTP (beware of
+# privacy concerns).
+# 3 - enable low-level CURL tracing for HTTP communication.
+buffet --v=2
diff --git a/buffet/README b/buffet/README
new file mode 100644
index 0000000..9dc1e87
--- /dev/null
+++ b/buffet/README
@@ -0,0 +1,6 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+This directory contains the a Brillo service for registering a device and
+sending/receiving remote commands.
diff --git a/buffet/async_event_sequencer.cc b/buffet/async_event_sequencer.cc
new file mode 100644
index 0000000..1dca62c
--- /dev/null
+++ b/buffet/async_event_sequencer.cc
@@ -0,0 +1,112 @@
+// Copyright 2014 The Chromium OS 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 "buffet/async_event_sequencer.h"
+
+namespace buffet {
+
+namespace dbus_utils {
+
+AsyncEventSequencer::AsyncEventSequencer() { }
+AsyncEventSequencer::~AsyncEventSequencer() { }
+
+AsyncEventSequencer::Handler AsyncEventSequencer::GetHandler(
+ const std::string& descriptive_message, bool failure_is_fatal) {
+ CHECK(!started_) << "Cannot create handlers after OnAllTasksCompletedCall()";
+ int unique_registration_id = ++registration_counter_;
+ outstanding_registrations_.insert(unique_registration_id);
+ return base::Bind(&AsyncEventSequencer::HandleFinish, this,
+ unique_registration_id, descriptive_message,
+ failure_is_fatal);
+}
+
+AsyncEventSequencer::ExportHandler AsyncEventSequencer::GetExportHandler(
+ const std::string& interface_name, const std::string& method_name,
+ const std::string& descriptive_message, bool failure_is_fatal) {
+ auto finish_handler = GetHandler(descriptive_message, failure_is_fatal);
+ return base::Bind(&AsyncEventSequencer::HandleDBusMethodExported, this,
+ finish_handler,
+ interface_name,
+ method_name);
+}
+
+void AsyncEventSequencer::OnAllTasksCompletedCall(
+ std::vector<CompletionAction> actions) {
+ CHECK(!started_) << "OnAllTasksCompletedCall called twice!";
+ started_ = true;
+ completion_actions_.assign(actions.begin(), actions.end());
+ // All of our callbacks might have been called already.
+ PossiblyRunCompletionActions();
+}
+
+namespace {
+void IgnoreSuccess(const AsyncEventSequencer::CompletionTask& task,
+ bool /*success*/) { task.Run(); }
+} // namespace
+
+AsyncEventSequencer::CompletionAction AsyncEventSequencer::WrapCompletionTask(
+ const CompletionTask& task) {
+ return base::Bind(&IgnoreSuccess, task);
+}
+
+void AsyncEventSequencer::HandleFinish(int registration_number,
+ const std::string& error_message,
+ bool failure_is_fatal, bool success) {
+ RetireRegistration(registration_number);
+ CheckForFailure(failure_is_fatal, success, error_message);
+ PossiblyRunCompletionActions();
+}
+
+void AsyncEventSequencer::HandleDBusMethodExported(
+ const AsyncEventSequencer::Handler& finish_handler,
+ const std::string& expected_interface_name,
+ const std::string& expected_method_name,
+ const std::string& actual_interface_name,
+ const std::string& actual_method_name, bool success) {
+ CHECK_EQ(expected_method_name, actual_method_name)
+ << "Exported DBus method '" << actual_method_name << "' "
+ << "but expected '" << expected_method_name << "'";
+ CHECK_EQ(expected_interface_name, actual_interface_name)
+ << "Exported method DBus interface '" << actual_interface_name << "' "
+ << "but expected '" << expected_interface_name << "'";
+ finish_handler.Run(success);
+}
+
+
+void AsyncEventSequencer::RetireRegistration(int registration_number) {
+ const size_t handlers_retired = outstanding_registrations_.erase(
+ registration_number);
+ CHECK_EQ(1, handlers_retired)
+ << "Tried to retire invalid handler " << registration_number << ")";
+}
+
+void AsyncEventSequencer::CheckForFailure(bool failure_is_fatal, bool success,
+ const std::string& error_message) {
+ if (failure_is_fatal) {
+ CHECK(success) << error_message;
+ }
+ if (!success) {
+ LOG(ERROR) << error_message;
+ had_failures_ = true;
+ }
+}
+
+void AsyncEventSequencer::PossiblyRunCompletionActions() {
+ if (!started_ || !outstanding_registrations_.empty()) {
+ // Don't run completion actions if we have any outstanding
+ // Handlers outstanding or if any more handlers might
+ // be scheduled in the future.
+ return;
+ }
+ for (auto&& completion_action : completion_actions_) {
+ // Should this be put on the message loop or run directly?
+ completion_action.Run(!had_failures_);
+ }
+ // Discard our references to those actions.
+ completion_actions_.clear();
+}
+
+} // namespace dbus_utils
+
+} // namespace buffet
diff --git a/buffet/async_event_sequencer.h b/buffet/async_event_sequencer.h
new file mode 100644
index 0000000..96adad7
--- /dev/null
+++ b/buffet/async_event_sequencer.h
@@ -0,0 +1,105 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_ASYNC_EVENT_SEQUENCER_H_
+#define BUFFET_ASYNC_EVENT_SEQUENCER_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/basictypes.h>
+#include <base/memory/ref_counted.h>
+
+namespace buffet {
+
+namespace dbus_utils {
+
+// A helper class for coordinating the multiple async tasks. A consumer
+// may grab any number of callbacks via Get*Handler() and schedule a list
+// of completion actions to take. When all handlers obtained via Get*Handler()
+// have been called, the AsyncEventSequencer will call its CompletionActions.
+//
+// Usage:
+//
+// void Init(const base::Callback<void(bool success)> cb) {
+// scoped_refptr<AsyncEventSequencer> sequencer(
+// new AsyncEventSequencer());
+// one_delegate_needing_init_.Init(sequencer->GetHandler(
+// "my delegate failed to init", false));
+// dbus_init_delegate_.Init(sequencer->GetExportHandler(
+// "org.test.Interface", "ExposedMethodName",
+// "another delegate is flaky", false));
+// sequencer->OnAllTasksCompletedCall({cb});
+// }
+class AsyncEventSequencer : public base::RefCounted<AsyncEventSequencer> {
+ public:
+ typedef base::Callback<void(bool success)> Handler;
+ typedef base::Callback<void (const std::string& interface_name,
+ const std::string& method_name,
+ bool success)> ExportHandler;
+ typedef base::Callback<void(bool all_succeeded)> CompletionAction;
+ typedef base::Callback<void(void)> CompletionTask;
+
+ AsyncEventSequencer();
+
+ // Get a Finished handler callback. Each callback is "unique" in the sense
+ // that subsequent calls to GetHandler() will create new handlers
+ // which will need to be called before completion actions are run.
+ Handler GetHandler(const std::string& descriptive_message,
+ bool failure_is_fatal);
+
+ // Like GetHandler except with a signature tailored to
+ // ExportedObject's ExportMethod callback requirements. Will also assert
+ // that the passed interface/method names from ExportedObject are correct.
+ ExportHandler GetExportHandler(
+ const std::string& interface_name, const std::string& method_name,
+ const std::string& descriptive_message, bool failure_is_fatal);
+
+ // Once all handlers obtained via GetHandler have run,
+ // we'll run each CompletionAction, then discard our references.
+ // No more handlers may be obtained after this call.
+ void OnAllTasksCompletedCall(std::vector<CompletionAction> actions);
+
+ // Wrap a CompletionTask with a function that discards the result.
+ // This CompletionTask retains no references to the AsyncEventSequencer.
+ CompletionAction WrapCompletionTask(const CompletionTask& task);
+
+ private:
+ // We'll partially bind this function before giving it back via
+ // GetHandler. Note that the returned callbacks have
+ // references to *this, which gives us the neat property that we'll
+ // destroy *this only when all our callbacks have been destroyed.
+ void HandleFinish(int registration_number, const std::string& error_message,
+ bool failure_is_fatal, bool success);
+ // Similar to HandleFinish.
+ void HandleDBusMethodExported(
+ const Handler& finish_handler,
+ const std::string& expected_interface_name,
+ const std::string& expected_method_name,
+ const std::string& actual_interface_name,
+ const std::string& actual_method_name,
+ bool success);
+ void RetireRegistration(int registration_number);
+ void CheckForFailure(bool failure_is_fatal, bool success,
+ const std::string& error_message);
+ void PossiblyRunCompletionActions();
+
+ bool started_{false}; // NOLINT - initializer list
+ int registration_counter_{0}; // NOLINT - initializer list
+ std::set<int> outstanding_registrations_;
+ std::vector<CompletionAction> completion_actions_;
+ bool had_failures_{false}; // NOLINT - initializer list
+ // Ref counted objects have private destructors.
+ ~AsyncEventSequencer();
+ friend class base::RefCounted<AsyncEventSequencer>;
+ DISALLOW_COPY_AND_ASSIGN(AsyncEventSequencer);
+};
+
+} // namespace dbus_utils
+
+} // namespace buffet
+
+#endif // BUFFET_ASYNC_EVENT_SEQUENCER_H_
diff --git a/buffet/async_event_sequencer_unittest.cc b/buffet/async_event_sequencer_unittest.cc
new file mode 100644
index 0000000..d95ff56
--- /dev/null
+++ b/buffet/async_event_sequencer_unittest.cc
@@ -0,0 +1,96 @@
+// Copyright 2014 The Chromium OS 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 "buffet/async_event_sequencer.h"
+
+#include <base/bind_helpers.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace buffet {
+
+namespace dbus_utils {
+
+namespace {
+
+const char kTestInterface[] = "org.test.if";
+const char kTestMethod1[] = "TestMethod1";
+const char kTestMethod2[] = "TestMethod2";
+
+} // namespace
+
+class AsyncEventSequencerTest : public ::testing::Test {
+ public:
+ MOCK_METHOD1(HandleCompletion, void(bool all_succeeded));
+
+ void SetUp() {
+ aec_ = new AsyncEventSequencer();
+ cb_ = base::Bind(&AsyncEventSequencerTest::HandleCompletion,
+ base::Unretained(this));
+ }
+
+ scoped_refptr<AsyncEventSequencer> aec_;
+ AsyncEventSequencer::CompletionAction cb_;
+};
+
+TEST_F(AsyncEventSequencerTest, WaitForCompletionActions) {
+ auto finished_handler = aec_->GetHandler("handler failed", false);
+ finished_handler.Run(true);
+ EXPECT_CALL(*this, HandleCompletion(true)).Times(1);
+ aec_->OnAllTasksCompletedCall({cb_});
+}
+
+TEST_F(AsyncEventSequencerTest, MultiInitActionsSucceed) {
+ auto finished_handler1 = aec_->GetHandler("handler failed", false);
+ auto finished_handler2 = aec_->GetHandler("handler failed", false);
+ aec_->OnAllTasksCompletedCall({cb_});
+ finished_handler1.Run(true);
+ EXPECT_CALL(*this, HandleCompletion(true)).Times(1);
+ finished_handler2.Run(true);
+}
+
+TEST_F(AsyncEventSequencerTest, SomeInitActionsFail) {
+ auto finished_handler1 = aec_->GetHandler("handler failed", false);
+ auto finished_handler2 = aec_->GetHandler("handler failed", false);
+ aec_->OnAllTasksCompletedCall({cb_});
+ finished_handler1.Run(false);
+ EXPECT_CALL(*this, HandleCompletion(false)).Times(1);
+ finished_handler2.Run(true);
+}
+
+TEST_F(AsyncEventSequencerTest, MultiDBusActionsSucceed) {
+ auto handler1 = aec_->GetExportHandler(
+ kTestInterface, kTestMethod1, "method export failed", false);
+ auto handler2 = aec_->GetExportHandler(
+ kTestInterface, kTestMethod2, "method export failed", false);
+ aec_->OnAllTasksCompletedCall({cb_});
+ handler1.Run(kTestInterface, kTestMethod1, true);
+ EXPECT_CALL(*this, HandleCompletion(true)).Times(1);
+ handler2.Run(kTestInterface, kTestMethod2, true);
+}
+
+TEST_F(AsyncEventSequencerTest, SomeDBusActionsFail) {
+ auto handler1 = aec_->GetExportHandler(
+ kTestInterface, kTestMethod1, "method export failed", false);
+ auto handler2 = aec_->GetExportHandler(
+ kTestInterface, kTestMethod2, "method export failed", false);
+ aec_->OnAllTasksCompletedCall({cb_});
+ handler1.Run(kTestInterface, kTestMethod1, true);
+ EXPECT_CALL(*this, HandleCompletion(false)).Times(1);
+ handler2.Run(kTestInterface, kTestMethod2, false);
+}
+
+TEST_F(AsyncEventSequencerTest, MixedActions) {
+ auto handler1 = aec_->GetExportHandler(
+ kTestInterface, kTestMethod1, "method export failed", false);
+ auto handler2 = aec_->GetHandler("handler failed", false);
+ aec_->OnAllTasksCompletedCall({cb_});
+ handler1.Run(kTestInterface, kTestMethod1, true);
+ EXPECT_CALL(*this, HandleCompletion(true)).Times(1);
+ handler2.Run(true);
+}
+
+} // namespace dbus_utils
+
+} // namespace buffet
diff --git a/buffet/bind_lambda.h b/buffet/bind_lambda.h
new file mode 100644
index 0000000..0172ab3
--- /dev/null
+++ b/buffet/bind_lambda.h
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_BIND_LAMBDA_H_
+#define BUFFET_BIND_LAMBDA_H_
+
+#include <base/bind.h>
+
+////////////////////////////////////////////////////////////////////////////////
+// This file is an extension to base/bind_internal.h and adds a RunnableAdapter
+// class specialization that wraps a functor (including lambda objects), so
+// they can be used in base::Callback/base::Bind constructs.
+// By including this file you will gain the ability to write expressions like:
+// base::Callback<int(int)> callback = base::Bind([](int value) {
+// return value * value;
+// });
+////////////////////////////////////////////////////////////////////////////////
+namespace base {
+namespace internal {
+
+// LambdaAdapter is a helper class that specializes on different function call
+// signatures and provides the RunType and Run() method required by
+// RunnableAdapter<> class.
+template <typename Lambda, typename Sig>
+class LambdaAdapter;
+
+// R(...)
+template <typename Lambda, typename R, typename... Args>
+class LambdaAdapter<Lambda, R(Lambda::*)(Args... args)> {
+ public:
+ typedef R(RunType)(Args...);
+ LambdaAdapter(Lambda lambda) : lambda_(lambda) {}
+ R Run(Args... args) { return lambda_(args...); }
+
+ private:
+ Lambda lambda_;
+};
+
+// R(...) const
+template <typename Lambda, typename R, typename... Args>
+class LambdaAdapter<Lambda, R(Lambda::*)(Args... args) const> {
+ public:
+ typedef R(RunType)(Args...);
+ LambdaAdapter(Lambda lambda) : lambda_(lambda) {}
+ R Run(Args... args) { return lambda_(args...); }
+
+ private:
+ Lambda lambda_;
+};
+
+template <typename Lambda>
+class RunnableAdapter : public LambdaAdapter<Lambda,
+ decltype(&Lambda::operator())> {
+ public:
+ explicit RunnableAdapter(Lambda lambda) :
+ LambdaAdapter<Lambda, decltype(&Lambda::operator())>(lambda) {
+ }
+};
+
+
+} // namespace internal
+} // namespace base
+
+#endif // BUFFET_BIND_LAMBDA_H_
diff --git a/buffet/buffet.conf b/buffet/buffet.conf
new file mode 100644
index 0000000..66cd9ed
--- /dev/null
+++ b/buffet/buffet.conf
@@ -0,0 +1,16 @@
+# Copyright 2014 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+description "Brillo Buffet Service"
+author "chromium-os-dev@chromium.org"
+
+start on starting system-services
+stop on stopping system-services
+respawn
+
+pre-start script
+ mkdir -p /var/lib/buffet
+end script
+
+exec buffet
diff --git a/buffet/buffet.gyp b/buffet/buffet.gyp
new file mode 100644
index 0000000..7e5c71a
--- /dev/null
+++ b/buffet/buffet.gyp
@@ -0,0 +1,94 @@
+{
+ 'target_defaults': {
+ 'dependencies': [
+ '<(platform_root)/metrics/libmetrics-<(libbase_ver).gyp:libmetrics-<(libbase_ver)',
+ ],
+ 'variables': {
+ 'deps': [
+ 'dbus-1',
+ 'libchrome-<(libbase_ver)',
+ 'libchrome-test-<(libbase_ver)',
+ 'libcurl',
+ ],
+ },
+ # TODO(sosa): Remove gflags: crbug.com/356745.
+ 'link_settings': {
+ 'libraries': [
+ '-lgflags',
+ '-lbase-dbus_test_support-<(libbase_ver)',
+ ],
+ },
+ 'cflags_cc': [
+ '-std=gnu++11',
+ ],
+ },
+ 'targets': [
+ {
+ 'target_name': 'buffet_common',
+ 'type': 'static_library',
+ 'sources': [
+ 'async_event_sequencer.cc',
+ 'data_encoding.cc',
+ 'dbus_constants.cc',
+ 'dbus_utils.cc',
+ 'device_registration_info.cc',
+ 'error.cc',
+ 'exported_object_manager.cc',
+ 'exported_property_set.cc',
+ 'http_request.cc',
+ 'http_connection_curl.cc',
+ 'http_transport_curl.cc',
+ 'http_utils.cc',
+ 'manager.cc',
+ 'mime_utils.cc',
+ 'storage_impls.cc',
+ 'string_utils.cc',
+ 'url_utils.cc'
+ ],
+ },
+ {
+ 'target_name': 'buffet',
+ 'type': 'executable',
+ 'sources': [
+ 'main.cc',
+ ],
+ 'dependencies': [
+ 'buffet_common',
+ ],
+ },
+ {
+ 'target_name': 'buffet_client',
+ 'type': 'executable',
+ 'sources': [
+ 'buffet_client.cc',
+ 'dbus_constants.cc',
+ ],
+ 'dependencies': [
+ 'buffet_common',
+ ],
+ },
+ {
+ 'target_name': 'buffet_testrunner',
+ 'type': 'executable',
+ 'dependencies': [
+ 'buffet_common',
+ ],
+ 'includes': ['../common-mk/common_test.gypi'],
+ 'sources': [
+ 'async_event_sequencer_unittest.cc',
+ 'buffet_testrunner.cc',
+ 'data_encoding_unittest.cc',
+ 'device_registration_info_unittest.cc',
+ 'error_unittest.cc',
+ 'exported_object_manager_unittest.cc',
+ 'exported_property_set_unittest.cc',
+ 'http_connection_fake.cc',
+ 'http_transport_fake.cc',
+ 'http_utils_unittest.cc',
+ 'mime_utils_unittest.cc',
+ 'string_utils_unittest.cc',
+ 'url_utils_unittest.cc'
+ ],
+ },
+ ],
+}
diff --git a/buffet/buffet_client.cc b/buffet/buffet_client.cc
new file mode 100644
index 0000000..a763e42
--- /dev/null
+++ b/buffet/buffet_client.cc
@@ -0,0 +1,311 @@
+// Copyright 2014 The Chromium OS 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 <iostream> // NOLINT(readability/streams)
+#include <string>
+#include <sysexits.h>
+
+#include <base/command_line.h>
+#include <base/logging.h>
+#include <base/memory/ref_counted.h>
+#include <base/memory/scoped_ptr.h>
+#include <base/values.h>
+#include <dbus/bus.h>
+#include <dbus/message.h>
+#include <dbus/object_proxy.h>
+#include <dbus/object_manager.h>
+#include <dbus/values_util.h>
+
+#include "buffet/dbus_constants.h"
+#include "buffet/data_encoding.h"
+
+using namespace buffet::dbus_constants; // NOLINT(build/namespaces)
+
+namespace {
+static const int default_timeout_ms = 1000;
+
+void usage() {
+ std::cerr << "Possible commands:" << std::endl;
+ std::cerr << " " << kManagerTestMethod << std::endl;
+ std::cerr << " " << kManagerCheckDeviceRegistered << std::endl;
+ std::cerr << " " << kManagerGetDeviceInfo << std::endl;
+ std::cerr << " " << kManagerStartRegisterDevice
+ << " param1 = val1¶m2 = val2..." << std::endl;
+ std::cerr << " " << kManagerFinishRegisterDevice
+ << " device_id" << std::endl;
+ std::cerr << " " << kManagerUpdateStateMethod << std::endl;
+ std::cerr << " " << dbus::kObjectManagerGetManagedObjects << std::endl;
+}
+
+class BuffetHelperProxy {
+ public:
+ int Init() {
+ dbus::Bus::Options options;
+ options.bus_type = dbus::Bus::SYSTEM;
+ bus_ = new dbus::Bus(options);
+ manager_proxy_ = bus_->GetObjectProxy(
+ kServiceName,
+ dbus::ObjectPath(kManagerServicePath));
+ root_proxy_ = bus_->GetObjectProxy(
+ kServiceName,
+ dbus::ObjectPath(kRootServicePath));
+ return EX_OK;
+ }
+
+ int CallTestMethod(const CommandLine::StringVector& args) {
+ dbus::MethodCall method_call(kManagerInterface, kManagerTestMethod);
+ scoped_ptr<dbus::Response> response(
+ manager_proxy_->CallMethodAndBlock(&method_call, default_timeout_ms));
+ if (!response) {
+ std::cout << "Failed to receive a response." << std::endl;
+ return EX_UNAVAILABLE;
+ }
+ std::cout << "Received a response." << std::endl;
+ return EX_OK;
+ }
+
+ int CallManagerCheckDeviceRegistered(const CommandLine::StringVector& args) {
+ if (!args.empty()) {
+ std::cerr << "Invalid number of arguments for "
+ << "Manager." << kManagerCheckDeviceRegistered << std::endl;
+ usage();
+ return EX_USAGE;
+ }
+ dbus::MethodCall method_call(
+ kManagerInterface, kManagerCheckDeviceRegistered);
+
+ scoped_ptr<dbus::Response> response(
+ manager_proxy_->CallMethodAndBlock(&method_call, default_timeout_ms));
+ if (!response) {
+ std::cout << "Failed to receive a response." << std::endl;
+ return EX_UNAVAILABLE;
+ }
+
+ dbus::MessageReader reader(response.get());
+ std::string device_id;
+ if (!reader.PopString(&device_id)) {
+ std::cout << "No device ID in response." << std::endl;
+ return EX_SOFTWARE;
+ }
+
+ std::cout << "Device ID: "
+ << (device_id.empty() ? std::string("<unregistered>") : device_id)
+ << std::endl;
+ return EX_OK;
+ }
+
+ int CallManagerGetDeviceInfo(const CommandLine::StringVector& args) {
+ if (!args.empty()) {
+ std::cerr << "Invalid number of arguments for "
+ << "Manager." << kManagerGetDeviceInfo << std::endl;
+ usage();
+ return EX_USAGE;
+ }
+ dbus::MethodCall method_call(
+ kManagerInterface, kManagerGetDeviceInfo);
+
+ scoped_ptr<dbus::Response> response(
+ manager_proxy_->CallMethodAndBlock(&method_call, default_timeout_ms));
+ if (!response) {
+ std::cout << "Failed to receive a response." << std::endl;
+ return EX_UNAVAILABLE;
+ }
+
+ dbus::MessageReader reader(response.get());
+ std::string device_info;
+ if (!reader.PopString(&device_info)) {
+ std::cout << "No device info in response." << std::endl;
+ return EX_SOFTWARE;
+ }
+
+ std::cout << "Device Info: "
+ << (device_info.empty() ? std::string("<unregistered>") : device_info)
+ << std::endl;
+ return EX_OK;
+ }
+
+ int CallManagerStartRegisterDevice(const CommandLine::StringVector& args) {
+ if (args.size() > 1) {
+ std::cerr << "Invalid number of arguments for "
+ << "Manager." << kManagerStartRegisterDevice << std::endl;
+ usage();
+ return EX_USAGE;
+ }
+ std::map<std::string, std::shared_ptr<base::Value>> params;
+
+ if (!args.empty()) {
+ auto key_values = buffet::data_encoding::WebParamsDecode(args.front());
+ for (auto&& pair : key_values) {
+ params.insert(std::make_pair(
+ pair.first, std::shared_ptr<base::Value>(
+ base::Value::CreateStringValue(pair.second))));
+ }
+ }
+
+ dbus::MethodCall method_call(
+ kManagerInterface, kManagerStartRegisterDevice);
+ dbus::MessageWriter writer(&method_call);
+ dbus::MessageWriter dict_writer(nullptr);
+ writer.OpenArray("{sv}", &dict_writer);
+ for (auto&& pair : params) {
+ dbus::MessageWriter dict_entry_writer(nullptr);
+ dict_writer.OpenDictEntry(&dict_entry_writer);
+ dict_entry_writer.AppendString(pair.first);
+ dbus::AppendBasicTypeValueDataAsVariant(&dict_entry_writer,
+ *pair.second.get());
+ dict_writer.CloseContainer(&dict_entry_writer);
+ }
+ writer.CloseContainer(&dict_writer);
+
+ static const int timeout_ms = 3000;
+ scoped_ptr<dbus::Response> response(
+ manager_proxy_->CallMethodAndBlock(&method_call, timeout_ms));
+ if (!response) {
+ std::cout << "Failed to receive a response." << std::endl;
+ return EX_UNAVAILABLE;
+ }
+
+ dbus::MessageReader reader(response.get());
+ std::string info;
+ if (!reader.PopString(&info)) {
+ std::cout << "No valid response." << std::endl;
+ return EX_SOFTWARE;
+ }
+
+ std::cout << "Registration started: " << info << std::endl;
+ return EX_OK;
+ }
+
+ int CallManagerFinishRegisterDevice(const CommandLine::StringVector& args) {
+ if (args.size() > 1) {
+ std::cerr << "Invalid number of arguments for "
+ << "Manager." << kManagerFinishRegisterDevice << std::endl;
+ usage();
+ return EX_USAGE;
+ }
+ dbus::MethodCall method_call(
+ kManagerInterface, kManagerFinishRegisterDevice);
+ dbus::MessageWriter writer(&method_call);
+ std::string user_auth_code;
+ if (!args.empty()) { user_auth_code = args.front(); }
+ writer.AppendString(user_auth_code);
+ static const int timeout_ms = 10000;
+ scoped_ptr<dbus::Response> response(
+ manager_proxy_->CallMethodAndBlock(&method_call, timeout_ms));
+ if (!response) {
+ std::cout << "Failed to receive a response." << std::endl;
+ return EX_UNAVAILABLE;
+ }
+
+ dbus::MessageReader reader(response.get());
+ std::string device_id;
+ if (!reader.PopString(&device_id)) {
+ std::cout << "No device ID in response." << std::endl;
+ return EX_SOFTWARE;
+ }
+
+ std::cout << "Device ID is "
+ << (device_id.empty() ? std::string("<unregistered>") : device_id)
+ << std::endl;
+ return EX_OK;
+ }
+
+ int CallManagerUpdateState(const CommandLine::StringVector& args) {
+ if (args.size() != 1) {
+ std::cerr << "Invalid number of arguments for "
+ << "Manager." << kManagerUpdateStateMethod << std::endl;
+ usage();
+ return EX_USAGE;
+ }
+ dbus::MethodCall method_call(
+ kManagerInterface, kManagerUpdateStateMethod);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString(args.front());
+ scoped_ptr<dbus::Response> response(
+ manager_proxy_->CallMethodAndBlock(&method_call, default_timeout_ms));
+ if (!response) {
+ std::cout << "Failed to receive a response." << std::endl;
+ return EX_UNAVAILABLE;
+ }
+ return EX_OK;
+ }
+
+ int CallRootGetManagedObjects(const CommandLine::StringVector& args) {
+ if (!args.empty()) {
+ std::cerr << "Invalid number of arguments for "
+ << dbus::kObjectManagerGetManagedObjects << std::endl;
+ usage();
+ return EX_USAGE;
+ }
+ dbus::MethodCall method_call(
+ dbus::kObjectManagerInterface, dbus::kObjectManagerGetManagedObjects);
+ scoped_ptr<dbus::Response> response(
+ root_proxy_->CallMethodAndBlock(&method_call, default_timeout_ms));
+ if (!response) {
+ std::cout << "Failed to receive a response." << std::endl;
+ return EX_UNAVAILABLE;
+ }
+ std::cout << response->ToString() << std::endl;
+ return EX_OK;
+ }
+
+ private:
+ scoped_refptr<dbus::Bus> bus_;
+ dbus::ObjectProxy* manager_proxy_{nullptr}; // NOLINT - initializer list
+ dbus::ObjectProxy* root_proxy_{nullptr}; // NOLINT - initializer list
+};
+
+} // namespace
+
+int main(int argc, char** argv) {
+ CommandLine::Init(argc, argv);
+ CommandLine* cl = CommandLine::ForCurrentProcess();
+ CommandLine::StringVector args = cl->GetArgs();
+ if (args.empty()) {
+ usage();
+ return EX_USAGE;
+ }
+
+ // Pop the command off of the args list.
+ std::string command = args.front();
+ args.erase(args.begin());
+ int err = EX_USAGE;
+ BuffetHelperProxy helper;
+ err = helper.Init();
+ if (err) {
+ std::cerr << "Error initializing proxies." << std::endl;
+ return err;
+ }
+
+ if (command.compare(kManagerTestMethod) == 0) {
+ err = helper.CallTestMethod(args);
+ } else if (command.compare(kManagerCheckDeviceRegistered) == 0 ||
+ command.compare("cr") == 0) {
+ err = helper.CallManagerCheckDeviceRegistered(args);
+ } else if (command.compare(kManagerGetDeviceInfo) == 0 ||
+ command.compare("di") == 0) {
+ err = helper.CallManagerGetDeviceInfo(args);
+ } else if (command.compare(kManagerStartRegisterDevice) == 0 ||
+ command.compare("sr") == 0) {
+ err = helper.CallManagerStartRegisterDevice(args);
+ } else if (command.compare(kManagerFinishRegisterDevice) == 0 ||
+ command.compare("fr") == 0) {
+ err = helper.CallManagerFinishRegisterDevice(args);
+ } else if (command.compare(kManagerUpdateStateMethod) == 0 ||
+ command.compare("us") == 0) {
+ err = helper.CallManagerUpdateState(args);
+ } else if (command.compare(dbus::kObjectManagerGetManagedObjects) == 0) {
+ err = helper.CallRootGetManagedObjects(args);
+ } else {
+ std::cerr << "Unknown command: " << command << std::endl;
+ usage();
+ }
+
+ if (err) {
+ std::cerr << "Done, with errors." << std::endl;
+ } else {
+ std::cout << "Done." << std::endl;
+ }
+ return err;
+}
diff --git a/buffet/buffet_testrunner.cc b/buffet/buffet_testrunner.cc
new file mode 100644
index 0000000..575f952
--- /dev/null
+++ b/buffet/buffet_testrunner.cc
@@ -0,0 +1,12 @@
+// Copyright (c) 2014 The Chromium OS 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 <base/at_exit.h>
+#include <gtest/gtest.h>
+
+int main(int argc, char **argv) {
+ base::AtExitManager exit_manager;
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/buffet/data_encoding.cc b/buffet/data_encoding.cc
new file mode 100644
index 0000000..9f351de
--- /dev/null
+++ b/buffet/data_encoding.cc
@@ -0,0 +1,101 @@
+// Copyright 2014 The Chromium OS 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 "buffet/data_encoding.h"
+
+#include <base/strings/stringprintf.h>
+#include <string.h>
+
+#include "buffet/string_utils.h"
+
+namespace {
+
+inline int HexToDec(int hex) {
+ int dec = -1;
+ if (hex >= '0' && hex <= '9') {
+ dec = hex - '0';
+ } else if (hex >= 'A' && hex <= 'F') {
+ dec = hex - 'A' + 10;
+ } else if (hex >= 'a' && hex <= 'f') {
+ dec = hex - 'a' + 10;
+ }
+ return dec;
+}
+
+} // namespace
+
+/////////////////////////////////////////////////////////////////////////
+namespace buffet {
+namespace data_encoding {
+
+std::string UrlEncode(const char* data, bool encodeSpaceAsPlus) {
+ std::string result;
+
+ while (*data) {
+ char c = *data++;
+ // According to RFC3986 (http://www.faqs.org/rfcs/rfc3986.html),
+ // section 2.3. - Unreserved Characters
+ if ((c >= '0' && c <= '9') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= 'a' && c <= 'z') ||
+ c == '-' || c == '.' || c == '_' || c == '~') {
+ result += c;
+ } else if (c == ' ' && encodeSpaceAsPlus) {
+ // For historical reasons, some URLs have spaces encoded as '+',
+ // this also applies to form data encoded as
+ // 'application/x-www-form-urlencoded'
+ result += '+';
+ } else {
+ base::StringAppendF(&result, "%%%02X",
+ static_cast<unsigned char>(c)); // Encode as %NN
+ }
+ }
+ return result;
+}
+
+std::string UrlDecode(const char* data) {
+ std::string result;
+ while (*data) {
+ char c = *data++;
+ int part1 = 0, part2 = 0;
+ // HexToDec would return -1 even for character 0 (end of string),
+ // so it is safe to access data[0] and data[1] without overrunning the buf.
+ if (c == '%' &&
+ (part1 = HexToDec(data[0])) >= 0 && (part2 = HexToDec(data[1])) >= 0) {
+ c = static_cast<char>((part1 << 4) | part2);
+ data += 2;
+ } else if (c == '+') {
+ c = ' ';
+ }
+ result += c;
+ }
+ return result;
+}
+
+std::string WebParamsEncode(const WebParamList& params,
+ bool encodeSpaceAsPlus) {
+ std::vector<std::string> pairs;
+ pairs.reserve(params.size());
+ for (auto&& p : params) {
+ std::string key = UrlEncode(p.first.c_str(), encodeSpaceAsPlus);
+ std::string value = UrlEncode(p.second.c_str(), encodeSpaceAsPlus);
+ pairs.push_back(string_utils::Join('=', key, value));
+ }
+
+ return string_utils::Join('&', pairs);
+}
+
+WebParamList WebParamsDecode(const std::string& data) {
+ WebParamList result;
+ std::vector<std::string> params = string_utils::Split(data, '&');
+ for (auto p : params) {
+ auto pair = string_utils::SplitAtFirst(p, '=');
+ result.emplace_back(UrlDecode(pair.first.c_str()),
+ UrlDecode(pair.second.c_str()));
+ }
+ return result;
+}
+
+} // namespace data_encoding
+} // namespace buffet
diff --git a/buffet/data_encoding.h b/buffet/data_encoding.h
new file mode 100644
index 0000000..e493e36
--- /dev/null
+++ b/buffet/data_encoding.h
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_DATA_ENCODING_H_
+#define BUFFET_DATA_ENCODING_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace buffet {
+namespace data_encoding {
+
+typedef std::vector<std::pair<std::string, std::string>> WebParamList;
+
+// Encode/escape string to be used in the query portion of a URL.
+// If |encodeSpaceAsPlus| is set to true, spaces are encoded as '+' instead
+// of "%20"
+std::string UrlEncode(const char* data, bool encodeSpaceAsPlus);
+
+inline std::string UrlEncode(const char* data) {
+ return UrlEncode(data, true);
+}
+
+// Decodes/unescapes a URL. Replaces all %XX sequences with actual characters.
+// Also replaces '+' with spaces.
+std::string UrlDecode(const char* data);
+
+// Converts a list of key-value pairs into a string compatible with
+// 'application/x-www-form-urlencoded' content encoding.
+std::string WebParamsEncode(const WebParamList& params, bool encodeSpaceAsPlus);
+
+inline std::string WebParamsEncode(const WebParamList& params) {
+ return WebParamsEncode(params, true);
+}
+
+// Parses a string of '&'-delimited key-value pairs (separated by '=') and
+// encoded in a way compatible with 'application/x-www-form-urlencoded'
+// content encoding.
+WebParamList WebParamsDecode(const std::string& data);
+
+} // namespace data_encoding
+} // namespace buffet
+
+#endif // BUFFET_DATA_ENCODING_H_
diff --git a/buffet/data_encoding_unittest.cc b/buffet/data_encoding_unittest.cc
new file mode 100644
index 0000000..ed6295c
--- /dev/null
+++ b/buffet/data_encoding_unittest.cc
@@ -0,0 +1,39 @@
+// Copyright (c) 2011 The Chromium OS 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 "buffet/data_encoding.h"
+
+#include <gtest/gtest.h>
+
+using namespace buffet::data_encoding; // NOLINT(build/namespaces)
+
+TEST(data_encoding, UrlEncoding) {
+ std::string test = "\"http://sample/path/0014.html \"";
+ std::string encoded = UrlEncode(test.c_str());
+ EXPECT_EQ("%22http%3A%2F%2Fsample%2Fpath%2F0014.html+%22",
+ encoded);
+ EXPECT_EQ(test, UrlDecode(encoded.c_str()));
+
+ test = "\"http://sample/path/0014.html \"";
+ encoded = UrlEncode(test.c_str(), false);
+ EXPECT_EQ("%22http%3A%2F%2Fsample%2Fpath%2F0014.html%20%22",
+ encoded);
+ EXPECT_EQ(test, UrlDecode(encoded.c_str()));
+}
+
+TEST(data_encoding, WebParamsEncoding) {
+ std::string encoded = WebParamsEncode({{"q", "test"},
+ {"path", "/usr/bin"},
+ {"#", "%"}});
+ EXPECT_EQ("q=test&path=%2Fusr%2Fbin&%23=%25", encoded);
+
+ auto params = WebParamsDecode(encoded);
+ EXPECT_EQ(3, params.size());
+ EXPECT_EQ("q", params[0].first);
+ EXPECT_EQ("test", params[0].second);
+ EXPECT_EQ("path", params[1].first);
+ EXPECT_EQ("/usr/bin", params[1].second);
+ EXPECT_EQ("#", params[2].first);
+ EXPECT_EQ("%", params[2].second);
+}
diff --git a/buffet/dbus/org.chromium.Buffet.conf b/buffet/dbus/org.chromium.Buffet.conf
new file mode 100644
index 0000000..0f6ea4d
--- /dev/null
+++ b/buffet/dbus/org.chromium.Buffet.conf
@@ -0,0 +1,13 @@
+<!DOCTYPE busconfig PUBLIC
+ "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+ <policy user="root">
+ <allow own="org.chromium.Buffet" />
+ <allow send_destination="org.chromium.Buffet" />
+ </policy>
+
+ <policy context="default">
+ <allow send_destination="org.chromium.Buffet" />
+ </policy>
+</busconfig>
\ No newline at end of file
diff --git a/buffet/dbus_constants.cc b/buffet/dbus_constants.cc
new file mode 100644
index 0000000..d8c70ba
--- /dev/null
+++ b/buffet/dbus_constants.cc
@@ -0,0 +1,27 @@
+// Copyright 2014 The Chromium OS 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 "buffet/dbus_constants.h"
+
+namespace buffet {
+
+namespace dbus_constants {
+
+const char kServiceName[] = "org.chromium.Buffet";
+
+const char kRootServicePath[] = "/org/chromium/Buffet";
+
+const char kManagerInterface[] = "org.chromium.Buffet.Manager";
+const char kManagerServicePath[] = "/org/chromium/Buffet/Manager";
+
+const char kManagerCheckDeviceRegistered[] = "CheckDeviceRegistered";
+const char kManagerGetDeviceInfo[] = "GetDeviceInfo";
+const char kManagerStartRegisterDevice[] = "StartRegisterDevice";
+const char kManagerFinishRegisterDevice[] = "FinishRegisterDevice";
+const char kManagerUpdateStateMethod[] = "UpdateState";
+const char kManagerTestMethod[] = "TestMethod";
+
+} // namespace dbus_constants
+
+} // namespace buffet
diff --git a/buffet/dbus_constants.h b/buffet/dbus_constants.h
new file mode 100644
index 0000000..b7e9239
--- /dev/null
+++ b/buffet/dbus_constants.h
@@ -0,0 +1,34 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_DBUS_CONSTANTS_H_
+#define BUFFET_DBUS_CONSTANTS_H_
+
+namespace buffet {
+
+namespace dbus_constants {
+
+// The service name claimed by the Buffet daemon.
+extern const char kServiceName[];
+
+// The object at this path implements the ObjectManager interface.
+extern const char kRootServicePath[];
+
+// Interface implemented by the object at kManagerServicePath.
+extern const char kManagerInterface[];
+extern const char kManagerServicePath[];
+
+// Methods exposed as part of kManagerInterface.
+extern const char kManagerCheckDeviceRegistered[];
+extern const char kManagerGetDeviceInfo[];
+extern const char kManagerStartRegisterDevice[];
+extern const char kManagerFinishRegisterDevice[];
+extern const char kManagerUpdateStateMethod[];
+extern const char kManagerTestMethod[];
+
+} // namespace dbus_constants
+
+} // namespace buffet
+
+#endif // BUFFET_DBUS_CONSTANTS_H_
diff --git a/buffet/dbus_utils.cc b/buffet/dbus_utils.cc
new file mode 100644
index 0000000..58f0b26
--- /dev/null
+++ b/buffet/dbus_utils.cc
@@ -0,0 +1,63 @@
+// Copyright 2014 The Chromium OS 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 "buffet/dbus_utils.h"
+
+#include <base/logging.h>
+#include <base/bind.h>
+
+namespace buffet {
+
+namespace dbus_utils {
+
+namespace {
+
+// Passes |method_call| to |handler| and passes the response to
+// |response_sender|. If |handler| returns NULL, an empty response is created
+// and sent.
+void HandleSynchronousDBusMethodCall(
+ base::Callback<scoped_ptr<dbus::Response>(dbus::MethodCall*)> handler,
+ dbus::MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender) {
+ auto response = handler.Run(method_call);
+ if (!response)
+ response = dbus::Response::FromMethodCall(method_call);
+
+ response_sender.Run(response.Pass());
+}
+
+} // namespace
+
+scoped_ptr<dbus::Response> GetBadArgsError(dbus::MethodCall* method_call,
+ const std::string& message) {
+ LOG(ERROR) << "Error while handling DBus call: " << message;
+ scoped_ptr<dbus::ErrorResponse> resp(dbus::ErrorResponse::FromMethodCall(
+ method_call, "org.freedesktop.DBus.Error.InvalidArgs", message));
+ return scoped_ptr<dbus::Response>(resp.release());
+}
+
+scoped_ptr<dbus::Response> GetDBusError(dbus::MethodCall* method_call,
+ const Error* error) {
+ std::string message;
+ while (error) {
+ // Format error string as "domain/code:message".
+ if (!message.empty())
+ message += ';';
+ message += error->GetDomain() + '/' + error->GetCode() + ':' +
+ error->GetMessage();
+ error = error->GetInnerError();
+ }
+ scoped_ptr<dbus::ErrorResponse> resp(dbus::ErrorResponse::FromMethodCall(
+ method_call, "org.freedesktop.DBus.Error.Failed", message));
+ return scoped_ptr<dbus::Response>(resp.release());
+}
+
+dbus::ExportedObject::MethodCallCallback GetExportableDBusMethod(
+ base::Callback<scoped_ptr<dbus::Response>(dbus::MethodCall*)> handler) {
+ return base::Bind(&HandleSynchronousDBusMethodCall, handler);
+}
+
+} // namespace dbus_utils
+
+} // namespace buffet
diff --git a/buffet/dbus_utils.h b/buffet/dbus_utils.h
new file mode 100644
index 0000000..6314531
--- /dev/null
+++ b/buffet/dbus_utils.h
@@ -0,0 +1,35 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_DBUS_UTILS_H_
+#define BUFFET_DBUS_UTILS_H_
+
+#include <string>
+
+#include <base/memory/scoped_ptr.h>
+#include <dbus/exported_object.h>
+#include <dbus/message.h>
+
+#include "buffet/error.h"
+
+namespace buffet {
+
+namespace dbus_utils {
+
+scoped_ptr<dbus::Response> GetBadArgsError(dbus::MethodCall* method_call,
+ const std::string& message);
+
+scoped_ptr<dbus::Response> GetDBusError(dbus::MethodCall* method_call,
+ const Error* error);
+
+
+dbus::ExportedObject::MethodCallCallback GetExportableDBusMethod(
+ base::Callback<scoped_ptr<dbus::Response>(dbus::MethodCall*)> handler);
+
+} // namespace dbus_utils
+
+} // namespace buffet
+
+#endif // BUFFET_DBUS_UTILS_H_
+
diff --git a/buffet/device_registration_info.cc b/buffet/device_registration_info.cc
new file mode 100644
index 0000000..4aab715
--- /dev/null
+++ b/buffet/device_registration_info.cc
@@ -0,0 +1,515 @@
+// Copyright 2014 The Chromium OS 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 "buffet/device_registration_info.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include <base/json/json_writer.h>
+#include <base/values.h>
+
+#include "buffet/data_encoding.h"
+#include "buffet/device_registration_storage_keys.h"
+#include "buffet/storage_impls.h"
+#include "buffet/http_transport_curl.h"
+#include "buffet/http_utils.h"
+#include "buffet/mime_utils.h"
+#include "buffet/string_utils.h"
+#include "buffet/url_utils.h"
+
+const char buffet::kErrorDomainOAuth2[] = "oauth2";
+const char buffet::kErrorDomainGCD[] = "gcd";
+const char buffet::kErrorDomainGCDServer[] = "gcd_server";
+const char buffet::kErrorDomainBuffet[] = "buffet";
+
+namespace buffet {
+namespace storage_keys {
+
+// Persistent keys
+const char kClientId[] = "client_id";
+const char kClientSecret[] = "client_secret";
+const char kApiKey[] = "api_key";
+const char kRefreshToken[] = "refresh_token";
+const char kDeviceId[] = "device_id";
+const char kOAuthURL[] = "oauth_url";
+const char kServiceURL[] = "service_url";
+const char kRobotAccount[] = "robot_account";
+// Transient keys
+const char kDeviceKind[] = "device_kind";
+const char kSystemName[] = "system_name";
+const char kDisplayName[] = "display_name";
+
+} // namespace storage_keys
+} // namespace buffet
+
+namespace {
+
+const base::FilePath::CharType kDeviceInfoFilePath[] =
+ FILE_PATH_LITERAL("/var/lib/buffet/device_reg_info");
+
+bool GetParamValue(
+ const std::map<std::string, std::shared_ptr<base::Value>>& params,
+ const std::string& param_name,
+ std::string* param_value) {
+ auto p = params.find(param_name);
+ if (p == params.end())
+ return false;
+
+ return p->second->GetAsString(param_value);
+}
+
+std::pair<std::string, std::string> BuildAuthHeader(
+ const std::string& access_token_type,
+ const std::string& access_token) {
+ std::string authorization =
+ buffet::string_utils::Join(' ', access_token_type, access_token);
+ // Linter doesn't like the ; after } on the following line...
+ return {buffet::http::request_header::kAuthorization,
+ authorization}; // NOLINT
+}
+
+std::unique_ptr<base::DictionaryValue> ParseOAuthResponse(
+ const buffet::http::Response* response, buffet::ErrorPtr* error) {
+ int code = 0;
+ auto resp = buffet::http::ParseJsonResponse(response, &code, error);
+ if (resp && code >= buffet::http::status_code::BadRequest) {
+ if (error) {
+ std::string error_code, error_message;
+ if (resp->GetString("error", &error_code) &&
+ resp->GetString("error_description", &error_message)) {
+ buffet::Error::AddTo(error, buffet::kErrorDomainOAuth2, error_code,
+ error_message);
+ } else {
+ buffet::Error::AddTo(error, buffet::kErrorDomainOAuth2,
+ "unexpected_response", "Unexpected OAuth error");
+ }
+ }
+ return std::unique_ptr<base::DictionaryValue>();
+ }
+ return resp;
+}
+
+inline void SetUnexpectedError(buffet::ErrorPtr* error) {
+ buffet::Error::AddTo(error, buffet::kErrorDomainGCD, "unexpected_response",
+ "Unexpected GCD error");
+}
+
+void ParseGCDError(const base::DictionaryValue* json, buffet::ErrorPtr* error) {
+ if (!error)
+ return;
+
+ const base::Value* list_value = nullptr;
+ const base::ListValue* error_list = nullptr;
+ if (!json->Get("error.errors", &list_value) ||
+ !list_value->GetAsList(&error_list)) {
+ SetUnexpectedError(error);
+ return;
+ }
+
+ for (size_t i = 0; i < error_list->GetSize(); i++) {
+ const base::Value* error_value = nullptr;
+ const base::DictionaryValue* error_object = nullptr;
+ if (!error_list->Get(i, &error_value) ||
+ !error_value->GetAsDictionary(&error_object)) {
+ SetUnexpectedError(error);
+ continue;
+ }
+ std::string error_code, error_message;
+ if (error_object->GetString("reason", &error_code) &&
+ error_object->GetString("message", &error_message)) {
+ buffet::Error::AddTo(error, buffet::kErrorDomainGCDServer,
+ error_code, error_message);
+ } else {
+ SetUnexpectedError(error);
+ }
+ }
+}
+
+std::string BuildURL(const std::string& url,
+ const std::vector<std::string>& subpaths,
+ const buffet::data_encoding::WebParamList& params) {
+ std::string result = buffet::url::CombineMultiple(url, subpaths);
+ return buffet::url::AppendQueryParams(result, params);
+}
+
+} // anonymous namespace
+
+namespace buffet {
+
+DeviceRegistrationInfo::DeviceRegistrationInfo()
+ : transport_(new http::curl::Transport()),
+ // TODO(avakulenko): Figure out security implications of storing
+ // this data unencrypted.
+ storage_(new FileStorage(base::FilePath(kDeviceInfoFilePath))) {
+}
+
+DeviceRegistrationInfo::DeviceRegistrationInfo(
+ std::shared_ptr<http::Transport> transport,
+ std::shared_ptr<StorageInterface> storage) : transport_(transport),
+ storage_(storage) {
+}
+
+std::pair<std::string, std::string>
+ DeviceRegistrationInfo::GetAuthorizationHeader() const {
+ return BuildAuthHeader("Bearer", access_token_);
+}
+
+std::string DeviceRegistrationInfo::GetServiceURL(
+ const std::string& subpath,
+ const data_encoding::WebParamList& params) const {
+ return BuildURL(service_url_, {subpath}, params);
+}
+
+std::string DeviceRegistrationInfo::GetDeviceURL(
+ const std::string& subpath,
+ const data_encoding::WebParamList& params) const {
+ CHECK(!device_id_.empty()) << "Must have a valid device ID";
+ return BuildURL(service_url_, {"devices", device_id_, subpath}, params);
+}
+
+std::string DeviceRegistrationInfo::GetOAuthURL(
+ const std::string& subpath,
+ const data_encoding::WebParamList& params) const {
+ return BuildURL(oauth_url_, {subpath}, params);
+}
+
+std::string DeviceRegistrationInfo::GetDeviceId(ErrorPtr* error) {
+ return CheckRegistration(error) ? device_id_ : std::string();
+}
+
+bool DeviceRegistrationInfo::Load() {
+ auto value = storage_->Load();
+ const base::DictionaryValue* dict = nullptr;
+ if (!value || !value->GetAsDictionary(&dict))
+ return false;
+
+ // Get the values into temp variables first to make sure we can get
+ // all the data correctly before changing the state of this object.
+ std::string client_id;
+ if (!dict->GetString(storage_keys::kClientId, &client_id))
+ return false;
+ std::string client_secret;
+ if (!dict->GetString(storage_keys::kClientSecret, &client_secret))
+ return false;
+ std::string api_key;
+ if (!dict->GetString(storage_keys::kApiKey, &api_key))
+ return false;
+ std::string refresh_token;
+ if (!dict->GetString(storage_keys::kRefreshToken, &refresh_token))
+ return false;
+ std::string device_id;
+ if (!dict->GetString(storage_keys::kDeviceId, &device_id))
+ return false;
+ std::string oauth_url;
+ if (!dict->GetString(storage_keys::kOAuthURL, &oauth_url))
+ return false;
+ std::string service_url;
+ if (!dict->GetString(storage_keys::kServiceURL, &service_url))
+ return false;
+ std::string device_robot_account;
+ if (!dict->GetString(storage_keys::kRobotAccount, &device_robot_account))
+ return false;
+
+ client_id_ = client_id;
+ client_secret_ = client_secret;
+ api_key_ = api_key;
+ refresh_token_ = refresh_token;
+ device_id_ = device_id;
+ oauth_url_ = oauth_url;
+ service_url_ = service_url;
+ device_robot_account_ = device_robot_account;
+ return true;
+}
+
+bool DeviceRegistrationInfo::Save() const {
+ base::DictionaryValue dict;
+ dict.SetString(storage_keys::kClientId, client_id_);
+ dict.SetString(storage_keys::kClientSecret, client_secret_);
+ dict.SetString(storage_keys::kApiKey, api_key_);
+ dict.SetString(storage_keys::kRefreshToken, refresh_token_);
+ dict.SetString(storage_keys::kDeviceId, device_id_);
+ dict.SetString(storage_keys::kOAuthURL, oauth_url_);
+ dict.SetString(storage_keys::kServiceURL, service_url_);
+ dict.SetString(storage_keys::kRobotAccount, device_robot_account_);
+ return storage_->Save(&dict);
+}
+
+bool DeviceRegistrationInfo::CheckRegistration(ErrorPtr* error) {
+ LOG(INFO) << "Checking device registration record.";
+ if (refresh_token_.empty() ||
+ device_id_.empty() ||
+ device_robot_account_.empty()) {
+ LOG(INFO) << "No valid device registration record found.";
+ Error::AddTo(error, kErrorDomainGCD, "device_not_registered",
+ "No valid device registration record found");
+ return false;
+ }
+
+ LOG(INFO) << "Device registration record found.";
+ return ValidateAndRefreshAccessToken(error);
+}
+
+bool DeviceRegistrationInfo::ValidateAndRefreshAccessToken(ErrorPtr* error) {
+ LOG(INFO) << "Checking access token expiration.";
+ if (!access_token_.empty() &&
+ !access_token_expiration_.is_null() &&
+ access_token_expiration_ > base::Time::Now()) {
+ LOG(INFO) << "Access token is still valid.";
+ return true;
+ }
+
+ auto response = http::PostFormData(GetOAuthURL("token"), {
+ {"refresh_token", refresh_token_},
+ {"client_id", client_id_},
+ {"client_secret", client_secret_},
+ {"grant_type", "refresh_token"},
+ }, transport_, error);
+ if (!response)
+ return false;
+
+ auto json = ParseOAuthResponse(response.get(), error);
+ if (!json)
+ return false;
+
+ int expires_in = 0;
+ if (!json->GetString("access_token", &access_token_) ||
+ !json->GetInteger("expires_in", &expires_in) ||
+ access_token_.empty() ||
+ expires_in <= 0) {
+ LOG(ERROR) << "Access token unavailable.";
+ Error::AddTo(error, kErrorDomainOAuth2, "unexpected_server_response",
+ "Access token unavailable");
+ return false;
+ }
+
+ access_token_expiration_ = base::Time::Now() +
+ base::TimeDelta::FromSeconds(expires_in);
+
+ LOG(INFO) << "Access token is refreshed for additional " << expires_in
+ << " seconds.";
+ return true;
+}
+
+std::unique_ptr<base::Value> DeviceRegistrationInfo::GetDeviceInfo(
+ ErrorPtr* error) {
+ if (!CheckRegistration(error))
+ return std::unique_ptr<base::Value>();
+
+ auto response = http::Get(GetDeviceURL(),
+ {GetAuthorizationHeader()}, transport_, error);
+ int status_code = 0;
+ std::unique_ptr<base::DictionaryValue> json =
+ http::ParseJsonResponse(response.get(), &status_code, error);
+ if (json) {
+ if (status_code >= http::status_code::BadRequest) {
+ LOG(WARNING) << "Failed to retrieve the device info. Response code = "
+ << status_code;
+ ParseGCDError(json.get(), error);
+ return std::unique_ptr<base::Value>();
+ }
+ }
+ return std::unique_ptr<base::Value>(json.release());
+}
+
+bool CheckParam(const std::string& param_name,
+ const std::string& param_value,
+ ErrorPtr* error) {
+ if (!param_value.empty())
+ return true;
+
+ Error::AddTo(error, kErrorDomainBuffet, "missing_parameter",
+ "Parameter " + param_name + " not specified");
+ return false;
+}
+
+std::string DeviceRegistrationInfo::StartRegistration(
+ const std::map<std::string, std::shared_ptr<base::Value>>& params,
+ ErrorPtr* error) {
+ GetParamValue(params, storage_keys::kClientId, &client_id_);
+ GetParamValue(params, storage_keys::kClientSecret, &client_secret_);
+ GetParamValue(params, storage_keys::kApiKey, &api_key_);
+ GetParamValue(params, storage_keys::kDeviceId, &device_id_);
+ GetParamValue(params, storage_keys::kDeviceKind, &device_kind_);
+ GetParamValue(params, storage_keys::kSystemName, &system_name_);
+ GetParamValue(params, storage_keys::kDisplayName, &display_name_);
+ GetParamValue(params, storage_keys::kOAuthURL, &oauth_url_);
+ GetParamValue(params, storage_keys::kServiceURL, &service_url_);
+
+ if (!CheckParam(storage_keys::kClientId, client_id_, error))
+ return std::string();
+ if (!CheckParam(storage_keys::kClientSecret, client_secret_, error))
+ return std::string();
+ if (!CheckParam(storage_keys::kApiKey, api_key_, error))
+ return std::string();
+ if (!CheckParam(storage_keys::kDeviceKind, device_kind_, error))
+ return std::string();
+ if (!CheckParam(storage_keys::kSystemName, system_name_, error))
+ return std::string();
+ if (!CheckParam(storage_keys::kOAuthURL, oauth_url_, error))
+ return std::string();
+ if (!CheckParam(storage_keys::kServiceURL, service_url_, error))
+ return std::string();
+
+ std::vector<std::pair<std::string, std::vector<std::string>>> commands = {
+ {"SetDeviceConfiguration", {"data"}}
+ };
+
+ base::DictionaryValue req_json;
+ base::ListValue* set_device_configuration_params = new base::ListValue;
+ base::DictionaryValue* param1 = new base::DictionaryValue;
+ param1->SetString("name", "data");
+ set_device_configuration_params->Append(param1);
+
+ base::ListValue* vendor_commands = new base::ListValue;
+ for (auto&& pair : commands) {
+ base::ListValue* params = new base::ListValue;
+ for (auto&& param_name : pair.second) {
+ base::DictionaryValue* param = new base::DictionaryValue;
+ param->SetString("name", param_name);
+ params->Append(param);
+ }
+ base::DictionaryValue* command = new base::DictionaryValue;
+ command->SetString("name", pair.first);
+ command->Set("parameter", params);
+ vendor_commands->Append(command);
+ }
+
+ req_json.SetString("oauthClientId", client_id_);
+ req_json.SetString("deviceDraft.deviceKind", device_kind_);
+ req_json.SetString("deviceDraft.systemName", system_name_);
+ req_json.SetString("deviceDraft.displayName", display_name_);
+ req_json.SetString("deviceDraft.channel.supportedType", "xmpp");
+ req_json.Set("deviceDraft.commands.base.vendorCommands", vendor_commands);
+
+ std::string url = GetServiceURL("registrationTickets", {{"key", api_key_}});
+ auto resp_json = http::ParseJsonResponse(
+ http::PostJson(url, &req_json, transport_, error).get(), nullptr, error);
+ if (!resp_json)
+ return std::string();
+
+ if (!resp_json->GetString("id", &ticket_id_)) {
+ Error::AddTo(error, kErrorDomainGCD, "unexpected_response",
+ "Device ID missing");
+ return std::string();
+ }
+
+ std::string auth_url = GetOAuthURL("auth", {
+ {"scope", "https://www.googleapis.com/auth/clouddevices"},
+ {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
+ {"response_type", "code"},
+ {"client_id", client_id_}
+ });
+
+ base::DictionaryValue json;
+ json.SetString("ticket_id", ticket_id_);
+ json.SetString("auth_url", auth_url);
+
+ std::string ret;
+ base::JSONWriter::Write(&json, &ret);
+ return ret;
+}
+
+bool DeviceRegistrationInfo::FinishRegistration(
+ const std::string& user_auth_code, ErrorPtr* error) {
+ if (ticket_id_.empty()) {
+ LOG(ERROR) << "Finish registration without ticket ID";
+ Error::AddTo(error, kErrorDomainBuffet, "registration_not_started",
+ "Device registration not started");
+ return false;
+ }
+
+ std::string url = GetServiceURL("registrationTickets/" + ticket_id_);
+ std::unique_ptr<http::Response> response;
+ if (!user_auth_code.empty()) {
+ response = http::PostFormData(GetOAuthURL("token"), {
+ {"code", user_auth_code},
+ {"client_id", client_id_},
+ {"client_secret", client_secret_},
+ {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
+ {"grant_type", "authorization_code"}
+ }, transport_, error);
+ if (!response)
+ return false;
+
+ auto json_resp = ParseOAuthResponse(response.get(), error);
+ if (!json_resp)
+ return false;
+
+ std::string user_access_token;
+ std::string token_type;
+ if (!json_resp->GetString("access_token", &user_access_token) ||
+ !json_resp->GetString("token_type", &token_type)) {
+ Error::AddTo(error, kErrorDomainOAuth2, "unexpected_response",
+ "User access_token is missing in response");
+ return false;
+ }
+
+ base::DictionaryValue user_info;
+ user_info.SetString("userEmail", "me");
+ response = http::PatchJson(
+ url, &user_info, {BuildAuthHeader(token_type, user_access_token)},
+ transport_, error);
+
+ auto json = http::ParseJsonResponse(response.get(), nullptr, error);
+ if (!json)
+ return false;
+ }
+
+ std::string auth_code;
+ url += "/finalize?key=" + api_key_;
+ LOG(INFO) << "Sending request to: " << url;
+ response = http::PostBinary(url, nullptr, 0, transport_, error);
+ if (!response)
+ return false;
+ auto json_resp = http::ParseJsonResponse(response.get(), nullptr, error);
+ if (!json_resp)
+ return false;
+ if (!response->IsSuccessful()) {
+ ParseGCDError(json_resp.get(), error);
+ return false;
+ }
+ if (!json_resp->GetString("robotAccountEmail", &device_robot_account_) ||
+ !json_resp->GetString("robotAccountAuthorizationCode", &auth_code) ||
+ !json_resp->GetString("deviceDraft.id", &device_id_)) {
+ Error::AddTo(error, kErrorDomainGCD, "unexpected_response",
+ "Device account missing in response");
+ return false;
+ }
+
+ // Now get access_token and refresh_token
+ response = http::PostFormData(GetOAuthURL("token"), {
+ {"code", auth_code},
+ {"client_id", client_id_},
+ {"client_secret", client_secret_},
+ {"redirect_uri", "oob"},
+ {"scope", "https://www.googleapis.com/auth/clouddevices"},
+ {"grant_type", "authorization_code"}
+ }, transport_, error);
+ if (!response)
+ return false;
+
+ json_resp = ParseOAuthResponse(response.get(), error);
+ int expires_in = 0;
+ if (!json_resp ||
+ !json_resp->GetString("access_token", &access_token_) ||
+ !json_resp->GetString("refresh_token", &refresh_token_) ||
+ !json_resp->GetInteger("expires_in", &expires_in) ||
+ access_token_.empty() ||
+ refresh_token_.empty() ||
+ expires_in <= 0) {
+ Error::AddTo(error, kErrorDomainGCD, "unexpected_response",
+ "Device access_token missing in response");
+ return false;
+ }
+
+ access_token_expiration_ = base::Time::Now() +
+ base::TimeDelta::FromSeconds(expires_in);
+
+ Save();
+ return true;
+}
+
+} // namespace buffet
diff --git a/buffet/device_registration_info.h b/buffet/device_registration_info.h
new file mode 100644
index 0000000..da0d409
--- /dev/null
+++ b/buffet/device_registration_info.h
@@ -0,0 +1,146 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_DEVICE_REGISTRATION_INFO_H_
+#define BUFFET_DEVICE_REGISTRATION_INFO_H_
+
+#include <string>
+#include <map>
+#include <memory>
+#include <utility>
+
+#include <base/basictypes.h>
+#include <base/time/time.h>
+
+#include "buffet/data_encoding.h"
+#include "buffet/error.h"
+#include "buffet/http_transport.h"
+#include "buffet/storage_interface.h"
+
+namespace base {
+ class Value;
+} // namespace base
+
+namespace buffet {
+
+extern const char kErrorDomainOAuth2[];
+extern const char kErrorDomainGCD[];
+extern const char kErrorDomainGCDServer[];
+extern const char kErrorDomainBuffet[];
+
+// The DeviceRegistrationInfo class represents device registration information.
+class DeviceRegistrationInfo {
+ public:
+ // This is a helper class for unit testing.
+ class TestHelper;
+ // Default-constructed uses CURL HTTP transport.
+ DeviceRegistrationInfo();
+ // This constructor allows to pass in a custom HTTP transport
+ // (mainly for testing).
+ DeviceRegistrationInfo(std::shared_ptr<http::Transport> transport,
+ std::shared_ptr<StorageInterface> storage);
+
+ // Returns the authorization HTTP header that can be used to talk
+ // to GCD server for authenticated device communication.
+ // Make sure ValidateAndRefreshAccessToken() is called before this call.
+ std::pair<std::string, std::string> GetAuthorizationHeader() const;
+
+ // Returns the GCD service request URL. If |subpath| is specified, it is
+ // appended to the base URL which is normally
+ // https://www.googleapis.com/clouddevices/v1/".
+ // If |params| are specified, each key-value pair is formatted using
+ // data_encoding::WebParamsEncode() and appended to URL as a query
+ // string.
+ // So, calling:
+ // GetServiceURL("ticket", {{"key","apiKey"}})
+ // will return something like:
+ // https://www.googleapis.com/clouddevices/v1/ticket?key=apiKey
+ std::string GetServiceURL(
+ const std::string& subpath = {},
+ const data_encoding::WebParamList& params = {}) const;
+
+ // Returns a service URL to access the registered device on GCD server.
+ // The base URL used to construct the full URL looks like this:
+ // https://www.googleapis.com/clouddevices/v1/devices/<device_id>/
+ std::string GetDeviceURL(
+ const std::string& subpath = {},
+ const data_encoding::WebParamList& params = {}) const;
+
+ // Similar to GetServiceURL, GetOAuthURL() returns a URL of OAuth 2.0 server.
+ // The base URL used is https://accounts.google.com/o/oauth2/.
+ std::string GetOAuthURL(
+ const std::string& subpath = {},
+ const data_encoding::WebParamList& params = {}) const;
+
+ // Returns the registered device ID (GUID) or empty string if failed
+ std::string GetDeviceId(ErrorPtr* error);
+
+ // Loads the device registration information from cache.
+ bool Load();
+
+ // Checks for the valid device registration as well as refreshes
+ // the device access token, if available.
+ bool CheckRegistration(ErrorPtr* error);
+
+ // Gets the full device description JSON object, or nullptr if
+ // the device is not registered or communication failure.
+ std::unique_ptr<base::Value> GetDeviceInfo(ErrorPtr* error);
+
+ // Starts device registration procedure. |params| are a list of
+ // key-value pairs of device information, such as client_id, client_secret,
+ // and so on. If a particular key-value pair is omitted, a default value
+ // is used when possible. Returns a device claim ID on success.
+ std::string StartRegistration(
+ const std::map<std::string, std::shared_ptr<base::Value>>& params,
+ ErrorPtr* error);
+
+ // Finalizes the device registration. If |user_auth_code| is provided, then
+ // the device record is populated with user email on user's behalf. Otherwise
+ // the user is responsible to issue a PATCH request to provide a valid
+ // email address before calling FinishRegistration.
+ bool FinishRegistration(const std::string& user_auth_code,
+ ErrorPtr* error);
+
+ private:
+ // Saves the device registration to cache.
+ bool Save() const;
+
+ // Makes sure the access token is available and up-to-date.
+ bool ValidateAndRefreshAccessToken(ErrorPtr* error);
+
+ // Persistent data. Some of default values for testing purposes are used.
+ // TODO(avakulenko): remove these default values in the future.
+ // http://crbug.com/364692
+ std::string client_id_ =
+ "583509257718-lnmeofvjef3b1tm33sbjmckfnumfvn8j.apps.googleusercontent.com";
+ std::string client_secret_ = "6fzZwQhgnsHhvYYvvFdpv5SD";
+ std::string api_key_ = "AIzaSyAp7KVig5m9g4LWWKr79mTS8sXWfUU6w9g";
+ std::string refresh_token_;
+ std::string device_id_;
+ std::string device_robot_account_;
+ std::string oauth_url_ = "https://accounts.google.com/o/oauth2/";
+ std::string service_url_ =
+ "https://www-googleapis-staging.sandbox.google.com/"
+ "clouddevices/v1/";
+
+ // Transient data
+ std::string access_token_;
+ base::Time access_token_expiration_;
+ std::string ticket_id_;
+ std::string device_kind_ = "vendor";
+ std::string system_name_ = "coffee_pot";
+ std::string display_name_ = "Coffee Pot";
+
+ // HTTP transport used for communications.
+ std::shared_ptr<http::Transport> transport_;
+ // Serialization interface to save and load device registration info.
+ std::shared_ptr<StorageInterface> storage_;
+
+ friend class TestHelper;
+ DISALLOW_COPY_AND_ASSIGN(DeviceRegistrationInfo);
+};
+
+} // namespace buffet
+
+#endif // BUFFET_DEVICE_REGISTRATION_INFO_H_
diff --git a/buffet/device_registration_info_unittest.cc b/buffet/device_registration_info_unittest.cc
new file mode 100644
index 0000000..5108e35
--- /dev/null
+++ b/buffet/device_registration_info_unittest.cc
@@ -0,0 +1,392 @@
+// Copyright 2014 The Chromium OS 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 <base/json/json_reader.h>
+#include <base/values.h>
+#include <gtest/gtest.h>
+
+#include "buffet/bind_lambda.h"
+#include "buffet/device_registration_info.h"
+#include "buffet/device_registration_storage_keys.h"
+#include "buffet/http_request.h"
+#include "buffet/http_transport_fake.h"
+#include "buffet/mime_utils.h"
+#include "buffet/storage_impls.h"
+
+using namespace buffet; // NOLINT(build/namespaces)
+using namespace buffet::http; // NOLINT(build/namespaces)
+
+namespace {
+
+namespace test_data {
+
+const char kServiceURL[] = "http://gcd.server.com/";
+const char kOAuthURL[] = "http://oauth.server.com/";
+const char kApiKey[] = "GOadRdTf9FERf0k4w6EFOof56fUJ3kFDdFL3d7f";
+const char kClientId[] = "123543821385-sfjkjshdkjhfk234sdfsdfkskd"
+ "fkjh7f.apps.googleusercontent.com";
+const char kClientSecret[] = "5sdGdGlfolGlrFKfdFlgP6FG";
+const char kDeviceId[] = "4a7ea2d1-b331-1e1f-b206-e863c7635196";
+const char kClaimTicketId[] = "RTcUE";
+const char kAccessToken[] = "ya29.1.AADtN_V-dLUM-sVZ0qVjG9Dxm5NgdS9J"
+ "Mx_JLUqhC9bED_YFjzHZtYt65ZzXCS35NMAeaVZ"
+ "Dei530-w0yE2urpQ";
+const char kRefreshToken[] = "1/zQmxR6PKNvhcxf9SjXUrCjcmCrcqRKXctc6cp"
+ "1nI-GQ";
+const char kRobotAccountAuthCode[] = "4/Mf_ujEhPejVhOq-OxW9F5cSOnWzx."
+ "YgciVjTYGscRshQV0ieZDAqiTIjMigI";
+const char kRobotAccountEmail[] = "6ed0b3f54f9bd619b942f4ad2441c252@"
+ "clouddevices.gserviceaccount.com";
+const char kUserAccountAuthCode[] = "2/sd_GD1TGFKpJOLJ34-0g5fK0fflp.GlT"
+ "I0F5g7hNtFgj5HFGOf8FlGK9eflO";
+const char kUserAccessToken[] = "sd56.4.FGDjG_F-gFGF-dFG6gGOG9Dxm5NgdS9"
+ "JMx_JLUqhC9bED_YFjLKjlkjLKJlkjLKjlKJea"
+ "VZDei530-w0yE2urpQ";
+const char kUserRefreshToken[] = "1/zQLKjlKJlkLkLKjLkjLKjLkjLjLkjl0ftc6"
+ "cp1nI-GQ";
+
+} // namespace test_data
+
+// Fill in the storage with default environment information (URLs, etc).
+void InitDefaultStorage(base::DictionaryValue* data) {
+ data->SetString(storage_keys::kClientId, test_data::kClientId);
+ data->SetString(storage_keys::kClientSecret, test_data::kClientSecret);
+ data->SetString(storage_keys::kApiKey, test_data::kApiKey);
+ data->SetString(storage_keys::kRefreshToken, "");
+ data->SetString(storage_keys::kDeviceId, "");
+ data->SetString(storage_keys::kOAuthURL, test_data::kOAuthURL);
+ data->SetString(storage_keys::kServiceURL, test_data::kServiceURL);
+ data->SetString(storage_keys::kRobotAccount, "");
+}
+
+// Add the test device registration information.
+void SetDefaultDeviceRegistration(base::DictionaryValue* data) {
+ data->SetString(storage_keys::kRefreshToken, test_data::kRefreshToken);
+ data->SetString(storage_keys::kDeviceId, test_data::kDeviceId);
+ data->SetString(storage_keys::kRobotAccount, test_data::kRobotAccountEmail);
+}
+
+void OAuth2Handler(const fake::ServerRequest& request,
+ fake::ServerResponse* response) {
+ base::DictionaryValue json;
+ if (request.GetFormField("grant_type") == "refresh_token") {
+ // Refresh device access token.
+ EXPECT_EQ(test_data::kRefreshToken, request.GetFormField("refresh_token"));
+ EXPECT_EQ(test_data::kClientId, request.GetFormField("client_id"));
+ EXPECT_EQ(test_data::kClientSecret, request.GetFormField("client_secret"));
+ json.SetString("access_token", test_data::kAccessToken);
+ } else if (request.GetFormField("grant_type") == "authorization_code") {
+ // Obtain access token.
+ std::string code = request.GetFormField("code");
+ if (code == test_data::kUserAccountAuthCode) {
+ // Get user access token.
+ EXPECT_EQ(test_data::kClientId, request.GetFormField("client_id"));
+ EXPECT_EQ(test_data::kClientSecret,
+ request.GetFormField("client_secret"));
+ EXPECT_EQ("urn:ietf:wg:oauth:2.0:oob",
+ request.GetFormField("redirect_uri"));
+ json.SetString("access_token", test_data::kUserAccessToken);
+ json.SetString("token_type", "Bearer");
+ json.SetString("refresh_token", test_data::kUserRefreshToken);
+ } else if (code == test_data::kRobotAccountAuthCode) {
+ // Get device access token.
+ EXPECT_EQ(test_data::kClientId, request.GetFormField("client_id"));
+ EXPECT_EQ(test_data::kClientSecret,
+ request.GetFormField("client_secret"));
+ EXPECT_EQ("oob", request.GetFormField("redirect_uri"));
+ EXPECT_EQ("https://www.googleapis.com/auth/clouddevices",
+ request.GetFormField("scope"));
+ json.SetString("access_token", test_data::kAccessToken);
+ json.SetString("token_type", "Bearer");
+ json.SetString("refresh_token", test_data::kRefreshToken);
+ } else {
+ FAIL() << "Unexpected authorization code";
+ }
+ } else {
+ FAIL() << "Unexpected grant type";
+ }
+ json.SetInteger("expires_in", 3600);
+ response->ReplyJson(status_code::Ok, &json);
+}
+
+void DeviceInfoHandler(const fake::ServerRequest& request,
+ fake::ServerResponse* response) {
+ std::string auth = "Bearer ";
+ auth += test_data::kAccessToken;
+ EXPECT_EQ(auth, request.GetHeader(http::request_header::kAuthorization));
+ response->ReplyJson(status_code::Ok, {
+ {"channel.supportedType", "xmpp"},
+ {"deviceKind", "vendor"},
+ {"id", test_data::kDeviceId},
+ {"kind", "clouddevices#device"},
+ });
+}
+
+void FinalizeTicketHandler(const fake::ServerRequest& request,
+ fake::ServerResponse* response) {
+ EXPECT_EQ(test_data::kApiKey, request.GetFormField("key"));
+ EXPECT_TRUE(request.GetData().empty());
+
+ response->ReplyJson(status_code::Ok, {
+ {"id", test_data::kClaimTicketId},
+ {"kind", "clouddevices#registrationTicket"},
+ {"oauthClientId", test_data::kClientId},
+ {"userEmail", "user@email.com"},
+ {"deviceDraft.id", test_data::kDeviceId},
+ {"deviceDraft.kind", "clouddevices#device"},
+ {"deviceDraft.channel.supportedType", "xmpp"},
+ {"robotAccountEmail", test_data::kRobotAccountEmail},
+ {"robotAccountAuthorizationCode", test_data::kRobotAccountAuthCode},
+ });
+}
+
+} // anonymous namespace
+
+// This is a helper class that allows the unit tests to set the private
+// member DeviceRegistrationInfo::ticket_id_, since TestHelper is declared
+// as a friend to DeviceRegistrationInfo.
+class DeviceRegistrationInfo::TestHelper {
+ public:
+ static void SetTestTicketId(DeviceRegistrationInfo* info) {
+ info->ticket_id_ = test_data::kClaimTicketId;
+ }
+};
+
+class DeviceRegistrationInfoTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() override {
+ InitDefaultStorage(&data);
+ storage = std::make_shared<MemStorage>();
+ storage->Save(&data);
+ transport = std::make_shared<fake::Transport>();
+ dev_reg = std::unique_ptr<DeviceRegistrationInfo>(
+ new DeviceRegistrationInfo(transport, storage));
+ }
+
+ base::DictionaryValue data;
+ std::shared_ptr<MemStorage> storage;
+ std::shared_ptr<fake::Transport> transport;
+ std::unique_ptr<DeviceRegistrationInfo> dev_reg;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(DeviceRegistrationInfoTest, GetServiceURL) {
+ EXPECT_TRUE(dev_reg->Load());
+ EXPECT_EQ(test_data::kServiceURL, dev_reg->GetServiceURL());
+ std::string url = test_data::kServiceURL;
+ url += "registrationTickets";
+ EXPECT_EQ(url, dev_reg->GetServiceURL("registrationTickets"));
+ url += "?key=";
+ url += test_data::kApiKey;
+ EXPECT_EQ(url, dev_reg->GetServiceURL("registrationTickets", {
+ {"key", test_data::kApiKey}
+ }));
+ url += "&restart=true";
+ EXPECT_EQ(url, dev_reg->GetServiceURL("registrationTickets", {
+ {"key", test_data::kApiKey},
+ {"restart", "true"},
+ }));
+}
+
+TEST_F(DeviceRegistrationInfoTest, GetOAuthURL) {
+ EXPECT_TRUE(dev_reg->Load());
+ EXPECT_EQ(test_data::kOAuthURL, dev_reg->GetOAuthURL());
+ std::string url = test_data::kOAuthURL;
+ url += "auth?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fclouddevices&";
+ url += "redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&";
+ url += "response_type=code&";
+ url += "client_id=";
+ url += test_data::kClientId;
+ EXPECT_EQ(url, dev_reg->GetOAuthURL("auth", {
+ {"scope", "https://www.googleapis.com/auth/clouddevices"},
+ {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
+ {"response_type", "code"},
+ {"client_id", test_data::kClientId}
+ }));
+}
+
+TEST_F(DeviceRegistrationInfoTest, CheckRegistration) {
+ EXPECT_TRUE(dev_reg->Load());
+ EXPECT_FALSE(dev_reg->CheckRegistration(nullptr));
+ EXPECT_EQ(0, transport->GetRequestCount());
+
+ SetDefaultDeviceRegistration(&data);
+ storage->Save(&data);
+ EXPECT_TRUE(dev_reg->Load());
+
+ transport->AddHandler(dev_reg->GetOAuthURL("token"), request_type::kPost,
+ base::Bind(OAuth2Handler));
+ transport->ResetRequestCount();
+ EXPECT_TRUE(dev_reg->CheckRegistration(nullptr));
+ EXPECT_EQ(1, transport->GetRequestCount());
+}
+
+TEST_F(DeviceRegistrationInfoTest, GetDeviceInfo) {
+ SetDefaultDeviceRegistration(&data);
+ storage->Save(&data);
+ EXPECT_TRUE(dev_reg->Load());
+
+ transport->AddHandler(dev_reg->GetOAuthURL("token"), request_type::kPost,
+ base::Bind(OAuth2Handler));
+ transport->AddHandler(dev_reg->GetDeviceURL(), request_type::kGet,
+ base::Bind(DeviceInfoHandler));
+ transport->ResetRequestCount();
+ auto device_info = dev_reg->GetDeviceInfo(nullptr);
+ EXPECT_EQ(2, transport->GetRequestCount());
+ EXPECT_NE(nullptr, device_info.get());
+ base::DictionaryValue* dict = nullptr;
+ EXPECT_TRUE(device_info->GetAsDictionary(&dict));
+ std::string id;
+ EXPECT_TRUE(dict->GetString("id", &id));
+ EXPECT_EQ(test_data::kDeviceId, id);
+}
+
+TEST_F(DeviceRegistrationInfoTest, GetDeviceId) {
+ SetDefaultDeviceRegistration(&data);
+ storage->Save(&data);
+ EXPECT_TRUE(dev_reg->Load());
+
+ transport->AddHandler(dev_reg->GetOAuthURL("token"), request_type::kPost,
+ base::Bind(OAuth2Handler));
+ transport->AddHandler(dev_reg->GetDeviceURL(), request_type::kGet,
+ base::Bind(DeviceInfoHandler));
+ std::string id = dev_reg->GetDeviceId(nullptr);
+ EXPECT_EQ(test_data::kDeviceId, id);
+}
+
+TEST_F(DeviceRegistrationInfoTest, StartRegistration) {
+ EXPECT_TRUE(dev_reg->Load());
+
+ auto create_ticket = [](const fake::ServerRequest& request,
+ fake::ServerResponse* response) {
+ EXPECT_EQ(test_data::kApiKey, request.GetFormField("key"));
+ auto json = request.GetDataAsJson();
+ EXPECT_NE(nullptr, json.get());
+ std::string value;
+ EXPECT_TRUE(json->GetString("deviceDraft.channel.supportedType", &value));
+ EXPECT_EQ("xmpp", value);
+ EXPECT_TRUE(json->GetString("oauthClientId", &value));
+ EXPECT_EQ(test_data::kClientId, value);
+ EXPECT_TRUE(json->GetString("deviceDraft.deviceKind", &value));
+ EXPECT_EQ("vendor", value);
+
+ base::DictionaryValue json_resp;
+ json_resp.SetString("id", test_data::kClaimTicketId);
+ json_resp.SetString("kind", "clouddevices#registrationTicket");
+ json_resp.SetString("oauthClientId", test_data::kClientId);
+ base::DictionaryValue* device_draft = nullptr;
+ EXPECT_TRUE(json->GetDictionary("deviceDraft", &device_draft));
+ device_draft = device_draft->DeepCopy();
+ device_draft->SetString("id", test_data::kDeviceId);
+ device_draft->SetString("kind", "clouddevices#device");
+ json_resp.Set("deviceDraft", device_draft);
+
+ response->ReplyJson(status_code::Ok, &json_resp);
+ };
+
+ transport->AddHandler(dev_reg->GetServiceURL("registrationTickets"),
+ request_type::kPost,
+ base::Bind(create_ticket));
+ std::map<std::string, std::shared_ptr<base::Value>> params;
+ std::string json_resp = dev_reg->StartRegistration(params, nullptr);
+ auto json = std::unique_ptr<base::Value>(base::JSONReader::Read(json_resp));
+ EXPECT_NE(nullptr, json.get());
+ base::DictionaryValue* dict = nullptr;
+ EXPECT_TRUE(json->GetAsDictionary(&dict));
+ std::string value;
+ EXPECT_TRUE(dict->GetString("ticket_id", &value));
+ EXPECT_EQ(test_data::kClaimTicketId, value);
+}
+
+TEST_F(DeviceRegistrationInfoTest, FinishRegistration_NoAuth) {
+ // Test finalizing ticket with no user authorization token.
+ // This assumes that a client would patch in their email separately.
+ EXPECT_TRUE(dev_reg->Load());
+
+ // General ticket finalization handler.
+ std::string ticket_url =
+ dev_reg->GetServiceURL("registrationTickets/" +
+ std::string(test_data::kClaimTicketId));
+ transport->AddHandler(ticket_url + "/finalize", request_type::kPost,
+ base::Bind(FinalizeTicketHandler));
+
+ transport->AddHandler(dev_reg->GetOAuthURL("token"), request_type::kPost,
+ base::Bind(OAuth2Handler));
+
+ storage->reset_save_count();
+ DeviceRegistrationInfo::TestHelper::SetTestTicketId(dev_reg.get());
+ EXPECT_TRUE(dev_reg->FinishRegistration("", nullptr));
+ EXPECT_EQ(1, storage->save_count()); // The device info must have been saved.
+ EXPECT_EQ(2, transport->GetRequestCount());
+
+ // Validate the device info saved to storage...
+ auto storage_data = storage->Load();
+ base::DictionaryValue* dict = nullptr;
+ EXPECT_TRUE(storage_data->GetAsDictionary(&dict));
+ std::string value;
+ EXPECT_TRUE(dict->GetString(storage_keys::kApiKey, &value));
+ EXPECT_EQ(test_data::kApiKey, value);
+ EXPECT_TRUE(dict->GetString(storage_keys::kClientId, &value));
+ EXPECT_EQ(test_data::kClientId, value);
+ EXPECT_TRUE(dict->GetString(storage_keys::kClientSecret, &value));
+ EXPECT_EQ(test_data::kClientSecret, value);
+ EXPECT_TRUE(dict->GetString(storage_keys::kDeviceId, &value));
+ EXPECT_EQ(test_data::kDeviceId, value);
+ EXPECT_TRUE(dict->GetString(storage_keys::kOAuthURL, &value));
+ EXPECT_EQ(test_data::kOAuthURL, value);
+ EXPECT_TRUE(dict->GetString(storage_keys::kRefreshToken, &value));
+ EXPECT_EQ(test_data::kRefreshToken, value);
+ EXPECT_TRUE(dict->GetString(storage_keys::kRobotAccount, &value));
+ EXPECT_EQ(test_data::kRobotAccountEmail, value);
+ EXPECT_TRUE(dict->GetString(storage_keys::kServiceURL, &value));
+ EXPECT_EQ(test_data::kServiceURL, value);
+}
+
+TEST_F(DeviceRegistrationInfoTest, FinishRegistration_Auth) {
+ // Test finalizing ticket with user authorization token.
+ EXPECT_TRUE(dev_reg->Load());
+
+ // General ticket finalization handler.
+ std::string ticket_url =
+ dev_reg->GetServiceURL("registrationTickets/" +
+ std::string(test_data::kClaimTicketId));
+ transport->AddHandler(ticket_url + "/finalize", request_type::kPost,
+ base::Bind(FinalizeTicketHandler));
+
+ transport->AddHandler(dev_reg->GetOAuthURL("token"), request_type::kPost,
+ base::Bind(OAuth2Handler));
+
+ // Handle patching in the user email onto the device record.
+ auto email_patch_handler = [](const fake::ServerRequest& request,
+ fake::ServerResponse* response) {
+ std::string auth_header = "Bearer ";
+ auth_header += test_data::kUserAccessToken;
+ EXPECT_EQ(auth_header,
+ request.GetHeader(http::request_header::kAuthorization));
+ auto json = request.GetDataAsJson();
+ EXPECT_NE(nullptr, json.get());
+ std::string value;
+ EXPECT_TRUE(json->GetString("userEmail", &value));
+ EXPECT_EQ("me", value);
+
+ response->ReplyJson(status_code::Ok, {
+ {"id", test_data::kClaimTicketId},
+ {"kind", "clouddevices#registrationTicket"},
+ {"oauthClientId", test_data::kClientId},
+ {"userEmail", "user@email.com"},
+ {"deviceDraft.id", test_data::kDeviceId},
+ {"deviceDraft.kind", "clouddevices#device"},
+ {"deviceDraft.channel.supportedType", "xmpp"},
+ });
+ };
+ transport->AddHandler(ticket_url, request_type::kPatch,
+ base::Bind(email_patch_handler));
+
+ storage->reset_save_count();
+ DeviceRegistrationInfo::TestHelper::SetTestTicketId(dev_reg.get());
+ EXPECT_TRUE(dev_reg->FinishRegistration(test_data::kUserAccountAuthCode,
+ nullptr));
+ EXPECT_EQ(1, storage->save_count()); // The device info must have been saved.
+ EXPECT_EQ(4, transport->GetRequestCount());
+}
diff --git a/buffet/device_registration_storage_keys.h b/buffet/device_registration_storage_keys.h
new file mode 100644
index 0000000..a6c9239
--- /dev/null
+++ b/buffet/device_registration_storage_keys.h
@@ -0,0 +1,31 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_DEVICE_REGISTRATION_STORAGE_KEYS_H_
+#define BUFFET_DEVICE_REGISTRATION_STORAGE_KEYS_H_
+
+// These are the keys used to identify specific device registration information
+// being saved to a storage. Used mostly internally by DeviceRegistrationInfo
+// but also exposed so that tests can access them.
+namespace buffet {
+namespace storage_keys {
+
+// Persistent keys
+extern const char kClientId[];
+extern const char kClientSecret[];
+extern const char kApiKey[];
+extern const char kRefreshToken[];
+extern const char kDeviceId[];
+extern const char kOAuthURL[];
+extern const char kServiceURL[];
+extern const char kRobotAccount[];
+// Transient keys
+extern const char kDeviceKind[];
+extern const char kSystemName[];
+extern const char kDisplayName[];
+
+} // namespace storage_keys
+} // namespace buffet
+
+#endif // BUFFET_DEVICE_REGISTRATION_STORAGE_KEYS_H_
diff --git a/buffet/error.cc b/buffet/error.cc
new file mode 100644
index 0000000..bccb9b8
--- /dev/null
+++ b/buffet/error.cc
@@ -0,0 +1,63 @@
+// Copyright 2014 The Chromium OS 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 "buffet/error.h"
+
+#include <base/logging.h>
+
+using buffet::Error;
+using buffet::ErrorPtr;
+
+ErrorPtr Error::Create(const std::string& domain,
+ const std::string& code,
+ const std::string& message) {
+ return Create(domain, code, message, ErrorPtr());
+}
+
+ErrorPtr Error::Create(const std::string& domain,
+ const std::string& code,
+ const std::string& message,
+ ErrorPtr inner_error) {
+ LOG(ERROR) << "Error::Create: Domain=" << domain
+ << ", Code=" << code << ", Message=" << message;
+ return ErrorPtr(new Error(domain, code, message, std::move(inner_error)));
+}
+
+void Error::AddTo(ErrorPtr* error, const std::string& domain,
+ const std::string& code, const std::string& message) {
+ if (error) {
+ *error = Create(domain, code, message, std::move(*error));
+ } else {
+ // Create already logs the error, but if |error| is nullptr,
+ // we still want to log the error...
+ LOG(ERROR) << "Error::Create: Domain=" << domain
+ << ", Code=" << code << ", Message=" << message;
+ }
+}
+
+bool Error::HasDomain(const std::string& domain) const {
+ const Error* err = this;
+ while (err) {
+ if (err->GetDomain() == domain)
+ return true;
+ err = err->GetInnerError();
+ }
+ return false;
+}
+
+bool Error::HasError(const std::string& domain, const std::string& code) const {
+ const Error* err = this;
+ while (err) {
+ if (err->GetDomain() == domain && err->GetCode() == code)
+ return true;
+ err = err->GetInnerError();
+ }
+ return false;
+}
+
+Error::Error(const std::string& domain, const std::string& code,
+ const std::string& message, ErrorPtr inner_error) :
+ domain_(domain), code_(code), message_(message),
+ inner_error_(std::move(inner_error)) {
+}
diff --git a/buffet/error.h b/buffet/error.h
new file mode 100644
index 0000000..2f5ba66
--- /dev/null
+++ b/buffet/error.h
@@ -0,0 +1,71 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_ERROR_H_
+#define BUFFET_ERROR_H_
+
+#include <memory>
+#include <string>
+
+#include <base/basictypes.h>
+
+namespace buffet {
+
+class Error; // Forward declaration.
+
+typedef std::unique_ptr<Error> ErrorPtr;
+
+class Error {
+ public:
+ virtual ~Error() = default;
+
+ // Creates an instance of Error class.
+ static ErrorPtr Create(const std::string& domain, const std::string& code,
+ const std::string& message);
+ static ErrorPtr Create(const std::string& domain, const std::string& code,
+ const std::string& message, ErrorPtr inner_error);
+ // If |error| is not nullptr, creates another instance of Error class,
+ // initializes it with specified arguments and adds it to the head of
+ // the error chain pointed to by |error|.
+ static void AddTo(ErrorPtr* error, const std::string& domain,
+ const std::string& code, const std::string& message);
+
+ // Returns the error domain, code and message
+ const std::string& GetDomain() const { return domain_; }
+ const std::string& GetCode() const { return code_; }
+ const std::string& GetMessage() const { return message_; }
+
+ // Checks if this or any of the inner error in the chain has the specified
+ // error domain.
+ bool HasDomain(const std::string& domain) const;
+ // Checks if this or any of the inner error in the chain matches the specified
+ // error domain and code.
+ bool HasError(const std::string& domain, const std::string& code) const;
+
+ // Gets a pointer to the inner error, if present. Returns nullptr otherwise.
+ const Error* GetInnerError() const { return inner_error_.get(); }
+
+ protected:
+ // Constructor is protected since this object is supposed to be
+ // created via the Create factory methods.
+ Error(const std::string& domain, const std::string& code,
+ const std::string& message, ErrorPtr inner_error);
+
+ // Error domain. The domain defines the scopes for error codes.
+ // Two errors with the same code but different domains are different errors.
+ std::string domain_;
+ // Error code. A unique error code identifier within the given domain.
+ std::string code_;
+ // Human-readable error message.
+ std::string message_;
+ // Pointer to inner error, if any. This forms a chain of errors.
+ ErrorPtr inner_error_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Error);
+};
+
+} // namespace buffet
+
+#endif // BUFFET_ERROR_H_
diff --git a/buffet/error_unittest.cc b/buffet/error_unittest.cc
new file mode 100644
index 0000000..cf943ad
--- /dev/null
+++ b/buffet/error_unittest.cc
@@ -0,0 +1,56 @@
+// Copyright 2014 The Chromium OS 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 <gtest/gtest.h>
+#include <base/files/file_path.h>
+
+#include "buffet/error.h"
+
+using buffet::Error;
+
+namespace {
+
+buffet::ErrorPtr GenerateNetworkError() {
+ return Error::Create("network", "not_found", "Resource not found");
+}
+
+buffet::ErrorPtr GenerateHttpError() {
+ auto inner = GenerateNetworkError();
+ return Error::Create("HTTP", "404", "Not found", std::move(inner));
+}
+
+} // namespace
+
+TEST(Error, Single) {
+ auto err = GenerateNetworkError();
+ EXPECT_EQ("network", err->GetDomain());
+ EXPECT_EQ("not_found", err->GetCode());
+ EXPECT_EQ("Resource not found", err->GetMessage());
+ EXPECT_EQ(nullptr, err->GetInnerError());
+ EXPECT_TRUE(err->HasDomain("network"));
+ EXPECT_FALSE(err->HasDomain("HTTP"));
+ EXPECT_FALSE(err->HasDomain("foo"));
+ EXPECT_TRUE(err->HasError("network", "not_found"));
+ EXPECT_FALSE(err->HasError("network", "404"));
+ EXPECT_FALSE(err->HasError("HTTP", "404"));
+ EXPECT_FALSE(err->HasError("HTTP", "not_found"));
+ EXPECT_FALSE(err->HasError("foo", "bar"));
+}
+
+TEST(Error, Nested) {
+ auto err = GenerateHttpError();
+ EXPECT_EQ("HTTP", err->GetDomain());
+ EXPECT_EQ("404", err->GetCode());
+ EXPECT_EQ("Not found", err->GetMessage());
+ EXPECT_NE(nullptr, err->GetInnerError());
+ EXPECT_EQ("network", err->GetInnerError()->GetDomain());
+ EXPECT_TRUE(err->HasDomain("network"));
+ EXPECT_TRUE(err->HasDomain("HTTP"));
+ EXPECT_FALSE(err->HasDomain("foo"));
+ EXPECT_TRUE(err->HasError("network", "not_found"));
+ EXPECT_FALSE(err->HasError("network", "404"));
+ EXPECT_TRUE(err->HasError("HTTP", "404"));
+ EXPECT_FALSE(err->HasError("HTTP", "not_found"));
+ EXPECT_FALSE(err->HasError("foo", "bar"));
+}
diff --git a/buffet/exported_object_manager.cc b/buffet/exported_object_manager.cc
new file mode 100644
index 0000000..c1403ad
--- /dev/null
+++ b/buffet/exported_object_manager.cc
@@ -0,0 +1,133 @@
+// Copyright 2014 The Chromium OS 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 "buffet/exported_object_manager.h"
+
+#include <dbus/object_manager.h>
+
+#include "buffet/async_event_sequencer.h"
+
+namespace buffet {
+
+namespace dbus_utils {
+
+ExportedObjectManager::ExportedObjectManager(scoped_refptr<dbus::Bus> bus,
+ const dbus::ObjectPath& path)
+ : bus_(bus), exported_object_(bus->GetExportedObject(path)) {}
+
+void ExportedObjectManager::Init(const OnInitFinish& cb) {
+ bus_->AssertOnOriginThread();
+ scoped_refptr<dbus_utils::AsyncEventSequencer> sequencer(
+ new dbus_utils::AsyncEventSequencer());
+ exported_object_->ExportMethod(
+ dbus::kObjectManagerInterface,
+ dbus::kObjectManagerGetManagedObjects,
+ base::Bind(&ExportedObjectManager::HandleGetManagedObjects,
+ AsWeakPtr()),
+ sequencer->GetExportHandler(
+ dbus::kObjectManagerInterface,
+ dbus::kObjectManagerGetManagedObjects,
+ "Failed exporting GetManagedObjects method of ObjectManager",
+ false));
+ sequencer->OnAllTasksCompletedCall({cb});
+}
+
+void ExportedObjectManager::ClaimInterface(
+ const dbus::ObjectPath& path,
+ const std::string& interface_name,
+ const PropertyWriter& property_writer) {
+ bus_->AssertOnOriginThread();
+ // We're sending signals that look like:
+ // org.freedesktop.DBus.ObjectManager.InterfacesAdded (
+ // OBJPATH object_path,
+ // DICT<STRING,DICT<STRING,VARIANT>> interfaces_and_properties);
+ dbus::Signal signal(dbus::kObjectManagerInterface,
+ dbus::kObjectManagerInterfacesAdded);
+ dbus::MessageWriter signal_writer(&signal);
+ dbus::MessageWriter all_interfaces(&signal);
+ dbus::MessageWriter each_interface(&signal);
+ signal_writer.AppendObjectPath(path);
+ signal_writer.OpenArray("{sa{sv}}", &all_interfaces);
+ all_interfaces.OpenDictEntry(&each_interface);
+ each_interface.AppendString(interface_name);
+ property_writer.Run(&each_interface);
+ all_interfaces.CloseContainer(&each_interface);
+ signal_writer.CloseContainer(&all_interfaces);
+ exported_object_->SendSignal(&signal);
+ registered_objects_[path][interface_name] = property_writer;
+}
+
+void ExportedObjectManager::ReleaseInterface(
+ const dbus::ObjectPath& path, const std::string& interface_name) {
+ bus_->AssertOnOriginThread();
+ auto interfaces_for_path_itr = registered_objects_.find(path);
+ CHECK(interfaces_for_path_itr != registered_objects_.end())
+ << "Attempting to signal interface removal for path " << path.value()
+ << " which was never registered.";
+ auto interfaces_for_path = interfaces_for_path_itr->second;
+ auto property_for_interface_itr = interfaces_for_path.find(interface_name);
+ CHECK(property_for_interface_itr != interfaces_for_path.end())
+ << "Attempted to remove interface " << interface_name << " from "
+ << path.value() << ", but this interface was never registered.";
+ interfaces_for_path.erase(interface_name);
+ if (interfaces_for_path.size() < 1) {
+ registered_objects_.erase(path);
+ }
+ // We're sending signals that look like:
+ // org.freedesktop.DBus.ObjectManager.InterfacesRemoved (
+ // OBJPATH object_path, ARRAY<STRING> interfaces);
+ dbus::Signal signal(dbus::kObjectManagerInterface,
+ dbus::kObjectManagerInterfacesRemoved);
+ dbus::MessageWriter signal_writer(&signal);
+ signal_writer.AppendObjectPath(path);
+ dbus::MessageWriter interface_writer(nullptr);
+ signal_writer.OpenArray("s", &interface_writer);
+ interface_writer.AppendString(interface_name);
+ signal_writer.CloseContainer(&interface_writer);
+ exported_object_->SendSignal(&signal);
+}
+
+void ExportedObjectManager::HandleGetManagedObjects(
+ dbus::MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender) const {
+ // Implements the GetManagedObjects method:
+ //
+ // org.freedesktop.DBus.ObjectManager.GetManagedObjects (
+ // out DICT<OBJPATH,
+ // DICT<STRING,
+ // DICT<STRING,VARIANT>>> )
+ bus_->AssertOnOriginThread();
+ scoped_ptr<dbus::Response> response(
+ dbus::Response::FromMethodCall(method_call));
+ dbus::MessageWriter response_writer(response.get());
+ dbus::MessageWriter all_object_paths(nullptr);
+ dbus::MessageWriter each_object_path(nullptr);
+ dbus::MessageWriter all_interfaces(nullptr);
+ dbus::MessageWriter each_interface(nullptr);
+
+ response_writer.OpenArray("{oa{sa{sv}}}", &all_object_paths);
+ for (const auto path_pair : registered_objects_) {
+ const dbus::ObjectPath& path = path_pair.first;
+ const InterfaceProperties& interface2properties = path_pair.second;
+ all_object_paths.OpenDictEntry(&each_object_path);
+ each_object_path.AppendObjectPath(path);
+ each_object_path.OpenArray("{sa{sv}}", &all_interfaces);
+ for (const auto interface : interface2properties) {
+ const std::string& interface_name = interface.first;
+ const PropertyWriter& property_writer = interface.second;
+ all_interfaces.OpenDictEntry(&each_interface);
+ each_interface.AppendString(interface_name);
+ property_writer.Run(&each_interface);
+ all_interfaces.CloseContainer(&each_interface);
+ }
+ each_object_path.CloseContainer(&all_interfaces);
+ all_object_paths.CloseContainer(&each_object_path);
+ }
+ response_writer.CloseContainer(&all_object_paths);
+ response_sender.Run(response.Pass());
+}
+
+} // namespace dbus_utils
+
+} // namespace buffet
diff --git a/buffet/exported_object_manager.h b/buffet/exported_object_manager.h
new file mode 100644
index 0000000..d483db0
--- /dev/null
+++ b/buffet/exported_object_manager.h
@@ -0,0 +1,117 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_EXPORTED_OBJECT_MANAGER_H_
+#define BUFFET_EXPORTED_OBJECT_MANAGER_H_
+
+#include <map>
+#include <string>
+
+#include <base/memory/weak_ptr.h>
+#include <dbus/bus.h>
+#include <dbus/exported_object.h>
+#include <dbus/object_path.h>
+
+#include "buffet/exported_property_set.h"
+
+namespace buffet {
+
+namespace dbus_utils {
+
+// ExportedObjectManager is a delegate that implements the
+// org.freedesktop.DBus.ObjectManager interface on behalf of another
+// object. It handles sending signals when new interfaces are added.
+//
+// This class is very similar to the ExportedPropertySet class, except that
+// it allows objects to expose an object manager interface rather than the
+// properties interface.
+//
+// Example usage:
+//
+// class ExampleObjectManager {
+// public:
+// ExampleObjectManager(dbus::Bus* bus)
+// : object_manager_(bus, "/my/objects/path") { }
+//
+// void Init(const OnInitFinish& cb) { object_manager_.Init(cb); }
+// void ClaimInterface(const dbus::ObjectPath& path,
+// const std::string& interface_name,
+// const PropertyWriter& writer) {
+// object_manager_->ClaimInterface(...);
+// }
+// void ReleaseInterface(const dbus::ObjectPath& path,
+// const std::string& interface_name) {
+// object_manager_->ReleaseInterface(...);
+// }
+//
+// private:
+// ExportedObjectManager object_manager_;
+// };
+//
+// class MyObjectClaimingAnInterface {
+// public:
+// MyObjectClaimingAnInterface(ExampleObjectManager* object_manager)
+// : object_manager_(object_manager) {}
+//
+// void OnInitFinish(bool success) {
+// if (!success) { /* handle that */ }
+// object_manager_->ClaimInterface(
+// my_path_, my_interface_, my_properties_.GetWriter());
+// }
+//
+// private:
+// struct Properties : public ExportedPropertySet {
+// public:
+// /* Lots of interesting properties. */
+// };
+//
+// Properties my_properties_;
+// ExampleObjectManager* object_manager_;
+// };
+class ExportedObjectManager
+ : public base::SupportsWeakPtr<ExportedObjectManager> {
+ public:
+ // Writes a dictionary of property name to property value variants to writer.
+ typedef base::Callback<void(dbus::MessageWriter* writer)> PropertyWriter;
+ typedef base::Callback<void(bool success)> OnInitFinish;
+ typedef std::map<std::string, PropertyWriter> InterfaceProperties;
+
+ ExportedObjectManager(scoped_refptr<dbus::Bus> bus,
+ const dbus::ObjectPath& path);
+
+ // Registers methods implementing the ObjectManager interface on the object
+ // exported on the path given in the constructor. Must be called on the
+ // origin thread.
+ void Init(const OnInitFinish& cb);
+
+ // Trigger a signal that |path| has added an interface |interface_name|
+ // with properties as given by |writer|.
+ void ClaimInterface(const dbus::ObjectPath& path,
+ const std::string& interface_name,
+ const PropertyWriter& writer);
+
+ // Trigger a signal that |path| has removed an interface |interface_name|.
+ void ReleaseInterface(const dbus::ObjectPath& path,
+ const std::string& interface_name);
+
+ private:
+ void HandleGetManagedObjects(
+ dbus::MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender) const;
+
+ // Both |bus_| and |exported_object_| outlive *this.
+ dbus::Bus* const bus_;
+ dbus::ExportedObject* const exported_object_;
+ // Tracks all objects currently known to the ExportedObjectManager.
+ std::map<dbus::ObjectPath, InterfaceProperties> registered_objects_;
+
+ friend class ExportedObjectManagerTest;
+ DISALLOW_COPY_AND_ASSIGN(ExportedObjectManager);
+};
+
+} // namespace dbus_utils
+
+} // namespace buffet
+
+#endif // BUFFET_EXPORTED_OBJECT_MANAGER_H_
diff --git a/buffet/exported_object_manager_unittest.cc b/buffet/exported_object_manager_unittest.cc
new file mode 100644
index 0000000..696b76f
--- /dev/null
+++ b/buffet/exported_object_manager_unittest.cc
@@ -0,0 +1,205 @@
+// Copyright 2014 The Chromium OS 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 "buffet/exported_object_manager.h"
+
+#include <base/bind.h>
+#include <dbus/mock_bus.h>
+#include <dbus/mock_exported_object.h>
+#include <dbus/object_manager.h>
+#include <dbus/object_path.h>
+#include <gtest/gtest.h>
+
+using ::testing::AnyNumber;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::_;
+
+namespace buffet {
+
+namespace dbus_utils {
+
+namespace {
+
+const dbus::ObjectPath kTestPath(std::string("/test/om_path"));
+const dbus::ObjectPath kClaimedTestPath(std::string("/test/claimed_path"));
+const std::string kClaimedInterface("claimed.interface");
+const std::string kTestPropertyName("PropertyName");
+const std::string kTestPropertyValue("PropertyValue");
+
+void WriteTestPropertyDict(dbus::MessageWriter* writer) {
+ dbus::MessageWriter all_properties(nullptr);
+ dbus::MessageWriter each_property(nullptr);
+ writer->OpenArray("{sv}", &all_properties);
+ all_properties.OpenDictEntry(&each_property);
+ each_property.AppendString(kTestPropertyName);
+ each_property.AppendVariantOfString(kTestPropertyValue);
+ all_properties.CloseContainer(&each_property);
+ writer->CloseContainer(&all_properties);
+}
+
+void ReadTestPropertyDict(dbus::MessageReader* reader) {
+ dbus::MessageReader all_properties(nullptr);
+ dbus::MessageReader each_property(nullptr);
+ ASSERT_TRUE(reader->PopArray(&all_properties));
+ ASSERT_TRUE(all_properties.PopDictEntry(&each_property));
+ std::string property_name;
+ std::string property_value;
+ ASSERT_TRUE(each_property.PopString(&property_name));
+ ASSERT_TRUE(each_property.PopVariantOfString(&property_value));
+ EXPECT_FALSE(each_property.HasMoreData());
+ EXPECT_FALSE(all_properties.HasMoreData());
+ EXPECT_EQ(property_name, kTestPropertyName);
+ EXPECT_EQ(property_value, kTestPropertyValue);
+}
+
+void VerifyInterfaceClaimSignal(dbus::Signal* signal) {
+ EXPECT_EQ(signal->GetInterface(),
+ std::string(dbus::kObjectManagerInterface));
+ EXPECT_EQ(signal->GetMember(),
+ std::string(dbus::kObjectManagerInterfacesAdded));
+ // org.freedesktop.DBus.ObjectManager.InterfacesAdded (
+ // OBJPATH object_path,
+ // DICT<STRING,DICT<STRING,VARIANT>> interfaces_and_properties);
+ dbus::MessageReader reader(signal);
+ dbus::MessageReader all_interfaces(nullptr);
+ dbus::MessageReader each_interface(nullptr);
+ dbus::ObjectPath path;
+ ASSERT_TRUE(reader.PopObjectPath(&path));
+ ASSERT_TRUE(reader.PopArray(&all_interfaces));
+ ASSERT_TRUE(all_interfaces.PopDictEntry(&each_interface));
+ std::string interface_name;
+ ASSERT_TRUE(each_interface.PopString(&interface_name));
+ ReadTestPropertyDict(&each_interface);
+ EXPECT_FALSE(each_interface.HasMoreData());
+ EXPECT_FALSE(all_interfaces.HasMoreData());
+ EXPECT_FALSE(reader.HasMoreData());
+ EXPECT_EQ(interface_name, kClaimedInterface);
+ EXPECT_EQ(path, kClaimedTestPath);
+}
+
+void VerifyInterfaceDropSignal(dbus::Signal* signal) {
+ EXPECT_EQ(signal->GetInterface(),
+ std::string(dbus::kObjectManagerInterface));
+ EXPECT_EQ(signal->GetMember(),
+ std::string(dbus::kObjectManagerInterfacesRemoved));
+ // org.freedesktop.DBus.ObjectManager.InterfacesRemoved (
+ // OBJPATH object_path, ARRAY<STRING> interfaces);
+ dbus::MessageReader reader(signal);
+ dbus::MessageReader each_interface(nullptr);
+ dbus::ObjectPath path;
+ ASSERT_TRUE(reader.PopObjectPath(&path));
+ ASSERT_TRUE(reader.PopArray(&each_interface));
+ std::string interface_name;
+ ASSERT_TRUE(each_interface.PopString(&interface_name));
+ EXPECT_FALSE(each_interface.HasMoreData());
+ EXPECT_FALSE(reader.HasMoreData());
+ EXPECT_EQ(interface_name, kClaimedInterface);
+ EXPECT_EQ(path, kClaimedTestPath);
+}
+
+} // namespace
+
+class ExportedObjectManagerTest: public ::testing::Test {
+ public:
+ virtual void SetUp() {
+ dbus::Bus::Options options;
+ options.bus_type = dbus::Bus::SYSTEM;
+ bus_ = new dbus::MockBus(options);
+ // By default, don't worry about threading assertions.
+ EXPECT_CALL(*bus_, AssertOnOriginThread()).Times(AnyNumber());
+ EXPECT_CALL(*bus_, AssertOnDBusThread()).Times(AnyNumber());
+ // Use a mock exported object.
+ mock_exported_object_ = new dbus::MockExportedObject(
+ bus_.get(), kTestPath);
+ EXPECT_CALL(*bus_, GetExportedObject(kTestPath))
+ .Times(1).WillOnce(Return(mock_exported_object_.get()));
+ om_.reset(new ExportedObjectManager(bus_.get(), kTestPath));
+ property_writer_ = base::Bind(&WriteTestPropertyDict);
+ response_storer_ = base::Bind(&ExportedObjectManagerTest::StoreResponse,
+ base::Unretained(this));
+ }
+
+ void StoreResponse(scoped_ptr<dbus::Response> method_response) {
+ last_response_.reset(method_response.release());
+ }
+
+ void CallHandleGetManagedObjects(
+ dbus::MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender sender) {
+ om_->HandleGetManagedObjects(method_call, response_storer_);
+ }
+
+ scoped_refptr<dbus::MockBus> bus_;
+ scoped_refptr<dbus::MockExportedObject> mock_exported_object_;
+ scoped_ptr<ExportedObjectManager> om_;
+ ExportedObjectManager::PropertyWriter property_writer_;
+ dbus::ExportedObject::ResponseSender response_storer_;
+ scoped_ptr<dbus::Response> last_response_;
+};
+
+TEST_F(ExportedObjectManagerTest, ClaimInterfaceSendsSignals) {
+ EXPECT_CALL(*mock_exported_object_, SendSignal(_))
+ .Times(1).WillOnce(Invoke(&VerifyInterfaceClaimSignal));
+ om_->ClaimInterface(kClaimedTestPath, kClaimedInterface, property_writer_);
+}
+
+TEST_F(ExportedObjectManagerTest, ReleaseInterfaceSendsSignals) {
+ InSequence dummy;
+ EXPECT_CALL(*mock_exported_object_, SendSignal(_)).Times(1);
+ EXPECT_CALL(*mock_exported_object_, SendSignal(_))
+ .Times(1).WillOnce(Invoke(&VerifyInterfaceDropSignal));
+ om_->ClaimInterface(kClaimedTestPath, kClaimedInterface, property_writer_);
+ om_->ReleaseInterface(kClaimedTestPath, kClaimedInterface);
+}
+
+TEST_F(ExportedObjectManagerTest, GetManagedObjectsResponseEmptyCorrectness) {
+ dbus::MethodCall method_call(dbus::kObjectManagerInterface,
+ dbus::kObjectManagerGetManagedObjects);
+ method_call.SetSerial(123);
+ CallHandleGetManagedObjects(&method_call, response_storer_);
+ dbus::MessageReader reader(last_response_.get());
+ dbus::MessageReader all_paths(nullptr);
+ ASSERT_TRUE(reader.PopArray(&all_paths));
+ EXPECT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedObjectManagerTest, GetManagedObjectsResponseCorrectness) {
+ // org.freedesktop.DBus.ObjectManager.GetManagedObjects (
+ // out DICT<OBJPATH,
+ // DICT<STRING,
+ // DICT<STRING,VARIANT>>> )
+ dbus::MethodCall method_call(dbus::kObjectManagerInterface,
+ dbus::kObjectManagerGetManagedObjects);
+ method_call.SetSerial(123);
+ EXPECT_CALL(*mock_exported_object_, SendSignal(_)).Times(1);
+ om_->ClaimInterface(kClaimedTestPath, kClaimedInterface, property_writer_);
+ CallHandleGetManagedObjects(&method_call, response_storer_);
+ dbus::MessageReader reader(last_response_.get());
+ dbus::MessageReader all_paths(nullptr);
+ dbus::MessageReader each_path(nullptr);
+ dbus::MessageReader all_interfaces(nullptr);
+ dbus::MessageReader each_interface(nullptr);
+ ASSERT_TRUE(reader.PopArray(&all_paths));
+ ASSERT_TRUE(all_paths.PopDictEntry(&each_path));
+ dbus::ObjectPath path;
+ ASSERT_TRUE(each_path.PopObjectPath(&path));
+ ASSERT_TRUE(each_path.PopArray(&all_interfaces));
+ ASSERT_TRUE(all_interfaces.PopDictEntry(&each_interface));
+ std::string interface_name;
+ ASSERT_TRUE(each_interface.PopString(&interface_name));
+ ReadTestPropertyDict(&each_interface);
+ EXPECT_FALSE(each_interface.HasMoreData());
+ EXPECT_FALSE(all_interfaces.HasMoreData());
+ EXPECT_FALSE(each_path.HasMoreData());
+ EXPECT_FALSE(all_paths.HasMoreData());
+ EXPECT_FALSE(reader.HasMoreData());
+ EXPECT_EQ(path, kClaimedTestPath);
+ EXPECT_EQ(interface_name, kClaimedInterface);
+}
+
+} // namespace dbus_utils
+
+} // namespace buffet
diff --git a/buffet/exported_property_set.cc b/buffet/exported_property_set.cc
new file mode 100644
index 0000000..b1b5633
--- /dev/null
+++ b/buffet/exported_property_set.cc
@@ -0,0 +1,338 @@
+// Copyright 2014 The Chromium OS 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 "buffet/exported_property_set.h"
+
+#include <base/bind.h>
+#include <dbus/bus.h> // For kPropertyInterface
+#include <dbus/property.h> // For kPropertyInterface
+
+#include "buffet/async_event_sequencer.h"
+#include "buffet/dbus_utils.h"
+
+namespace buffet {
+
+namespace dbus_utils {
+
+namespace {
+const char kExportFailedMessage[] = "Failed to register DBus method.";
+} // namespace
+
+ExportedPropertySet::ExportedPropertySet(dbus::Bus* bus,
+ const dbus::ObjectPath& path)
+ : bus_(bus), exported_object_(bus->GetExportedObject(path)),
+ weak_ptr_factory_(this) { }
+
+void ExportedPropertySet::Init(const OnInitFinish& cb) {
+ bus_->AssertOnOriginThread();
+ scoped_refptr<AsyncEventSequencer> sequencer(new AsyncEventSequencer());
+ exported_object_->ExportMethod(
+ dbus::kPropertiesInterface, dbus::kPropertiesGetAll,
+ base::Bind(&ExportedPropertySet::HandleGetAll,
+ weak_ptr_factory_.GetWeakPtr()),
+ sequencer->GetExportHandler(
+ dbus::kPropertiesInterface, dbus::kPropertiesGetAll,
+ kExportFailedMessage, false));
+ exported_object_->ExportMethod(
+ dbus::kPropertiesInterface, dbus::kPropertiesGet,
+ base::Bind(&ExportedPropertySet::HandleGet,
+ weak_ptr_factory_.GetWeakPtr()),
+ sequencer->GetExportHandler(
+ dbus::kPropertiesInterface, dbus::kPropertiesGet,
+ kExportFailedMessage, false));
+ exported_object_->ExportMethod(
+ dbus::kPropertiesInterface, dbus::kPropertiesSet,
+ base::Bind(&ExportedPropertySet::HandleSet,
+ weak_ptr_factory_.GetWeakPtr()),
+ sequencer->GetExportHandler(
+ dbus::kPropertiesInterface, dbus::kPropertiesSet,
+ kExportFailedMessage, false));
+ sequencer->OnAllTasksCompletedCall({cb});
+}
+
+ExportedPropertySet::PropertyWriter ExportedPropertySet::GetPropertyWriter(
+ const std::string& interface) {
+ return base::Bind(&ExportedPropertySet::WritePropertiesDictToMessage,
+ weak_ptr_factory_.GetWeakPtr(),
+ interface);
+}
+
+void ExportedPropertySet::RegisterProperty(
+ const std::string& interface_name,
+ const std::string& property_name,
+ ExportedPropertyBase* exported_property) {
+ bus_->AssertOnOriginThread();
+ properties_[interface_name][property_name] = exported_property;
+ // Technically, the property set exists longer than the properties themselves,
+ // so we could use Unretained here rather than a weak pointer.
+ ExportedPropertyBase::OnUpdateCallback cb = base::Bind(
+ &ExportedPropertySet::HandlePropertyUpdated,
+ weak_ptr_factory_.GetWeakPtr(),
+ interface_name, property_name);
+ exported_property->SetUpdateCallback(cb);
+}
+
+void ExportedPropertySet::HandleGetAll(
+ dbus::MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender) {
+ bus_->AssertOnOriginThread();
+ dbus::MessageReader reader(method_call);
+ std::string interface_name;
+ if (!reader.PopString(&interface_name)) {
+ response_sender.Run(
+ GetBadArgsError(method_call, "No interface name specified."));
+ return;
+ }
+ if (reader.HasMoreData()) {
+ response_sender.Run(
+ GetBadArgsError(method_call, "Too many arguments to GetAll."));
+ return;
+ }
+ scoped_ptr<dbus::Response> response(
+ dbus::Response::FromMethodCall(method_call));
+ dbus::MessageWriter resp_writer(response.get());
+ WritePropertiesDictToMessage(interface_name, &resp_writer);
+ response_sender.Run(response.Pass());
+}
+
+void ExportedPropertySet::WritePropertiesDictToMessage(
+ const std::string& interface_name,
+ dbus::MessageWriter* writer) {
+ dbus::MessageWriter dict_writer(nullptr);
+ writer->OpenArray("{sv}", &dict_writer);
+ auto property_map_itr = properties_.find(interface_name);
+ if (property_map_itr != properties_.end()) {
+ for (const auto& kv : property_map_itr->second) {
+ dbus::MessageWriter entry_writer(nullptr);
+ dict_writer.OpenDictEntry(&entry_writer);
+ entry_writer.AppendString(kv.first);
+ kv.second->AppendValueToWriter(&entry_writer);
+ dict_writer.CloseContainer(&entry_writer);
+ }
+ } else {
+ LOG(WARNING) << "No properties found for interface interface_name";
+ }
+ writer->CloseContainer(&dict_writer);
+}
+
+void ExportedPropertySet::HandleGet(
+ dbus::MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender) {
+ bus_->AssertOnOriginThread();
+ dbus::MessageReader reader(method_call);
+ std::string interface_name, property_name;
+ if (!reader.PopString(&interface_name)) {
+ response_sender.Run(
+ GetBadArgsError(method_call, "No interface name specified."));
+ return;
+ }
+ if (!reader.PopString(&property_name)) {
+ response_sender.Run(
+ GetBadArgsError(method_call, "No property name specified."));
+ return;
+ }
+ if (reader.HasMoreData()) {
+ response_sender.Run(
+ GetBadArgsError(method_call, "Too many arguments to Get."));
+ return;
+ }
+ auto property_map_itr = properties_.find(interface_name);
+ if (property_map_itr == properties_.end()) {
+ response_sender.Run(
+ GetBadArgsError(method_call, "No such interface on object."));
+ return;
+ }
+ LOG(ERROR) << "Looking for " << property_name << " on " << interface_name;
+ auto property_itr = property_map_itr->second.find(property_name);
+ if (property_itr == property_map_itr->second.end()) {
+ response_sender.Run(
+ GetBadArgsError(method_call, "No such property on interface."));
+ return;
+ }
+ scoped_ptr<dbus::Response> response(
+ dbus::Response::FromMethodCall(method_call));
+ dbus::MessageWriter resp_writer(response.get());
+ property_itr->second->AppendValueToWriter(&resp_writer);
+ response_sender.Run(response.Pass());
+}
+
+void ExportedPropertySet::HandleSet(
+ dbus::MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender) {
+ bus_->AssertOnOriginThread();
+ scoped_ptr<dbus::ErrorResponse> error_resp(
+ dbus::ErrorResponse::FromMethodCall(
+ method_call, "org.freedesktop.DBus.Error.NotSupported", ""));
+ scoped_ptr<dbus::Response> response(error_resp.release());
+ response_sender.Run(response.Pass());
+}
+
+void ExportedPropertySet::HandlePropertyUpdated(
+ const std::string& interface,
+ const std::string& name,
+ const ExportedPropertyBase* property) {
+ bus_->AssertOnOriginThread();
+ dbus::Signal signal(dbus::kPropertiesInterface, dbus::kPropertiesChanged);
+ dbus::MessageWriter writer(&signal);
+ dbus::MessageWriter array_writer(nullptr);
+ dbus::MessageWriter dict_writer(nullptr);
+ writer.AppendString(interface);
+ writer.OpenArray("{sv}", &array_writer);
+ array_writer.OpenDictEntry(&dict_writer);
+ dict_writer.AppendString(name);
+ property->AppendValueToWriter(&dict_writer);
+ array_writer.CloseContainer(&dict_writer);
+ writer.CloseContainer(&array_writer);
+ // The interface specification tells us to include this list of properties
+ // which have changed, but for whom no value is conveyed. Currently, we
+ // don't do anything interesting here.
+ writer.OpenArray("s", &array_writer);
+ writer.CloseContainer(&array_writer);
+ // This sends the signal asyncronously. However, the raw message inside
+ // the signal object is ref-counted, so we're fine to allocate the Signal
+ // object on our local stack.
+ exported_object_->SendSignal(&signal);
+}
+
+template <typename T>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const T& value);
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const bool& value) {
+ writer->AppendVariantOfBool(value);
+}
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const uint8& value) {
+ writer->AppendVariantOfByte(value);
+}
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const int16& value) {
+ writer->AppendVariantOfInt16(value);
+}
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const uint16& value) {
+ writer->AppendVariantOfUint16(value);
+}
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const int32& value) {
+ writer->AppendVariantOfInt32(value);
+}
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const uint32& value) {
+ writer->AppendVariantOfUint32(value);
+}
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const int64& value) {
+ writer->AppendVariantOfInt64(value);
+}
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const uint64& value) {
+ writer->AppendVariantOfUint64(value);
+}
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const double& value) {
+ writer->AppendVariantOfDouble(value);
+}
+
+template <>
+void AppendPropertyToWriter(
+ dbus::MessageWriter* writer, const std::string& value) {
+ writer->AppendVariantOfString(value);
+}
+
+template <>
+void AppendPropertyToWriter(
+ dbus::MessageWriter* writer, const dbus::ObjectPath& value) {
+ writer->AppendVariantOfObjectPath(value);
+}
+
+template <>
+void AppendPropertyToWriter(
+ dbus::MessageWriter* writer, const std::vector<std::string>& value) {
+ dbus::MessageWriter variant_writer(nullptr);
+ writer->OpenVariant("as", &variant_writer);
+ variant_writer.AppendArrayOfStrings(value);
+ writer->CloseContainer(&variant_writer);
+}
+
+template <>
+void AppendPropertyToWriter(
+ dbus::MessageWriter* writer, const std::vector<dbus::ObjectPath>& value) {
+ dbus::MessageWriter variant_writer(nullptr);
+ writer->OpenVariant("ao", &variant_writer);
+ variant_writer.AppendArrayOfObjectPaths(value);
+ writer->CloseContainer(&variant_writer);
+}
+
+template <>
+void AppendPropertyToWriter(
+ dbus::MessageWriter* writer, const std::vector<uint8>& value) {
+ dbus::MessageWriter variant_writer(nullptr);
+ writer->OpenVariant("ay", &variant_writer);
+ variant_writer.AppendArrayOfBytes(value.data(), value.size());
+ writer->CloseContainer(&variant_writer);
+}
+
+template <typename T>
+ExportedProperty<T>::ExportedProperty() {}
+
+template <typename T>
+ExportedProperty<T>::~ExportedProperty() {}
+
+template <typename T>
+const T& ExportedProperty<T>::value() const { return value_; }
+
+template <typename T>
+void ExportedProperty<T>::SetValue(const T& new_value) {
+ if (value_ == new_value) {
+ return;
+ }
+ value_ = new_value;
+ // These is a brief period after the construction of an ExportedProperty
+ // when this callback is not initialized because the property has not
+ // been registered with the parent ExportedPropertySet. During this period
+ // users should be initializing values via SetValue, and no notifications
+ // should be triggered by the ExportedPropertySet.
+ if (!on_update_.is_null()) {
+ on_update_.Run(this);
+ }
+}
+
+template <typename T>
+void ExportedProperty<T>::SetUpdateCallback(const OnUpdateCallback& cb) {
+ on_update_ = cb;
+}
+
+template <typename T>
+void ExportedProperty<T>::AppendValueToWriter(
+ dbus::MessageWriter* writer) const {
+ AppendPropertyToWriter(writer, value_);
+}
+
+template class ExportedProperty<bool>;
+template class ExportedProperty<uint8>;
+template class ExportedProperty<int16>;
+template class ExportedProperty<uint16>;
+template class ExportedProperty<int32>;
+template class ExportedProperty<uint32>;
+template class ExportedProperty<int64>;
+template class ExportedProperty<uint64>;
+template class ExportedProperty<double>;
+template class ExportedProperty<std::string>;
+template class ExportedProperty<dbus::ObjectPath>;
+template class ExportedProperty<std::vector<std::string>>;
+template class ExportedProperty<std::vector<dbus::ObjectPath>>;
+template class ExportedProperty<std::vector<uint8>>;
+
+} // namespace dbus_utils
+
+} // namespace buffet
diff --git a/buffet/exported_property_set.h b/buffet/exported_property_set.h
new file mode 100644
index 0000000..405ed7d
--- /dev/null
+++ b/buffet/exported_property_set.h
@@ -0,0 +1,200 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_EXPORTED_PROPERTY_SET_H_
+#define BUFFET_EXPORTED_PROPERTY_SET_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/memory/weak_ptr.h>
+#include <dbus/exported_object.h>
+#include <dbus/message.h>
+
+namespace buffet {
+
+namespace dbus_utils {
+
+// This class may be used to implement the org.freedesktop.DBus.Properties
+// interface. It sends the update signal on property updates:
+//
+// org.freedesktop.DBus.Properties.PropertiesChanged (
+// STRING interface_name,
+// DICT<STRING,VARIANT> changed_properties,
+// ARRAY<STRING> invalidated_properties);
+//
+//
+// and implements the required methods of the interface:
+//
+// org.freedesktop.DBus.Properties.Get(in STRING interface_name,
+// in STRING property_name,
+// out VARIANT value);
+// org.freedesktop.DBus.Properties.Set(in STRING interface_name,
+// in STRING property_name,
+// in VARIANT value);
+// org.freedesktop.DBus.Properties.GetAll(in STRING interface_name,
+// out DICT<STRING,VARIANT> props);
+//
+// This class is very similar to the PropertySet class in Chrome, except that
+// it allows objects to expose properties rather than to consume them.
+//
+// Example usage:
+//
+// class ExampleObjectExportingProperties {
+// public:
+// ExampleObjectExportingProperties(ExportedObject* exported_object)
+// : p_(exported_object) {
+// // Initialize properties appropriately. Do this before
+// // claiming the Properties interface so that daemons watching
+// // this object don't see partial or inaccurate state.
+// p_.ClaimPropertiesInterface();
+// }
+//
+// private:
+// struct Properties : public buffet::dbus::ExportedPropertySet {
+// public:
+// buffet::dbus::ExportedProperty<std::string> name_;
+// buffet::dbus::ExportedProperty<uint16> version_;
+// buffet::dbus::ExportedProperty<dbus::ObjectPath> parent_;
+// buffet::dbus::ExportedProperty<std::vector<std::string>> children_;
+//
+// Properties(dbus::ExportedObject* exported_object)
+// : buffet::dbus::ExportedPropertySet(exported_object) {
+// RegisterProperty(kExampleInterfaceName, "Name", &name_);
+// RegisterProperty(kExampleInterfaceName, "Version", &version_);
+// RegisterProperty(kExampleInterfaceName, "Parent", &parent_);
+// RegisterProperty(kExampleInterfaceName, "Children", &children_);
+// }
+// virtual ~Properties() {}
+// };
+//
+// Properties p_;
+// };
+
+class ExportedPropertyBase {
+ public:
+ ExportedPropertyBase() {}
+ virtual ~ExportedPropertyBase() {}
+
+ typedef base::Callback<void(const ExportedPropertyBase*)> OnUpdateCallback;
+
+ // Called by ExportedPropertySet to register a callback. This callback
+ // triggers ExportedPropertySet to send a signal from the properties
+ // interface of the exported object.
+ virtual void SetUpdateCallback(const OnUpdateCallback& cb) = 0;
+
+ // Appends a variant of the contained value to the writer. This is
+ // needed to write out properties to Get and GetAll methods implemented
+ // by the ExportedPropertySet since it doesn't actually know the type
+ // of each property.
+ virtual void AppendValueToWriter(dbus::MessageWriter* writer) const = 0;
+};
+
+class ExportedPropertySet {
+ public:
+ typedef base::Callback<void(bool success)> OnInitFinish;
+ typedef base::Callback<void(dbus::MessageWriter* writer)> PropertyWriter;
+
+ ExportedPropertySet(dbus::Bus* bus, const dbus::ObjectPath& path);
+ virtual ~ExportedPropertySet() = default;
+
+ // Claims the method associated with the org.freedesktop.DBus.Properties
+ // interface. This needs to be done after all properties are initialized to
+ // appropriate values. This method will call |cb| when all methods
+ // are exported to the DBus object. |cb| will be called on the origin
+ // thread.
+ void Init(const OnInitFinish& cb);
+
+ // Return a callback that knows how to write this property set's properties
+ // to a message. This writer retains a weak pointer to this, and must
+ // only be invoked on the same thread as the rest of ExportedPropertySet.
+ PropertyWriter GetPropertyWriter(const std::string& interface);
+
+ protected:
+ void RegisterProperty(const std::string& interface_name,
+ const std::string& property_name,
+ ExportedPropertyBase* exported_property);
+
+ private:
+ // Used to write the dictionary of string->variant to a message.
+ // This dictionary represents the property name/value pairs for the
+ // given interface.
+ void WritePropertiesDictToMessage(const std::string& interface_name,
+ dbus::MessageWriter* writer);
+ void HandleGetAll(dbus::MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender);
+ void HandleGet(dbus::MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender);
+ // While Properties.Set has a handler to complete the interface, we don't
+ // support writable properties. This is almost a feature, since bindings for
+ // many languages don't support errors coming back from invalid writes.
+ // Instead, use setters in exposed interfaces.
+ void HandleSet(dbus::MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender);
+ void HandlePropertyUpdated(const std::string& interface,
+ const std::string& name,
+ const ExportedPropertyBase* property);
+
+ dbus::Bus* bus_;
+ dbus::ExportedObject* exported_object_; // weak; owned by the Bus object.
+ // This is a map from interface name -> property name -> pointer to property.
+ std::map<std::string,
+ std::map<std::string, ExportedPropertyBase*>> properties_;
+
+ // D-Bus callbacks may last longer the property set exporting those methods.
+ base::WeakPtrFactory<ExportedPropertySet> weak_ptr_factory_;
+
+ friend class ExportedPropertySetTest;
+ DISALLOW_COPY_AND_ASSIGN(ExportedPropertySet);
+};
+
+template <typename T>
+class ExportedProperty : public ExportedPropertyBase {
+ public:
+ ExportedProperty();
+ virtual ~ExportedProperty() override;
+
+ // Retrieves the current value.
+ const T& value() const;
+
+ // Set the value exposed to remote applications. This triggers notifications
+ // of changes over the Properties interface.
+ void SetValue(const T& new_value);
+
+ // Called by ExportedPropertySet. This update callback triggers
+ // ExportedPropertySet to send a signal from the properties interface of the
+ // exported object.
+ virtual void SetUpdateCallback(const OnUpdateCallback& cb) override;
+
+ // Implementation provided by specialization.
+ virtual void AppendValueToWriter(dbus::MessageWriter* writer) const override;
+
+ private:
+ OnUpdateCallback on_update_;
+ T value_{}; // NOLINT - initializer list
+
+ DISALLOW_COPY_AND_ASSIGN(ExportedProperty);
+};
+
+extern template class ExportedProperty<bool>;
+extern template class ExportedProperty<uint8>;
+extern template class ExportedProperty<int16>;
+extern template class ExportedProperty<uint16>;
+extern template class ExportedProperty<int32>;
+extern template class ExportedProperty<uint32>;
+extern template class ExportedProperty<int64>;
+extern template class ExportedProperty<uint64>;
+extern template class ExportedProperty<double>;
+extern template class ExportedProperty<std::string>;
+extern template class ExportedProperty<dbus::ObjectPath>;
+extern template class ExportedProperty<std::vector<std::string>>;
+extern template class ExportedProperty<std::vector<dbus::ObjectPath>>;
+extern template class ExportedProperty<std::vector<uint8>>;
+
+} // namespace dbus_utils
+
+} // namespace buffet
+
+#endif // BUFFET_EXPORTED_PROPERTY_SET_H_
diff --git a/buffet/exported_property_set_unittest.cc b/buffet/exported_property_set_unittest.cc
new file mode 100644
index 0000000..ce8a3fe
--- /dev/null
+++ b/buffet/exported_property_set_unittest.cc
@@ -0,0 +1,506 @@
+// Copyright 2014 The Chromium OS 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 "buffet/exported_property_set.h"
+
+#include <string>
+#include <vector>
+
+#include <base/basictypes.h>
+#include <base/bind.h>
+#include <dbus/message.h>
+#include <dbus/property.h>
+#include <dbus/object_path.h>
+#include <dbus/mock_bus.h>
+#include <dbus/mock_exported_object.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using ::testing::AnyNumber;
+using ::testing::Return;
+using ::testing::Invoke;
+using ::testing::_;
+
+namespace buffet {
+
+namespace dbus_utils {
+
+namespace {
+
+const char kBoolPropName[] = "BoolProp";
+const char kUint8PropName[] = "Uint8Prop";
+const char kInt16PropName[] = "Int16Prop";
+const char kUint16PropName[] = "Uint16Prop";
+const char kInt32PropName[] = "Int32Prop";
+const char kUint32PropName[] = "Uint32Prop";
+const char kInt64PropName[] = "Int64Prop";
+const char kUint64PropName[] = "Uint64Prop";
+const char kDoublePropName[] = "DoubleProp";
+const char kStringPropName[] = "StringProp";
+const char kPathPropName[] = "PathProp";
+const char kStringListPropName[] = "StringListProp";
+const char kPathListPropName[] = "PathListProp";
+const char kUint8ListPropName[] = "Uint8ListProp";
+
+const char kTestInterface1[] = "org.chromium.TestInterface1";
+const char kTestInterface2[] = "org.chromium.TestInterface2";
+const char kTestInterface3[] = "org.chromium.TestInterface3";
+
+const std::string kTestString("lies");
+const dbus::ObjectPath kMethodsExportedOnPath(std::string("/export"));
+const dbus::ObjectPath kTestObjectPathInit(std::string("/path_init"));
+const dbus::ObjectPath kTestObjectPathUpdate(std::string("/path_update"));
+
+} // namespace
+
+class ExportedPropertySetTest : public ::testing::Test {
+ public:
+ struct Properties : public ExportedPropertySet {
+ public:
+ ExportedProperty<bool> bool_prop_;
+ ExportedProperty<uint8> uint8_prop_;
+ ExportedProperty<int16> int16_prop_;
+ ExportedProperty<uint16> uint16_prop_;
+ ExportedProperty<int32> int32_prop_;
+ ExportedProperty<uint32> uint32_prop_;
+ ExportedProperty<int64> int64_prop_;
+ ExportedProperty<uint64> uint64_prop_;
+ ExportedProperty<double> double_prop_;
+ ExportedProperty<std::string> string_prop_;
+ ExportedProperty<dbus::ObjectPath> path_prop_;
+ ExportedProperty<std::vector<std::string>> stringlist_prop_;
+ ExportedProperty<std::vector<dbus::ObjectPath>> pathlist_prop_;
+ ExportedProperty<std::vector<uint8>> uint8list_prop_;
+
+ Properties(dbus::Bus* bus, const dbus::ObjectPath& path)
+ : ExportedPropertySet(bus, path) {
+ // The empty string is not a valid value for an ObjectPath.
+ path_prop_.SetValue(kTestObjectPathInit);
+ RegisterProperty(kTestInterface1, kBoolPropName, &bool_prop_);
+ RegisterProperty(kTestInterface1, kUint8PropName, &uint8_prop_);
+ RegisterProperty(kTestInterface1, kInt16PropName, &int16_prop_);
+ // I chose this weird grouping because N=2 is about all the permutations
+ // of GetAll that I want to anticipate.
+ RegisterProperty(kTestInterface2, kUint16PropName, &uint16_prop_);
+ RegisterProperty(kTestInterface2, kInt32PropName, &int32_prop_);
+ RegisterProperty(kTestInterface3, kUint32PropName, &uint32_prop_);
+ RegisterProperty(kTestInterface3, kInt64PropName, &int64_prop_);
+ RegisterProperty(kTestInterface3, kUint64PropName, &uint64_prop_);
+ RegisterProperty(kTestInterface3, kDoublePropName, &double_prop_);
+ RegisterProperty(kTestInterface3, kStringPropName, &string_prop_);
+ RegisterProperty(kTestInterface3, kPathPropName, &path_prop_);
+ RegisterProperty(kTestInterface3, kStringListPropName, &stringlist_prop_);
+ RegisterProperty(kTestInterface3, kPathListPropName, &pathlist_prop_);
+ RegisterProperty(kTestInterface3, kUint8ListPropName, &uint8list_prop_);
+ }
+ virtual ~Properties() {}
+
+ void CallHandleGetAll(
+ dbus::MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender) {
+ HandleGetAll(method_call, response_sender);
+ }
+
+ void CallHandleGet(dbus::MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender) {
+ HandleGet(method_call, response_sender);
+ }
+
+ void CallHandleSet(dbus::MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender) {
+ HandleSet(method_call, response_sender);
+ }
+ };
+
+ virtual void SetUp() {
+ dbus::Bus::Options options;
+ options.bus_type = dbus::Bus::SYSTEM;
+ bus_ = new dbus::MockBus(options);
+ // By default, don't worry about threading assertions.
+ EXPECT_CALL(*bus_, AssertOnOriginThread()).Times(AnyNumber());
+ EXPECT_CALL(*bus_, AssertOnDBusThread()).Times(AnyNumber());
+ // Use a mock exported object.
+ mock_exported_object_ = new dbus::MockExportedObject(
+ bus_.get(), kMethodsExportedOnPath);
+ EXPECT_CALL(*bus_, GetExportedObject(kMethodsExportedOnPath))
+ .Times(1).WillOnce(Return(mock_exported_object_.get()));
+ p_.reset(new Properties(bus_.get(), kMethodsExportedOnPath));
+ }
+
+ void StoreResponse(scoped_ptr<dbus::Response> method_response) {
+ last_response_.reset(method_response.release());
+ }
+
+ void AssertGetAllReturnsError(dbus::MethodCall* method_call) {
+ auto response_sender = base::Bind(&ExportedPropertySetTest::StoreResponse,
+ base::Unretained(this));
+ method_call->SetSerial(123);
+ p_->CallHandleGetAll(method_call, response_sender);
+ ASSERT_NE(dynamic_cast<dbus::ErrorResponse*>(last_response_.get()),
+ nullptr);
+ }
+
+ void AssertGetReturnsError(dbus::MethodCall* method_call) {
+ auto response_sender = base::Bind(&ExportedPropertySetTest::StoreResponse,
+ base::Unretained(this));
+ method_call->SetSerial(123);
+ p_->CallHandleGet(method_call, response_sender);
+ ASSERT_NE(dynamic_cast<dbus::ErrorResponse*>(last_response_.get()),
+ nullptr);
+ }
+
+ scoped_ptr<dbus::Response> GetPropertyOnInterface(
+ const std::string& interface_name, const std::string& property_name) {
+ dbus::MethodCall method_call(dbus::kPropertiesInterface,
+ dbus::kPropertiesGet);
+ method_call.SetSerial(123);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString(interface_name);
+ writer.AppendString(property_name);
+ auto response_sender = base::Bind(&ExportedPropertySetTest::StoreResponse,
+ base::Unretained(this));
+ p_->CallHandleGet(&method_call, response_sender);
+ return last_response_.Pass();
+ }
+
+ scoped_ptr<dbus::Response> last_response_;
+ scoped_refptr<dbus::MockBus> bus_;
+ scoped_refptr<dbus::MockExportedObject> mock_exported_object_;
+ scoped_ptr<Properties> p_;
+};
+
+TEST_F(ExportedPropertySetTest, UpdateNotifications) {
+ EXPECT_CALL(*mock_exported_object_, SendSignal(_)).Times(14);
+ p_->bool_prop_.SetValue(true);
+ p_->uint8_prop_.SetValue(1);
+ p_->int16_prop_.SetValue(1);
+ p_->uint16_prop_.SetValue(1);
+ p_->int32_prop_.SetValue(1);
+ p_->uint32_prop_.SetValue(1);
+ p_->int64_prop_.SetValue(1);
+ p_->uint64_prop_.SetValue(1);
+ p_->double_prop_.SetValue(1.0);
+ p_->string_prop_.SetValue(kTestString);
+ p_->path_prop_.SetValue(kTestObjectPathUpdate);
+ p_->stringlist_prop_.SetValue({kTestString});
+ p_->pathlist_prop_.SetValue({kTestObjectPathUpdate});
+ p_->uint8list_prop_.SetValue({1});
+}
+
+TEST_F(ExportedPropertySetTest, UpdateToSameValue) {
+ EXPECT_CALL(*mock_exported_object_, SendSignal(_)).Times(1);
+ p_->bool_prop_.SetValue(true);
+ p_->bool_prop_.SetValue(true);
+}
+
+TEST_F(ExportedPropertySetTest, GetAllNoArgs) {
+ dbus::MethodCall method_call(dbus::kPropertiesInterface,
+ dbus::kPropertiesGetAll);
+ AssertGetAllReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetAllInvalidInterface) {
+ dbus::MethodCall method_call(
+ dbus::kPropertiesInterface, dbus::kPropertiesGetAll);
+ method_call.SetSerial(123);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString("org.chromium.BadInterface");
+ auto response_sender = base::Bind(&ExportedPropertySetTest::StoreResponse,
+ base::Unretained(this));
+ p_->CallHandleGetAll(&method_call, response_sender);
+ dbus::MessageReader response_reader(last_response_.get());
+ dbus::MessageReader dict_reader(nullptr);
+ ASSERT_TRUE(response_reader.PopArray(&dict_reader));
+ // The response should just be a an empty array, since there are no properties
+ // on this interface. The spec doesn't say much about error conditions here,
+ // so I'm going to assume this is a valid implementation.
+ ASSERT_FALSE(dict_reader.HasMoreData());
+ ASSERT_FALSE(response_reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetAllExtraArgs) {
+ dbus::MethodCall method_call(dbus::kPropertiesInterface,
+ dbus::kPropertiesGetAll);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString(kTestInterface1);
+ writer.AppendString(kTestInterface1);
+ AssertGetAllReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetAllCorrectness) {
+ dbus::MethodCall method_call(
+ dbus::kPropertiesInterface, dbus::kPropertiesGetAll);
+ method_call.SetSerial(123);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString(kTestInterface2);
+ auto response_sender = base::Bind(&ExportedPropertySetTest::StoreResponse,
+ base::Unretained(this));
+ p_->CallHandleGetAll(&method_call, response_sender);
+ dbus::MessageReader response_reader(last_response_.get());
+ dbus::MessageReader dict_reader(nullptr);
+ dbus::MessageReader entry_reader(nullptr);
+ ASSERT_TRUE(response_reader.PopArray(&dict_reader));
+ ASSERT_TRUE(dict_reader.PopDictEntry(&entry_reader));
+ std::string property_name;
+ ASSERT_TRUE(entry_reader.PopString(&property_name));
+ uint16 value16;
+ int32 value32;
+ if (property_name.compare(kUint16PropName) == 0) {
+ ASSERT_TRUE(entry_reader.PopVariantOfUint16(&value16));
+ ASSERT_FALSE(entry_reader.HasMoreData());
+ ASSERT_TRUE(dict_reader.PopDictEntry(&entry_reader));
+ ASSERT_TRUE(entry_reader.PopString(&property_name));
+ ASSERT_EQ(property_name.compare(kInt32PropName), 0);
+ ASSERT_TRUE(entry_reader.PopVariantOfInt32(&value32));
+ } else {
+ ASSERT_EQ(property_name.compare(kInt32PropName), 0);
+ ASSERT_TRUE(entry_reader.PopVariantOfInt32(&value32));
+ ASSERT_FALSE(entry_reader.HasMoreData());
+ ASSERT_TRUE(dict_reader.PopDictEntry(&entry_reader));
+ ASSERT_TRUE(entry_reader.PopString(&property_name));
+ ASSERT_EQ(property_name.compare(kUint16PropName), 0);
+ ASSERT_TRUE(entry_reader.PopVariantOfUint16(&value16));
+ }
+ ASSERT_FALSE(entry_reader.HasMoreData());
+ ASSERT_FALSE(dict_reader.HasMoreData());
+ ASSERT_FALSE(response_reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetNoArgs) {
+ dbus::MethodCall method_call(dbus::kPropertiesInterface,
+ dbus::kPropertiesGet);
+ AssertGetReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetInvalidInterface) {
+ dbus::MethodCall method_call(dbus::kPropertiesInterface,
+ dbus::kPropertiesGet);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString("org.chromium.BadInterface");
+ writer.AppendString(kInt16PropName);
+ AssertGetReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetBadPropertyName) {
+ dbus::MethodCall method_call(dbus::kPropertiesInterface,
+ dbus::kPropertiesGet);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString(kTestInterface1);
+ writer.AppendString("IAmNotAProperty");
+ AssertGetReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetPropIfMismatch) {
+ dbus::MethodCall method_call(dbus::kPropertiesInterface,
+ dbus::kPropertiesGet);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString(kTestInterface1);
+ writer.AppendString(kStringPropName);
+ AssertGetReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetNoPropertyName) {
+ dbus::MethodCall method_call(dbus::kPropertiesInterface,
+ dbus::kPropertiesGet);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString(kTestInterface1);
+ AssertGetReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetExtraArgs) {
+ dbus::MethodCall method_call(dbus::kPropertiesInterface,
+ dbus::kPropertiesGet);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString(kTestInterface1);
+ writer.AppendString(kBoolPropName);
+ writer.AppendString("Extra param");
+ AssertGetReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithBool) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface1, kBoolPropName);
+ dbus::MessageReader reader(response.get());
+ bool value;
+ ASSERT_TRUE(reader.PopVariantOfBool(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithUint8) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface1, kUint8PropName);
+ dbus::MessageReader reader(response.get());
+ uint8 value;
+ ASSERT_TRUE(reader.PopVariantOfByte(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithInt16) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface1, kInt16PropName);
+ dbus::MessageReader reader(response.get());
+ int16 value;
+ ASSERT_TRUE(reader.PopVariantOfInt16(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithUint16) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface2, kUint16PropName);
+ dbus::MessageReader reader(response.get());
+ uint16 value;
+ ASSERT_TRUE(reader.PopVariantOfUint16(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithInt32) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface2, kInt32PropName);
+ dbus::MessageReader reader(response.get());
+ int32 value;
+ ASSERT_TRUE(reader.PopVariantOfInt32(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithUint32) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface3, kUint32PropName);
+ dbus::MessageReader reader(response.get());
+ uint32 value;
+ ASSERT_TRUE(reader.PopVariantOfUint32(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithInt64) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface3, kInt64PropName);
+ dbus::MessageReader reader(response.get());
+ int64 value;
+ ASSERT_TRUE(reader.PopVariantOfInt64(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithUint64) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface3, kUint64PropName);
+ dbus::MessageReader reader(response.get());
+ uint64 value;
+ ASSERT_TRUE(reader.PopVariantOfUint64(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithDouble) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface3, kDoublePropName);
+ dbus::MessageReader reader(response.get());
+ double value;
+ ASSERT_TRUE(reader.PopVariantOfDouble(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithString) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface3, kStringPropName);
+ dbus::MessageReader reader(response.get());
+ std::string value;
+ ASSERT_TRUE(reader.PopVariantOfString(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithPath) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface3, kPathPropName);
+ dbus::MessageReader reader(response.get());
+ dbus::ObjectPath value;
+ ASSERT_TRUE(reader.PopVariantOfObjectPath(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithStringList) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface3, kStringListPropName);
+ dbus::MessageReader reader(response.get());
+ dbus::MessageReader variant_reader(nullptr);
+ std::vector<std::string> value;
+ ASSERT_TRUE(reader.PopVariant(&variant_reader));
+ ASSERT_TRUE(variant_reader.PopArrayOfStrings(&value));
+ ASSERT_FALSE(variant_reader.HasMoreData());
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithPathList) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface3, kPathListPropName);
+ dbus::MessageReader reader(response.get());
+ dbus::MessageReader variant_reader(nullptr);
+ std::vector<dbus::ObjectPath> value;
+ ASSERT_TRUE(reader.PopVariant(&variant_reader));
+ ASSERT_TRUE(variant_reader.PopArrayOfObjectPaths(&value));
+ ASSERT_FALSE(variant_reader.HasMoreData());
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithUint8List) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface3, kPathListPropName);
+ dbus::MessageReader reader(response.get());
+ dbus::MessageReader variant_reader(nullptr);
+ const uint8* buffer;
+ size_t buffer_len;
+ ASSERT_TRUE(reader.PopVariant(&variant_reader));
+ // |buffer| remains under the control of the MessageReader.
+ ASSERT_TRUE(variant_reader.PopArrayOfBytes(&buffer, &buffer_len));
+ ASSERT_FALSE(variant_reader.HasMoreData());
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, SetFailsGracefully) {
+ dbus::MethodCall method_call(dbus::kPropertiesInterface,
+ dbus::kPropertiesSet);
+ method_call.SetSerial(123);
+ auto response_sender = base::Bind(&ExportedPropertySetTest::StoreResponse,
+ base::Unretained(this));
+ p_->CallHandleSet(&method_call, response_sender);
+ ASSERT_TRUE(
+ dynamic_cast<dbus::ErrorResponse*>(last_response_.get()) != nullptr);
+}
+
+namespace {
+
+void VerifySignal(dbus::Signal* signal) {
+ ASSERT_NE(signal, nullptr);
+ std::string interface_name;
+ std::string property_name;
+ uint8 value;
+ dbus::MessageReader reader(signal);
+ dbus::MessageReader array_reader(signal);
+ dbus::MessageReader dict_reader(signal);
+ ASSERT_TRUE(reader.PopString(&interface_name));
+ ASSERT_TRUE(reader.PopArray(&array_reader));
+ ASSERT_TRUE(array_reader.PopDictEntry(&dict_reader));
+ ASSERT_TRUE(dict_reader.PopString(&property_name));
+ ASSERT_TRUE(dict_reader.PopVariantOfByte(&value));
+ ASSERT_FALSE(dict_reader.HasMoreData());
+ ASSERT_FALSE(array_reader.HasMoreData());
+ ASSERT_TRUE(reader.HasMoreData());
+ // Read the (empty) list of invalidated property names.
+ ASSERT_TRUE(reader.PopArray(&array_reader));
+ ASSERT_FALSE(array_reader.HasMoreData());
+ ASSERT_FALSE(reader.HasMoreData());
+ ASSERT_EQ(value, 57);
+ ASSERT_EQ(property_name, std::string(kUint8PropName));
+ ASSERT_EQ(interface_name, std::string(kTestInterface1));
+}
+
+} // namespace
+
+TEST_F(ExportedPropertySetTest, SignalsAreParsable) {
+ EXPECT_CALL(*mock_exported_object_, SendSignal(_))
+ .Times(1).WillOnce(Invoke(&VerifySignal));
+ p_->uint8_prop_.SetValue(57);
+}
+
+} // namespace dbus_utils
+
+} // namespace buffet
diff --git a/buffet/http_connection.h b/buffet/http_connection.h
new file mode 100644
index 0000000..ab67905
--- /dev/null
+++ b/buffet/http_connection.h
@@ -0,0 +1,84 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_HTTP_CONNECTION_H_
+#define BUFFET_HTTP_CONNECTION_H_
+
+#include <string>
+#include <vector>
+
+#include <base/basictypes.h>
+
+#include "buffet/error.h"
+#include "buffet/http_transport.h"
+
+namespace buffet {
+namespace http {
+
+///////////////////////////////////////////////////////////////////////////////
+// Connection class is the base class for HTTP communication session.
+// It abstracts the implementation of underlying transport library (ex libcurl).
+// When the Connection-derived class is constructed, it is pre-set up with
+// basic initialization information necessary to initiate the server request
+// connection (such as the URL, request method, etc - see
+// Transport::CreateConnection() for more details). But most implementations
+// would not probably initiate the physical connection until SendHeaders
+// is called.
+// You normally shouldn't worry about using this class directly.
+// http::Request and http::Response classes use it for communication.
+///////////////////////////////////////////////////////////////////////////////
+class Connection {
+ public:
+ explicit Connection(std::shared_ptr<Transport> transport)
+ : transport_(transport) {}
+ virtual ~Connection() = default;
+
+ // Called by http::Request to initiate the connection with the server.
+ // This normally opens the socket and sends the request headers.
+ virtual bool SendHeaders(const HeaderList& headers, ErrorPtr* error) = 0;
+ // If needed, this function can be called to send the request body data.
+ // This function can be called repeatedly until all data is sent.
+ virtual bool WriteRequestData(const void* data, size_t size,
+ ErrorPtr* error) = 0;
+ // This function is called when all the data is sent off and it's time
+ // to receive the response data.
+ virtual bool FinishRequest(ErrorPtr* error) = 0;
+
+ // Returns the HTTP status code (e.g. 200 for success).
+ virtual int GetResponseStatusCode() const = 0;
+ // Returns the status text (e.g. for error 403 it could be "NOT AUTHORIZED").
+ virtual std::string GetResponseStatusText() const = 0;
+ // Returns the HTTP protocol version (e.g. "HTTP/1.1").
+ virtual std::string GetProtocolVersion() const = 0;
+ // Returns the value of particular response header, or empty string if the
+ // headers wasn't received.
+ virtual std::string GetResponseHeader(
+ const std::string& header_name) const = 0;
+ // Returns the response data size, if known. For chunked (streaming)
+ // transmission this might not be known until all the data is sent.
+ // In this case GetResponseDataSize() will return 0.
+ virtual uint64_t GetResponseDataSize() const = 0;
+ // This function is called to read a block of response data.
+ // It needs to be called repeatedly until it returns false or |size_read| is
+ // set to 0. |data| is the destination buffer to read the data into.
+ // |buffer_size| is the size of the buffer (amount of data to read).
+ // |read_size| is the amount of data actually read, which could be less than
+ // the size requested or 0 if there is no more data available.
+ virtual bool ReadResponseData(void* data, size_t buffer_size,
+ size_t* size_read, ErrorPtr* error) = 0;
+
+ protected:
+ // |transport_| is mainly used to keep the object alive as long as the
+ // connection exists. But some implementations of Connection could use
+ // the Transport-derived class for their own needs as well.
+ std::shared_ptr<Transport> transport_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Connection);
+};
+
+} // namespace http
+} // namespace buffet
+
+#endif // BUFFET_HTTP_CONNECTION_H_
diff --git a/buffet/http_connection_curl.cc b/buffet/http_connection_curl.cc
new file mode 100644
index 0000000..0dae74c
--- /dev/null
+++ b/buffet/http_connection_curl.cc
@@ -0,0 +1,226 @@
+// Copyright 2014 The Chromium OS 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 "buffet/http_connection_curl.h"
+
+#include <base/logging.h>
+
+#include "buffet/http_request.h"
+#include "buffet/http_transport_curl.h"
+#include "buffet/string_utils.h"
+
+namespace buffet {
+namespace http {
+namespace curl {
+
+static int curl_trace(CURL *handle, curl_infotype type,
+ char *data, size_t size, void *userp) {
+ std::string msg(data, size);
+
+ switch (type) {
+ case CURLINFO_TEXT:
+ VLOG(3) << "== Info: " << msg;
+ break;
+ case CURLINFO_HEADER_OUT:
+ VLOG(3) << "=> Send headers:\n" << msg;
+ break;
+ case CURLINFO_DATA_OUT:
+ VLOG(3) << "=> Send data:\n" << msg;
+ break;
+ case CURLINFO_SSL_DATA_OUT:
+ VLOG(3) << "=> Send SSL data" << msg;
+ break;
+ case CURLINFO_HEADER_IN:
+ VLOG(3) << "<= Recv header: " << msg;
+ break;
+ case CURLINFO_DATA_IN:
+ VLOG(3) << "<= Recv data:\n" << msg;
+ break;
+ case CURLINFO_SSL_DATA_IN:
+ VLOG(3) << "<= Recv SSL data" << msg;
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+Connection::Connection(CURL* curl_handle, const std::string& method,
+ std::shared_ptr<http::Transport> transport) :
+ http::Connection(transport), method_(method), curl_handle_(curl_handle) {
+ VLOG(1) << "curl::Connection created: " << method_;
+}
+
+Connection::~Connection() {
+ VLOG(1) << "curl::Connection destroyed";
+}
+
+bool Connection::SendHeaders(const HeaderList& headers, ErrorPtr* error) {
+ headers_.insert(headers.begin(), headers.end());
+ return true;
+}
+
+bool Connection::WriteRequestData(const void* data, size_t size,
+ ErrorPtr* error) {
+ if (size > 0) {
+ auto data_ptr = reinterpret_cast<const unsigned char*>(data);
+ request_data_.insert(request_data_.end(), data_ptr, data_ptr + size);
+ }
+ return true;
+}
+
+bool Connection::FinishRequest(ErrorPtr* error) {
+ if (VLOG_IS_ON(3)) {
+ curl_easy_setopt(curl_handle_, CURLOPT_DEBUGFUNCTION, curl_trace);
+ curl_easy_setopt(curl_handle_, CURLOPT_VERBOSE, 1L);
+ }
+
+ // Set up HTTP request data.
+ if (method_ == request_type::kPut) {
+ curl_easy_setopt(curl_handle_, CURLOPT_INFILESIZE_LARGE,
+ curl_off_t(request_data_.size()));
+ } else {
+ curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDSIZE_LARGE,
+ curl_off_t(request_data_.size()));
+ }
+ if (!request_data_.empty()) {
+ curl_easy_setopt(curl_handle_,
+ CURLOPT_READFUNCTION, &Connection::read_callback);
+ curl_easy_setopt(curl_handle_, CURLOPT_READDATA, this);
+ VLOG(2) << "Raw request data: "
+ << std::string(reinterpret_cast<const char*>(request_data_.data()),
+ request_data_.size());
+ }
+
+ curl_slist* header_list = nullptr;
+ if (!headers_.empty()) {
+ for (auto pair : headers_) {
+ std::string header = string_utils::Join(": ", pair.first, pair.second);
+ VLOG(2) << "Request header: " << header;
+ header_list = curl_slist_append(header_list, header.c_str());
+ }
+ curl_easy_setopt(curl_handle_, CURLOPT_HTTPHEADER, header_list);
+ }
+
+ headers_.clear();
+
+ // Set up HTTP response data.
+ if (method_ != request_type::kHead) {
+ curl_easy_setopt(curl_handle_,
+ CURLOPT_WRITEFUNCTION, &Connection::write_callback);
+ curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, this);
+ }
+
+ // HTTP response headers
+ curl_easy_setopt(curl_handle_,
+ CURLOPT_HEADERFUNCTION, &Connection::header_callback);
+ curl_easy_setopt(curl_handle_, CURLOPT_HEADERDATA, this);
+
+ CURLcode ret = curl_easy_perform(curl_handle_);
+ if (header_list)
+ curl_slist_free_all(header_list);
+ if (ret != CURLE_OK) {
+ Error::AddTo(error, http::curl::kErrorDomain, std::to_string(ret),
+ curl_easy_strerror(ret));
+ } else {
+ LOG(INFO) << "Response: " << GetResponseStatusCode() << " ("
+ << GetResponseStatusText() << ")";
+ VLOG(2) << "Response data (" << response_data_.size() << "): "
+ << std::string(reinterpret_cast<const char*>(response_data_.data()),
+ response_data_.size());
+ }
+ return (ret == CURLE_OK);
+}
+
+int Connection::GetResponseStatusCode() const {
+ long status_code = 0; // NOLINT(runtime/int) - curl expects a long here.
+ curl_easy_getinfo(curl_handle_, CURLINFO_RESPONSE_CODE, &status_code);
+ return status_code;
+}
+
+std::string Connection::GetResponseStatusText() const {
+ return status_text_;
+}
+
+std::string Connection::GetProtocolVersion() const {
+ return protocol_version_;
+}
+
+std::string Connection::GetResponseHeader(
+ const std::string& header_name) const {
+ auto p = headers_.find(header_name);
+ return p != headers_.end() ? p->second : std::string();
+}
+
+uint64_t Connection::GetResponseDataSize() const {
+ return response_data_.size();
+}
+
+bool Connection::ReadResponseData(void* data, size_t buffer_size,
+ size_t* size_read, ErrorPtr* error) {
+ size_t size_to_read = response_data_.size() - response_data_ptr_;
+ if (size_to_read > buffer_size)
+ size_to_read = buffer_size;
+ memcpy(data, response_data_.data() + response_data_ptr_, size_to_read);
+ if (size_read)
+ *size_read = size_to_read;
+ response_data_ptr_ += size_to_read;
+ return true;
+}
+
+size_t Connection::write_callback(char* ptr, size_t size,
+ size_t num, void* data) {
+ Connection* me = reinterpret_cast<Connection*>(data);
+ size_t data_len = size * num;
+ me->response_data_.insert(me->response_data_.end(), ptr, ptr + data_len);
+ return data_len;
+}
+
+size_t Connection::read_callback(char* ptr, size_t size,
+ size_t num, void* data) {
+ Connection* me = reinterpret_cast<Connection*>(data);
+ size_t data_len = size * num;
+
+ if (me->request_data_ptr_ >= me->request_data_.size())
+ return 0;
+
+ if (me->request_data_ptr_ + data_len > me->request_data_.size())
+ data_len = me->request_data_.size() - me->request_data_ptr_;
+
+ memcpy(ptr, me->request_data_.data() + me->request_data_ptr_, data_len);
+ me->request_data_ptr_ += data_len;
+
+ return data_len;
+}
+
+size_t Connection::header_callback(char* ptr, size_t size,
+ size_t num, void* data) {
+ Connection* me = reinterpret_cast<Connection*>(data);
+ size_t hdr_len = size * num;
+ std::string header(ptr, hdr_len);
+ // Remove newlines at the end of header line.
+ while (!header.empty() && (header.back() == '\r' || header.back() == '\n')) {
+ header.pop_back();
+ }
+
+ VLOG(2) << "Response header: " << header;
+
+ if (!me->status_text_set_) {
+ // First header - response code as "HTTP/1.1 200 OK".
+ // Need to extract the OK part
+ auto pair = string_utils::SplitAtFirst(header, ' ');
+ me->protocol_version_ = pair.first;
+ me->status_text_ = string_utils::SplitAtFirst(pair.second, ' ').second;
+ me->status_text_set_ = true;
+ } else {
+ auto pair = string_utils::SplitAtFirst(header, ':');
+ if (!pair.second.empty())
+ me->headers_.insert(pair);
+ }
+ return hdr_len;
+}
+
+} // namespace curl
+} // namespace http
+} // namespace buffet
diff --git a/buffet/http_connection_curl.h b/buffet/http_connection_curl.h
new file mode 100644
index 0000000..9a82e26
--- /dev/null
+++ b/buffet/http_connection_curl.h
@@ -0,0 +1,86 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_HTTP_CONNECTION_CURL_H_
+#define BUFFET_HTTP_CONNECTION_CURL_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/basictypes.h>
+#include <curl/curl.h>
+
+#include "buffet/http_connection.h"
+
+namespace buffet {
+namespace http {
+namespace curl {
+
+// This is a libcurl-based implementation of http::Connection.
+class Connection : public http::Connection {
+ public:
+ Connection(CURL* curl_handle, const std::string& method,
+ std::shared_ptr<http::Transport> transport);
+ virtual ~Connection();
+
+ // Overrides from http::Connection.
+ // See http_connection.h for description of these methods.
+ virtual bool SendHeaders(const HeaderList& headers, ErrorPtr* error) override;
+ virtual bool WriteRequestData(const void* data, size_t size,
+ ErrorPtr* error) override;
+ virtual bool FinishRequest(ErrorPtr* error) override;
+
+ virtual int GetResponseStatusCode() const override;
+ virtual std::string GetResponseStatusText() const override;
+ virtual std::string GetProtocolVersion() const override;
+ virtual std::string GetResponseHeader(
+ const std::string& header_name) const override;
+ virtual uint64_t GetResponseDataSize() const override;
+ virtual bool ReadResponseData(void* data, size_t buffer_size,
+ size_t* size_read, ErrorPtr* error) override;
+
+ protected:
+ // Write data callback. Used by CURL when receiving response data.
+ static size_t write_callback(char* ptr, size_t size, size_t num, void* data);
+ // Read data callback. Used by CURL when sending request body data.
+ static size_t read_callback(char* ptr, size_t size, size_t num, void* data);
+ // Write header data callback. Used by CURL when receiving response headers.
+ static size_t header_callback(char* ptr, size_t size, size_t num, void* data);
+
+ // HTTP request verb, such as "GET", "POST", "PUT", ...
+ std::string method_;
+
+ // Binary data for request body.
+ std::vector<unsigned char> request_data_;
+ // Read pointer for request data. Used when streaming data to the server.
+ size_t request_data_ptr_ = 0;
+
+ // Received response data.
+ std::vector<unsigned char> response_data_;
+ size_t response_data_ptr_ = 0;
+
+ // List of optional request headers provided by the caller.
+ // After request has been sent, contains the received response headers.
+ std::map<std::string, std::string> headers_;
+
+ // HTTP protocol version, such as HTTP/1.1
+ std::string protocol_version_;
+ // Response status text, such as "OK" for 200, or "Forbidden" for 403
+ std::string status_text_;
+ // Flag used when parsing response headers to separate the response status
+ // from the rest of response headers.
+ bool status_text_set_ = false;
+
+ CURL* curl_handle_ = nullptr;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Connection);
+};
+
+} // namespace curl
+} // namespace http
+} // namespace buffet
+
+#endif // BUFFET_HTTP_CONNECTION_CURL_H_
diff --git a/buffet/http_connection_fake.cc b/buffet/http_connection_fake.cc
new file mode 100644
index 0000000..87fbf23
--- /dev/null
+++ b/buffet/http_connection_fake.cc
@@ -0,0 +1,94 @@
+// Copyright 2014 The Chromium OS 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 "buffet/http_connection_fake.h"
+
+#include <base/logging.h>
+
+#include "buffet/http_request.h"
+#include "buffet/mime_utils.h"
+#include "buffet/string_utils.h"
+
+namespace buffet {
+namespace http {
+namespace fake {
+
+Connection::Connection(const std::string& url, const std::string& method,
+ std::shared_ptr<http::Transport> transport) :
+ http::Connection(transport), request_(url, method) {
+ VLOG(1) << "fake::Connection created: " << method;
+}
+
+Connection::~Connection() {
+ VLOG(1) << "fake::Connection destroyed";
+}
+
+bool Connection::SendHeaders(const HeaderList& headers, ErrorPtr* error) {
+ request_.AddHeaders(headers);
+ return true;
+}
+
+bool Connection::WriteRequestData(const void* data, size_t size,
+ ErrorPtr* error) {
+ request_.AddData(data, size);
+ return true;
+}
+
+bool Connection::FinishRequest(ErrorPtr* error) {
+ request_.AddHeaders({{request_header::kContentLength,
+ std::to_string(request_.GetData().size())}});
+ fake::Transport* transport = static_cast<fake::Transport*>(transport_.get());
+ CHECK(transport) << "Expecting a fake transport";
+ auto handler = transport->GetHandler(request_.GetURL(), request_.GetMethod());
+ if (handler.is_null()) {
+ LOG(ERROR) << "Received unexpected " << request_.GetMethod()
+ << " request at " << request_.GetURL();
+ response_.ReplyText(status_code::NotFound,
+ "<html><body>Not found</body></html>",
+ mime::text::kHtml);
+ } else {
+ handler.Run(request_, &response_);
+ }
+ return true;
+}
+
+int Connection::GetResponseStatusCode() const {
+ return response_.GetStatusCode();
+}
+
+std::string Connection::GetResponseStatusText() const {
+ return response_.GetStatusText();
+}
+
+std::string Connection::GetProtocolVersion() const {
+ return response_.GetProtocolVersion();
+}
+
+std::string Connection::GetResponseHeader(
+ const std::string& header_name) const {
+ return response_.GetHeader(header_name);
+}
+
+uint64_t Connection::GetResponseDataSize() const {
+ // HEAD requests must not return body.
+ return (request_.GetMethod() != request_type::kHead) ?
+ response_.GetData().size() : 0;
+}
+
+bool Connection::ReadResponseData(void* data, size_t buffer_size,
+ size_t* size_read, ErrorPtr* error) {
+ size_t size_to_read = GetResponseDataSize() - response_data_ptr_;
+ if (size_to_read > buffer_size)
+ size_to_read = buffer_size;
+ if (size_to_read > 0)
+ memcpy(data, response_.GetData().data() + response_data_ptr_, size_to_read);
+ if (size_read)
+ *size_read = size_to_read;
+ response_data_ptr_ += size_to_read;
+ return true;
+}
+
+} // namespace fake
+} // namespace http
+} // namespace buffet
diff --git a/buffet/http_connection_fake.h b/buffet/http_connection_fake.h
new file mode 100644
index 0000000..57bf016
--- /dev/null
+++ b/buffet/http_connection_fake.h
@@ -0,0 +1,62 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_HTTP_CONNECTION_FAKE_H_
+#define BUFFET_HTTP_CONNECTION_FAKE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/basictypes.h>
+
+#include "buffet/http_connection.h"
+#include "buffet/http_transport_fake.h"
+
+namespace buffet {
+namespace http {
+namespace fake {
+
+// This is a fake implementation of http::Connection for unit testing.
+class Connection : public http::Connection {
+ public:
+ Connection(const std::string& url, const std::string& method,
+ std::shared_ptr<http::Transport> transport);
+ virtual ~Connection();
+
+ // Overrides from http::Connection.
+ // See http_connection.h for description of these methods.
+ virtual bool SendHeaders(const HeaderList& headers, ErrorPtr* error) override;
+ virtual bool WriteRequestData(const void* data, size_t size,
+ ErrorPtr* error) override;
+ virtual bool FinishRequest(ErrorPtr* error) override;
+
+ virtual int GetResponseStatusCode() const override;
+ virtual std::string GetResponseStatusText() const override;
+ virtual std::string GetProtocolVersion() const override;
+ virtual std::string GetResponseHeader(
+ const std::string& header_name) const override;
+ virtual uint64_t GetResponseDataSize() const override;
+ virtual bool ReadResponseData(void* data, size_t buffer_size,
+ size_t* size_read, ErrorPtr* error) override;
+
+ private:
+ // Request and response objects passed to the user-provided request handler
+ // callback. The request object contains all the request information.
+ // The response object is the server response that is created by
+ // the handler in response to the request.
+ ServerRequest request_;
+ ServerResponse response_;
+
+ // Internal read data pointer needed for ReadResponseData() implementation.
+ size_t response_data_ptr_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(Connection);
+};
+
+} // namespace fake
+} // namespace http
+} // namespace buffet
+
+#endif // BUFFET_HTTP_CONNECTION_FAKE_H_
diff --git a/buffet/http_request.cc b/buffet/http_request.cc
new file mode 100644
index 0000000..df09ca4
--- /dev/null
+++ b/buffet/http_request.cc
@@ -0,0 +1,297 @@
+// Copyright 2014 The Chromium OS 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 "buffet/http_request.h"
+
+#include <base/logging.h>
+
+#include "buffet/http_connection_curl.h"
+#include "buffet/http_transport_curl.h"
+#include "buffet/map_utils.h"
+#include "buffet/mime_utils.h"
+#include "buffet/string_utils.h"
+
+namespace buffet {
+namespace http {
+
+// request_type
+const char request_type::kOptions[] = "OPTIONS";
+const char request_type::kGet[] = "GET";
+const char request_type::kHead[] = "HEAD";
+const char request_type::kPost[] = "POST";
+const char request_type::kPut[] = "PUT";
+const char request_type::kPatch[] = "PATCH";
+const char request_type::kDelete[] = "DELETE";
+const char request_type::kTrace[] = "TRACE";
+const char request_type::kConnect[] = "CONNECT";
+const char request_type::kCopy[] = "COPY";
+const char request_type::kMove[] = "MOVE";
+
+// request_header
+const char request_header::kAccept[] = "Accept";
+const char request_header::kAcceptCharset[] = "Accept-Charset";
+const char request_header::kAcceptEncoding[] = "Accept-Encoding";
+const char request_header::kAcceptLanguage[] = "Accept-Language";
+const char request_header::kAllow[] = "Allow";
+const char request_header::kAuthorization[] = "Authorization";
+const char request_header::kCacheControl[] = "Cache-Control";
+const char request_header::kConnection[] = "Connection";
+const char request_header::kContentEncoding[] = "Content-Encoding";
+const char request_header::kContentLanguage[] = "Content-Language";
+const char request_header::kContentLength[] = "Content-Length";
+const char request_header::kContentLocation[] = "Content-Location";
+const char request_header::kContentMd5[] = "Content-MD5";
+const char request_header::kContentRange[] = "Content-Range";
+const char request_header::kContentType[] = "Content-Type";
+const char request_header::kCookie[] = "Cookie";
+const char request_header::kDate[] = "Date";
+const char request_header::kExpect[] = "Expect";
+const char request_header::kExpires[] = "Expires";
+const char request_header::kFrom[] = "From";
+const char request_header::kHost[] = "Host";
+const char request_header::kIfMatch[] = "If-Match";
+const char request_header::kIfModifiedSince[] = "If-Modified-Since";
+const char request_header::kIfNoneMatch[] = "If-None-Match";
+const char request_header::kIfRange[] = "If-Range";
+const char request_header::kIfUnmodifiedSince[] = "If-Unmodified-Since";
+const char request_header::kLastModified[] = "Last-Modified";
+const char request_header::kMaxForwards[] = "Max-Forwards";
+const char request_header::kPragma[] = "Pragma";
+const char request_header::kProxyAuthorization[] = "Proxy-Authorization";
+const char request_header::kRange[] = "Range";
+const char request_header::kReferer[] = "Referer";
+const char request_header::kTE[] = "TE";
+const char request_header::kTrailer[] = "Trailer";
+const char request_header::kTransferEncoding[] = "Transfer-Encoding";
+const char request_header::kUpgrade[] = "Upgrade";
+const char request_header::kUserAgent[] = "User-Agent";
+const char request_header::kVia[] = "Via";
+const char request_header::kWarning[] = "Warning";
+
+// response_header
+const char response_header::kAcceptRanges[] = "Accept-Ranges";
+const char response_header::kAge[] = "Age";
+const char response_header::kAllow[] = "Allow";
+const char response_header::kCacheControl[] = "Cache-Control";
+const char response_header::kConnection[] = "Connection";
+const char response_header::kContentEncoding[] = "Content-Encoding";
+const char response_header::kContentLanguage[] = "Content-Language";
+const char response_header::kContentLength[] = "Content-Length";
+const char response_header::kContentLocation[] = "Content-Location";
+const char response_header::kContentMd5[] = "Content-MD5";
+const char response_header::kContentRange[] = "Content-Range";
+const char response_header::kContentType[] = "Content-Type";
+const char response_header::kDate[] = "Date";
+const char response_header::kETag[] = "ETag";
+const char response_header::kExpires[] = "Expires";
+const char response_header::kLastModified[] = "Last-Modified";
+const char response_header::kLocation[] = "Location";
+const char response_header::kPragma[] = "Pragma";
+const char response_header::kProxyAuthenticate[] = "Proxy-Authenticate";
+const char response_header::kRetryAfter[] = "Retry-After";
+const char response_header::kServer[] = "Server";
+const char response_header::kSetCookie[] = "Set-Cookie";
+const char response_header::kTrailer[] = "Trailer";
+const char response_header::kTransferEncoding[] = "Transfer-Encoding";
+const char response_header::kUpgrade[] = "Upgrade";
+const char response_header::kVary[] = "Vary";
+const char response_header::kVia[] = "Via";
+const char response_header::kWarning[] = "Warning";
+const char response_header::kWwwAuthenticate[] = "WWW-Authenticate";
+
+// ***********************************************************
+// ********************** Request Class **********************
+// ***********************************************************
+Request::Request(const std::string& url, const char* method,
+ std::shared_ptr<Transport> transport) :
+ transport_(transport), request_url_(url), method_(method) {
+ VLOG(1) << "http::Request created";
+ if (!transport_)
+ transport_.reset(new http::curl::Transport());
+}
+
+Request::~Request() {
+ VLOG(1) << "http::Request destroyed";
+}
+
+void Request::AddRange(int64_t bytes) {
+ if (bytes < 0) {
+ ranges_.emplace_back(Request::range_value_omitted, -bytes);
+ } else {
+ ranges_.emplace_back(bytes, Request::range_value_omitted);
+ }
+}
+
+void Request::AddRange(uint64_t from_byte, uint64_t to_byte) {
+ ranges_.emplace_back(from_byte, to_byte);
+}
+
+std::unique_ptr<Response> Request::GetResponse(ErrorPtr* error) {
+ if (!SendRequestIfNeeded(error) || !connection_->FinishRequest(error))
+ return std::unique_ptr<Response>();
+ std::unique_ptr<Response> response(new Response(std::move(connection_)));
+ transport_.reset(); // Indicate that the response has been received
+ return response;
+}
+
+void Request::SetAccept(const char* accept_mime_types) {
+ accept_ = accept_mime_types;
+}
+
+std::string Request::GetAccept() const {
+ return accept_;
+}
+
+void Request::SetContentType(const char* contentType) {
+ content_type_ = contentType;
+}
+
+std::string Request::GetContentType() const {
+ return content_type_;
+}
+
+void Request::AddHeader(const char* header, const char* value) {
+ headers_[header] = value;
+}
+
+void Request::AddHeaders(const HeaderList& headers) {
+ headers_.insert(headers.begin(), headers.end());
+}
+
+bool Request::AddRequestBody(const void* data, size_t size, ErrorPtr* error) {
+ if (!SendRequestIfNeeded(error))
+ return false;
+ return connection_->WriteRequestData(data, size, error);
+}
+
+void Request::SetReferer(const char* referer) {
+ referer_ = referer;
+}
+
+std::string Request::GetReferer() const {
+ return referer_;
+}
+
+void Request::SetUserAgent(const char* user_agent) {
+ user_agent_ = user_agent;
+}
+
+std::string Request::GetUserAgent() const {
+ return user_agent_;
+}
+
+bool Request::SendRequestIfNeeded(ErrorPtr* error) {
+ if (transport_) {
+ if (!connection_) {
+ http::HeaderList headers = MapToVector(headers_);
+ std::vector<std::string> ranges;
+ if (method_ != request_type::kHead) {
+ ranges.reserve(ranges_.size());
+ for (auto p : ranges_) {
+ if (p.first != range_value_omitted ||
+ p.second != range_value_omitted) {
+ std::string range;
+ if (p.first != range_value_omitted) {
+ range = std::to_string(p.first);
+ }
+ range += '-';
+ if (p.second != range_value_omitted) {
+ range += std::to_string(p.second);
+ }
+ ranges.push_back(range);
+ }
+ }
+ }
+ if (!ranges.empty())
+ headers.emplace_back(request_header::kRange,
+ "bytes=" + string_utils::Join(',', ranges));
+
+ headers.emplace_back(request_header::kAccept, GetAccept());
+ if (method_ != request_type::kGet && method_ != request_type::kHead) {
+ if (!content_type_.empty())
+ headers.emplace_back(request_header::kContentType, content_type_);
+ }
+ connection_ = transport_->CreateConnection(transport_, request_url_,
+ method_, headers,
+ user_agent_, referer_,
+ error);
+ }
+
+ if (connection_)
+ return true;
+ } else {
+ Error::AddTo(error, http::curl::kErrorDomain,
+ "request_already_received", "HTTP response already received");
+ }
+ return false;
+}
+
+// ************************************************************
+// ********************** Response Class **********************
+// ************************************************************
+Response::Response(std::unique_ptr<Connection> connection)
+ : connection_(std::move(connection)) {
+ VLOG(1) << "http::Response created";
+ // Response object doesn't have streaming interface for response data (yet),
+ // so read the data into a buffer and cache it.
+ if (connection_) {
+ size_t size = static_cast<size_t>(connection_->GetResponseDataSize());
+ response_data_.reserve(size);
+ unsigned char buffer[1024];
+ size_t read = 0;
+ while (connection_->ReadResponseData(buffer, sizeof(buffer),
+ &read, nullptr) && read > 0) {
+ response_data_.insert(response_data_.end(), buffer, buffer + read);
+ }
+ }
+}
+
+Response::~Response() {
+ VLOG(1) << "http::Response destroyed";
+}
+
+bool Response::IsSuccessful() const {
+ int code = GetStatusCode();
+ return code >= status_code::Continue && code < status_code::BadRequest;
+}
+
+int Response::GetStatusCode() const {
+ if (!connection_)
+ return -1;
+
+ return connection_->GetResponseStatusCode();
+}
+
+std::string Response::GetStatusText() const {
+ if (!connection_)
+ return std::string();
+
+ return connection_->GetResponseStatusText();
+}
+
+std::string Response::GetContentType() const {
+ return GetHeader(response_header::kContentType);
+}
+
+const std::vector<unsigned char>& Response::GetData() const {
+ return response_data_;
+}
+
+std::string Response::GetDataAsString() const {
+ if (response_data_.empty())
+ return std::string();
+
+ const char* data_buf = reinterpret_cast<const char*>(response_data_.data());
+ return std::string(data_buf, data_buf + response_data_.size());
+}
+
+std::string Response::GetHeader(const char* header_name) const {
+ if (connection_)
+ return connection_->GetResponseHeader(header_name);
+
+ return std::string();
+}
+
+} // namespace http
+} // namespace buffet
diff --git a/buffet/http_request.h b/buffet/http_request.h
new file mode 100644
index 0000000..8aedc45
--- /dev/null
+++ b/buffet/http_request.h
@@ -0,0 +1,351 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_HTTP_REQUEST_H_
+#define BUFFET_HTTP_REQUEST_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/basictypes.h>
+
+#include "buffet/http_connection.h"
+#include "buffet/http_transport.h"
+#include "buffet/error.h"
+
+namespace buffet {
+namespace http {
+
+// HTTP request verbs
+namespace request_type {
+ extern const char kOptions[];
+ extern const char kGet[];
+ extern const char kHead[];
+ extern const char kPost[];
+ extern const char kPut[];
+ extern const char kPatch[]; // Not a standard HTTP/1.1 request method
+ extern const char kDelete[];
+ extern const char kTrace[];
+ extern const char kConnect[];
+ extern const char kCopy[]; // Not a standard HTTP/1.1 request method
+ extern const char kMove[]; // Not a standard HTTP/1.1 request method
+} // namespace request_type
+
+// HTTP request header names
+namespace request_header {
+ extern const char kAccept[];
+ extern const char kAcceptCharset[];
+ extern const char kAcceptEncoding[];
+ extern const char kAcceptLanguage[];
+ extern const char kAllow[];
+ extern const char kAuthorization[];
+ extern const char kCacheControl[];
+ extern const char kConnection[];
+ extern const char kContentEncoding[];
+ extern const char kContentLanguage[];
+ extern const char kContentLength[];
+ extern const char kContentLocation[];
+ extern const char kContentMd5[];
+ extern const char kContentRange[];
+ extern const char kContentType[];
+ extern const char kCookie[];
+ extern const char kDate[];
+ extern const char kExpect[];
+ extern const char kExpires[];
+ extern const char kFrom[];
+ extern const char kHost[];
+ extern const char kIfMatch[];
+ extern const char kIfModifiedSince[];
+ extern const char kIfNoneMatch[];
+ extern const char kIfRange[];
+ extern const char kIfUnmodifiedSince[];
+ extern const char kLastModified[];
+ extern const char kMaxForwards[];
+ extern const char kPragma[];
+ extern const char kProxyAuthorization[];
+ extern const char kRange[];
+ extern const char kReferer[];
+ extern const char kTE[];
+ extern const char kTrailer[];
+ extern const char kTransferEncoding[];
+ extern const char kUpgrade[];
+ extern const char kUserAgent[];
+ extern const char kVia[];
+ extern const char kWarning[];
+} // namespace request_header
+
+// HTTP response header names
+namespace response_header {
+ extern const char kAcceptRanges[];
+ extern const char kAge[];
+ extern const char kAllow[];
+ extern const char kCacheControl[];
+ extern const char kConnection[];
+ extern const char kContentEncoding[];
+ extern const char kContentLanguage[];
+ extern const char kContentLength[];
+ extern const char kContentLocation[];
+ extern const char kContentMd5[];
+ extern const char kContentRange[];
+ extern const char kContentType[];
+ extern const char kDate[];
+ extern const char kETag[];
+ extern const char kExpires[];
+ extern const char kLastModified[];
+ extern const char kLocation[];
+ extern const char kPragma[];
+ extern const char kProxyAuthenticate[];
+ extern const char kRetryAfter[];
+ extern const char kServer[];
+ extern const char kSetCookie[];
+ extern const char kTrailer[];
+ extern const char kTransferEncoding[];
+ extern const char kUpgrade[];
+ extern const char kVary[];
+ extern const char kVia[];
+ extern const char kWarning[];
+ extern const char kWwwAuthenticate[];
+} // namespace response_header
+
+// HTTP request status (error) codes
+namespace status_code {
+ // OK to continue with request
+ static const int Continue = 100;
+ // Server has switched protocols in upgrade header
+ static const int SwitchProtocols = 101;
+
+ // Request completed
+ static const int Ok = 200;
+ // Object created, reason = new URI
+ static const int Created = 201;
+ // Async completion (TBS)
+ static const int Accepted = 202;
+ // Partial completion
+ static const int Partial = 203;
+ // No info to return
+ static const int NoContent = 204;
+ // Request completed, but clear form
+ static const int ResetContent = 205;
+ // Partial GET fulfilled
+ static const int PartialContent = 206;
+
+ // Server couldn't decide what to return
+ static const int Ambiguous = 300;
+ // Object permanently moved
+ static const int Moved = 301;
+ // Object temporarily moved
+ static const int Redirect = 302;
+ // Redirection w/ new access method
+ static const int RedirectMethod = 303;
+ // If-Modified-Since was not modified
+ static const int NotModified = 304;
+ // Redirection to proxy, location header specifies proxy to use
+ static const int UseProxy = 305;
+ // HTTP/1.1: keep same verb
+ static const int RedirectKeepVerb = 307;
+
+ // Invalid syntax
+ static const int BadRequest = 400;
+ // Access denied
+ static const int Denied = 401;
+ // Payment required
+ static const int PaymentRequired = 402;
+ // Request forbidden
+ static const int Forbidden = 403;
+ // Object not found
+ static const int NotFound = 404;
+ // Method is not allowed
+ static const int BadMethod = 405;
+ // No response acceptable to client found
+ static const int NoneAcceptable = 406;
+ // Proxy authentication required
+ static const int ProxyAuthRequired = 407;
+ // Server timed out waiting for request
+ static const int RequestTimeout = 408;
+ // User should resubmit with more info
+ static const int Conflict = 409;
+ // The resource is no longer available
+ static const int Gone = 410;
+ // The server refused to accept request w/o a length
+ static const int LengthRequired = 411;
+ // Precondition given in request failed
+ static const int PrecondionFailed = 412;
+ // Request entity was too large
+ static const int RequestTooLarge = 413;
+ // Request URI too long
+ static const int UriTooLong = 414;
+ // Unsupported media type
+ static const int UnsupportedMedia = 415;
+ // Retry after doing the appropriate action.
+ static const int RetryWith = 449;
+
+ // Internal server error
+ static const int InternalServerError = 500;
+ // Request not supported
+ static const int NotSupported = 501;
+ // Error response received from gateway
+ static const int BadGateway = 502;
+ // Temporarily overloaded
+ static const int ServiceUnavailable = 503;
+ // Timed out waiting for gateway
+ static const int GatewayTimeout = 504;
+ // HTTP version not supported
+ static const int VersionNotSupported = 505;
+} // namespace status_code
+
+class Response; // Just a forward declaration.
+
+///////////////////////////////////////////////////////////////////////////////
+// Request class is the main object used to set up and initiate an HTTP
+// communication session. It is used to specify the HTTP request method,
+// request URL and many optional parameters (such as HTTP headers, user agent,
+// referer URL and so on.
+//
+// Once everything is setup, GetResponse() method is used to send the request
+// and obtain the server response. The returned Response object can be
+// used to inspect the response code, HTTP headers and/or response body.
+///////////////////////////////////////////////////////////////////////////////
+class Request {
+ public:
+ // The main constructor. |url| specifies the remote host address/path
+ // to send the request to. |method| is the HTTP request verb and
+ // |transport| is the HTTP transport implementation for server communications.
+ Request(const std::string& url, const char* method,
+ std::shared_ptr<Transport> transport);
+ ~Request();
+
+ // Gets/Sets "Accept:" header value. The default value is "*/*" if not set.
+ void SetAccept(const char* accept_mime_types);
+ std::string GetAccept() const;
+
+ // Gets/Sets "Content-Type:" header value
+ void SetContentType(const char* content_type);
+ std::string GetContentType() const;
+
+ // Adds additional HTTP request header
+ void AddHeader(const char* header, const char* value);
+ void AddHeaders(const HeaderList& headers);
+
+ // Removes HTTP request header
+ void RemoveHeader(const char* header);
+
+ // Adds a request body. This is not to be used with GET method
+ bool AddRequestBody(const void* data, size_t size, ErrorPtr* error);
+
+ // Makes a request for a subrange of data. Specifies a partial range with
+ // either from beginning of the data to the specified offset (if |bytes| is
+ // negative) or from the specified offset to the end of data (if |bytes| is
+ // positive).
+ // All individual ranges will be sent as part of "Range:" HTTP request header.
+ void AddRange(int64_t bytes);
+
+ // Makes a request for a subrange of data. Specifies a full range with
+ // start and end bytes from the beginning of the requested data.
+ // All individual ranges will be sent as part of "Range:" HTTP request header.
+ void AddRange(uint64_t from_byte, uint64_t to_byte);
+
+ // Returns the request URL
+ std::string GetRequestURL() const;
+
+ // Gets/Sets a request referer URL (sent as "Referer:" request header).
+ void SetReferer(const char* referer);
+ std::string GetReferer() const;
+
+ // Gets/Sets a user agent string (sent as "User-Agent:" request header).
+ void SetUserAgent(const char* user_agent);
+ std::string GetUserAgent() const;
+
+ // Sends the request to the server and returns the response object.
+ // In case the server couldn't be reached for whatever reason, returns
+ // empty unique_ptr (null). In such a case, the additional error information
+ // can be returned through the optional supplied |error| parameter.
+ std::unique_ptr<Response> GetResponse(ErrorPtr* error);
+
+ private:
+ // Helper function to create an http::Connection and send off request headers.
+ bool SendRequestIfNeeded(ErrorPtr* error);
+
+ // Implementation that provides particular HTTP transport.
+ std::shared_ptr<Transport> transport_;
+
+ // An established connection for adding request body. This connection
+ // is maintained by the request object after the headers have been
+ // sent and before the response is requested.
+ std::unique_ptr<Connection> connection_;
+
+ // Full request URL, such as "http://www.host.com/path/to/object"
+ std::string request_url_;
+ // HTTP request verb, such as "GET", "POST", "PUT", ...
+ std::string method_;
+
+ // Referrer URL, if any. Sent to the server via "Referer: " header.
+ std::string referer_;
+ // User agent string, if any. Sent to the server via "User-Agent: " header.
+ std::string user_agent_;
+ // Content type of the request body data.
+ // Sent to the server via "Content-Type: " header.
+ std::string content_type_;
+ // List of acceptable response data types.
+ // Sent to the server via "Accept: " header.
+ std::string accept_ = "*/*";
+
+ // List of optional request headers provided by the caller.
+ // After request has been sent, contains the received response headers.
+ std::map<std::string, std::string> headers_;
+ // List of optional data ranges to request partial content from the server.
+ // Sent to the server as "Range: " header.
+ std::vector<std::pair<uint64_t, uint64_t>> ranges_;
+
+ // range_value_omitted is used in |ranges_| list to indicate omitted value.
+ // E.g. range (10,range_value_omitted) represents bytes from 10 to the end
+ // of the data stream.
+ const uint64_t range_value_omitted = (uint64_t)-1;
+
+ DISALLOW_COPY_AND_ASSIGN(Request);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Response class is returned from Request::GetResponse() and is a way
+// to get to response status, error codes, response HTTP headers and response
+// data (body) if available.
+///////////////////////////////////////////////////////////////////////////////
+class Response {
+ public:
+ explicit Response(std::unique_ptr<Connection> connection);
+ ~Response();
+
+ // Returns true if server returned a success code (status code below 400).
+ bool IsSuccessful() const;
+
+ // Returns the HTTP status code (e.g. 200 for success)
+ int GetStatusCode() const;
+
+ // Returns the status text (e.g. for error 403 it could be "NOT AUTHORIZED").
+ std::string GetStatusText() const;
+
+ // Returns the content type of the response data.
+ std::string GetContentType() const;
+
+ // Returns response data as a byte array
+ const std::vector<unsigned char>& GetData() const;
+
+ // Returns response data as a string
+ std::string GetDataAsString() const;
+
+ // Returns a value of a given response HTTP header.
+ std::string GetHeader(const char* header_name) const;
+
+ private:
+ std::unique_ptr<Connection> connection_;
+ std::vector<unsigned char> response_data_;
+ DISALLOW_COPY_AND_ASSIGN(Response);
+};
+
+} // namespace http
+} // namespace buffet
+
+#endif // BUFFET_HTTP_REQUEST_H_
diff --git a/buffet/http_transport.h b/buffet/http_transport.h
new file mode 100644
index 0000000..e26ee77
--- /dev/null
+++ b/buffet/http_transport.h
@@ -0,0 +1,54 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_HTTP_TRANSPORT_H_
+#define BUFFET_HTTP_TRANSPORT_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/basictypes.h>
+
+#include "buffet/error.h"
+
+namespace buffet {
+namespace http {
+
+typedef std::vector<std::pair<std::string, std::string>> HeaderList;
+
+class Request;
+class Connection;
+
+///////////////////////////////////////////////////////////////////////////////
+// Transport is a base class for specific implementation of HTTP communication.
+// This class (and its underlying implementation) is used by http::Request and
+// http::Response classes to provide HTTP functionality to the clients.
+///////////////////////////////////////////////////////////////////////////////
+class Transport {
+ public:
+ Transport() = default;
+ virtual ~Transport() = default;
+
+ // Creates a connection object and initializes it with the specified data.
+ // |transport| is a shared pointer to this transport object instance,
+ // used to maintain the object alive as long as the connection exists.
+ virtual std::unique_ptr<Connection> CreateConnection(
+ std::shared_ptr<Transport> transport,
+ const std::string& url,
+ const std::string& method,
+ const HeaderList& headers,
+ const std::string& user_agent,
+ const std::string& referer,
+ ErrorPtr* error) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Transport);
+};
+
+} // namespace http
+} // namespace buffet
+
+#endif // BUFFET_HTTP_TRANSPORT_H_
diff --git a/buffet/http_transport_curl.cc b/buffet/http_transport_curl.cc
new file mode 100644
index 0000000..7ef914b
--- /dev/null
+++ b/buffet/http_transport_curl.cc
@@ -0,0 +1,81 @@
+// Copyright 2014 The Chromium OS 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 "buffet/http_transport_curl.h"
+
+#include <base/logging.h>
+
+#include "buffet/http_connection_curl.h"
+#include "buffet/http_request.h"
+
+namespace buffet {
+namespace http {
+namespace curl {
+
+const char kErrorDomain[] = "http_transport";
+
+Transport::Transport() {
+ VLOG(1) << "curl::Transport created";
+}
+
+Transport::~Transport() {
+ VLOG(1) << "curl::Transport destroyed";
+}
+
+std::unique_ptr<http::Connection> Transport::CreateConnection(
+ std::shared_ptr<http::Transport> transport,
+ const std::string& url,
+ const std::string& method,
+ const HeaderList& headers,
+ const std::string& user_agent,
+ const std::string& referer,
+ ErrorPtr* error) {
+ CURL* curl_handle = curl_easy_init();
+ if (!curl_handle) {
+ LOG(ERROR) << "Failed to initialize CURL";
+ Error::AddTo(error, http::curl::kErrorDomain, "curl_init_failed",
+ "Failed to initialize CURL");
+ return std::unique_ptr<http::Connection>();
+ }
+
+ LOG(INFO) << "Sending a " << method << " request to " << url;
+ curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
+
+ if (!user_agent.empty()) {
+ curl_easy_setopt(curl_handle,
+ CURLOPT_USERAGENT, user_agent.c_str());
+ }
+
+ if (!referer.empty()) {
+ curl_easy_setopt(curl_handle,
+ CURLOPT_REFERER, referer.c_str());
+ }
+
+ // Setup HTTP request method and optional request body.
+ if (method == request_type::kGet) {
+ curl_easy_setopt(curl_handle, CURLOPT_HTTPGET, 1L);
+ } else if (method == request_type::kHead) {
+ curl_easy_setopt(curl_handle, CURLOPT_NOBODY, 1L);
+ } else if (method == request_type::kPut) {
+ curl_easy_setopt(curl_handle, CURLOPT_UPLOAD, 1L);
+ } else {
+ // POST and custom request methods
+ curl_easy_setopt(curl_handle, CURLOPT_POST, 1L);
+ curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, nullptr);
+ if (method != request_type::kPost)
+ curl_easy_setopt(curl_handle, CURLOPT_CUSTOMREQUEST, method.c_str());
+ }
+
+ std::unique_ptr<http::Connection> connection(
+ new http::curl::Connection(curl_handle, method, transport));
+ CHECK(connection) << "Unable to create Connection object";
+ if (!connection->SendHeaders(headers, error)) {
+ connection.reset();
+ }
+ return connection;
+}
+
+} // namespace curl
+} // namespace http
+} // namespace buffet
diff --git a/buffet/http_transport_curl.h b/buffet/http_transport_curl.h
new file mode 100644
index 0000000..ddb24ce
--- /dev/null
+++ b/buffet/http_transport_curl.h
@@ -0,0 +1,47 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_HTTP_TRANSPORT_CURL_H_
+#define BUFFET_HTTP_TRANSPORT_CURL_H_
+
+#include <string>
+
+#include "buffet/http_transport.h"
+
+namespace buffet {
+namespace http {
+namespace curl {
+
+extern const char kErrorDomain[];
+
+///////////////////////////////////////////////////////////////////////////////
+// An implementation of http::Transport that uses libcurl for
+// HTTP communications. This class (as http::Transport base)
+// is used by http::Request and http::Response classes to provide HTTP
+// functionality to the clients.
+// See http_transport.h for more details.
+///////////////////////////////////////////////////////////////////////////////
+class Transport : public http::Transport {
+ public:
+ Transport();
+ virtual ~Transport();
+
+ virtual std::unique_ptr<http::Connection> CreateConnection(
+ std::shared_ptr<http::Transport> transport,
+ const std::string& url,
+ const std::string& method,
+ const HeaderList& headers,
+ const std::string& user_agent,
+ const std::string& referer,
+ ErrorPtr* error) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Transport);
+};
+
+} // namespace curl
+} // namespace http
+} // namespace buffet
+
+#endif // BUFFET_HTTP_TRANSPORT_CURL_H_
diff --git a/buffet/http_transport_fake.cc b/buffet/http_transport_fake.cc
new file mode 100644
index 0000000..0e7dfc5
--- /dev/null
+++ b/buffet/http_transport_fake.cc
@@ -0,0 +1,260 @@
+// Copyright 2014 The Chromium OS 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 "buffet/http_transport_fake.h"
+
+#include <utility>
+
+#include <base/json/json_reader.h>
+#include <base/json/json_writer.h>
+#include <base/logging.h>
+
+#include "buffet/bind_lambda.h"
+#include "buffet/http_connection_fake.h"
+#include "buffet/http_request.h"
+#include "buffet/mime_utils.h"
+#include "buffet/url_utils.h"
+
+namespace buffet {
+
+using http::fake::Transport;
+using http::fake::ServerRequestResponseBase;
+using http::fake::ServerRequest;
+using http::fake::ServerResponse;
+
+Transport::Transport() {
+ VLOG(1) << "fake::Transport created";
+}
+
+Transport::~Transport() {
+ VLOG(1) << "fake::Transport destroyed";
+}
+
+std::unique_ptr<http::Connection> Transport::CreateConnection(
+ std::shared_ptr<http::Transport> transport,
+ const std::string& url,
+ const std::string& method,
+ const HeaderList& headers,
+ const std::string& user_agent,
+ const std::string& referer,
+ ErrorPtr* error) {
+ HeaderList headers_copy = headers;
+ if (!user_agent.empty()) {
+ headers_copy.push_back(std::make_pair(http::request_header::kUserAgent,
+ user_agent));
+ }
+ if (!referer.empty()) {
+ headers_copy.push_back(std::make_pair(http::request_header::kReferer,
+ referer));
+ }
+ std::unique_ptr<http::Connection> connection(
+ new http::fake::Connection(url, method, transport));
+ CHECK(connection) << "Unable to create Connection object";
+ if (!connection->SendHeaders(headers_copy, error))
+ connection.reset();
+ request_count_++;
+ return connection;
+}
+
+static inline std::string GetHandlerMapKey(const std::string& url,
+ const std::string& method) {
+ return method + ":" + url;
+}
+
+void Transport::AddHandler(const std::string& url, const std::string& method,
+ const HandlerCallback& handler) {
+ handlers_.insert(std::make_pair(GetHandlerMapKey(url, method), handler));
+}
+
+void Transport::AddSimpleReplyHandler(const std::string& url,
+ const std::string& method,
+ int status_code,
+ const std::string& reply_text,
+ const std::string& mime_type) {
+ auto handler = [status_code, reply_text, mime_type](
+ const ServerRequest& request, ServerResponse* response) {
+ response->ReplyText(status_code, reply_text, mime_type.c_str());
+ };
+ AddHandler(url, method, base::Bind(handler));
+}
+
+Transport::HandlerCallback Transport::GetHandler(
+ const std::string& url, const std::string& method) const {
+ // First try the exact combination of URL/Method
+ auto p = handlers_.find(GetHandlerMapKey(url, method));
+ if (p != handlers_.end())
+ return p->second;
+ // If not found, try URL/*
+ p = handlers_.find(GetHandlerMapKey(url, "*"));
+ if (p != handlers_.end())
+ return p->second;
+ // If still not found, try */method
+ p = handlers_.find(GetHandlerMapKey("*", method));
+ if (p != handlers_.end())
+ return p->second;
+ // Finally, try */*
+ p = handlers_.find(GetHandlerMapKey("*", "*"));
+ return (p != handlers_.end()) ? p->second : HandlerCallback();
+}
+
+void ServerRequestResponseBase::AddData(const void* data, size_t data_size) {
+ auto bytes = reinterpret_cast<const unsigned char*>(data);
+ data_.insert(data_.end(), bytes, bytes + data_size);
+}
+
+std::string ServerRequestResponseBase::GetDataAsString() const {
+ if (data_.empty())
+ return std::string();
+ auto chars = reinterpret_cast<const char*>(data_.data());
+ return std::string(chars, data_.size());
+}
+
+std::unique_ptr<base::DictionaryValue>
+ ServerRequestResponseBase::GetDataAsJson() const {
+ if (mime::RemoveParameters(GetHeader(request_header::kContentType)) ==
+ mime::application::kJson) {
+ auto value = base::JSONReader::Read(GetDataAsString());
+ if (value) {
+ base::DictionaryValue* dict = nullptr;
+ if (value->GetAsDictionary(&dict)) {
+ return std::unique_ptr<base::DictionaryValue>(dict);
+ } else {
+ delete value;
+ }
+ }
+ }
+ return std::unique_ptr<base::DictionaryValue>();
+}
+
+void ServerRequestResponseBase::AddHeaders(const HeaderList& headers) {
+ for (auto&& pair : headers) {
+ if (pair.second.empty())
+ headers_.erase(pair.first);
+ else
+ headers_.insert(pair);
+ }
+}
+
+std::string ServerRequestResponseBase::GetHeader(
+ const std::string& header_name) const {
+ auto p = headers_.find(header_name);
+ return p != headers_.end() ? p->second : std::string();
+}
+
+ServerRequest::ServerRequest(const std::string& url,
+ const std::string& method) : method_(method) {
+ auto params = url::GetQueryStringParameters(url);
+ url_ = url::RemoveQueryString(url, true);
+ form_fields_.insert(params.begin(), params.end());
+}
+
+std::string ServerRequest::GetFormField(const std::string& field_name) const {
+ if (!form_fields_parsed_) {
+ std::string mime_type = mime::RemoveParameters(
+ GetHeader(request_header::kContentType));
+ if (mime_type == mime::application::kWwwFormUrlEncoded &&
+ !GetData().empty()) {
+ auto fields = data_encoding::WebParamsDecode(GetDataAsString());
+ form_fields_.insert(fields.begin(), fields.end());
+ }
+ form_fields_parsed_ = true;
+ }
+ auto p = form_fields_.find(field_name);
+ return p != form_fields_.end() ? p->second : std::string();
+}
+
+void ServerResponse::Reply(int status_code, const void* data, size_t data_size,
+ const char* mime_type) {
+ data_.clear();
+ status_code_ = status_code;
+ AddData(data, data_size);
+ AddHeaders({
+ {response_header::kContentLength, std::to_string(data_size)},
+ {response_header::kContentType, mime_type}
+ });
+}
+
+void ServerResponse::ReplyText(int status_code, const std::string& text,
+ const char* mime_type) {
+ Reply(status_code, text.data(), text.size(), mime_type);
+}
+
+void ServerResponse::ReplyJson(int status_code, const base::Value* json) {
+ std::string text;
+ base::JSONWriter::WriteWithOptions(json,
+ base::JSONWriter::OPTIONS_PRETTY_PRINT,
+ &text);
+ std::string mime_type = mime::AppendParameter(mime::application::kJson,
+ mime::parameters::kCharset,
+ "utf-8");
+ ReplyText(status_code, text, mime_type.c_str());
+}
+
+void ServerResponse::ReplyJson(int status_code,
+ const http::FormFieldList& fields) {
+ base::DictionaryValue json;
+ for (auto&& pair : fields) {
+ json.SetString(pair.first, pair.second);
+ }
+ ReplyJson(status_code, &json);
+}
+
+std::string ServerResponse::GetStatusText() const {
+ static std::vector<std::pair<int, const char*>> status_text_map = {
+ {100, "Continue"},
+ {101, "Switching Protocols"},
+ {102, "Processing"},
+ {200, "OK"},
+ {201, "Created"},
+ {202, "Accepted"},
+ {203, "Non-Authoritative Information"},
+ {204, "No Content"},
+ {205, "Reset Content"},
+ {206, "Partial Content"},
+ {207, "Multi-Status"},
+ {208, "Already Reported"},
+ {226, "IM Used"},
+ {300, "Multiple Choices"},
+ {301, "Moved Permanently"},
+ {302, "Found"},
+ {303, "See Other"},
+ {304, "Not Modified"},
+ {305, "Use Proxy"},
+ {306, "Switch Proxy"},
+ {307, "Temporary Redirect"},
+ {308, "Permanent Redirect"},
+ {400, "Bad Request"},
+ {401, "Unauthorized"},
+ {402, "Payment Required"},
+ {403, "Forbidden"},
+ {404, "Not Found"},
+ {405, "Method Not Allowed"},
+ {406, "Not Acceptable"},
+ {407, "Proxy Authentication Required"},
+ {408, "Request Timeout"},
+ {409, "Conflict"},
+ {410, "Gone"},
+ {411, "Length Required"},
+ {412, "Precondition Failed"},
+ {413, "Request Entity Too Large"},
+ {414, "Request - URI Too Long"},
+ {415, "Unsupported Media Type"},
+ {429, "Too Many Requests"},
+ {431, "Request Header Fields Too Large"},
+ {500, "Internal Server Error"},
+ {501, "Not Implemented"},
+ {502, "Bad Gateway"},
+ {503, "Service Unavailable"},
+ {504, "Gateway Timeout"},
+ {505, "HTTP Version Not Supported"},
+ };
+
+ for (auto&& pair : status_text_map) {
+ if (pair.first == status_code_)
+ return pair.second;
+ }
+ return std::string();
+}
+
+} // namespace buffet
diff --git a/buffet/http_transport_fake.h b/buffet/http_transport_fake.h
new file mode 100644
index 0000000..7905f3e
--- /dev/null
+++ b/buffet/http_transport_fake.h
@@ -0,0 +1,223 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_HTTP_TRANSPORT_FAKE_H_
+#define BUFFET_HTTP_TRANSPORT_FAKE_H_
+
+#include <map>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/values.h>
+
+#include "buffet/http_transport.h"
+#include "buffet/http_utils.h"
+
+namespace buffet {
+namespace http {
+namespace fake {
+
+class ServerRequest;
+class ServerResponse;
+class Connection;
+
+///////////////////////////////////////////////////////////////////////////////
+// A fake implementation of http::Transport that simulates HTTP communication
+// with a server.
+///////////////////////////////////////////////////////////////////////////////
+class Transport : public http::Transport {
+ public:
+ Transport();
+ virtual ~Transport();
+
+ // Server handler callback signature.
+ typedef base::Callback<void(const ServerRequest&, ServerResponse*)>
+ HandlerCallback;
+
+ // This method allows the test code to provide a callback to handle requests
+ // for specific URL/HTTP-verb combination. When a specific |method| request
+ // is made on the given |url|, the |handler| will be invoked and all the
+ // request data will be filled in the |ServerRequest| parameter. Any server
+ // response should be returned through the |ServerResponse| parameter.
+ // Either |method| or |url| (or both) can be specified as "*" to handle
+ // any requests. So, ("http://localhost","*") will handle any request type
+ // on that URL and ("*","GET") will handle any GET requests.
+ // The lookup starts with the most specific data pair to the catch-all (*,*).
+ void AddHandler(const std::string& url, const std::string& method,
+ const HandlerCallback& handler);
+ // Simple version of AddHandler. AddSimpleReplyHandler just returns the
+ // specified text response of given MIME type.
+ void AddSimpleReplyHandler(const std::string& url,
+ const std::string& method,
+ int status_code,
+ const std::string& reply_text,
+ const std::string& mime_type);
+ // Retrieve a handler for specific |url| and request |method|.
+ HandlerCallback GetHandler(const std::string& url,
+ const std::string& method) const;
+
+ // For tests that want to assert on the number of HTTP requests sent,
+ // these methods can be used to do just that.
+ int GetRequestCount() const { return request_count_; }
+ void ResetRequestCount() { request_count_ = 0; }
+
+ // Overload from http::Transport
+ virtual std::unique_ptr<http::Connection> CreateConnection(
+ std::shared_ptr<http::Transport> transport,
+ const std::string& url,
+ const std::string& method,
+ const HeaderList& headers,
+ const std::string& user_agent,
+ const std::string& referer,
+ ErrorPtr* error) override;
+
+ private:
+ // A list of user-supplied request handlers.
+ std::map<std::string, HandlerCallback> handlers_;
+ // Counter incremented each time a request is made.
+ int request_count_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(Transport);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// A base class for ServerRequest and ServerResponse. It provides common
+// functionality to work with request/response HTTP headers and data.
+///////////////////////////////////////////////////////////////////////////////
+class ServerRequestResponseBase {
+ public:
+ ServerRequestResponseBase() = default;
+
+ // Add/retrieve request/response body data.
+ void AddData(const void* data, size_t data_size);
+ const std::vector<unsigned char>& GetData() const { return data_; }
+ std::string GetDataAsString() const;
+ std::unique_ptr<base::DictionaryValue> GetDataAsJson() const;
+
+ // Add/retrieve request/response HTTP headers.
+ void AddHeaders(const HeaderList& headers);
+ std::string GetHeader(const std::string& header_name) const;
+ const std::map<std::string, std::string>& GetHeaders() const {
+ return headers_;
+ }
+
+ protected:
+ // Data buffer.
+ std::vector<unsigned char> data_;
+ // Header map.
+ std::map<std::string, std::string> headers_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ServerRequestResponseBase);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// A container class that encapsulates all the HTTP server request information.
+///////////////////////////////////////////////////////////////////////////////
+class ServerRequest : public ServerRequestResponseBase {
+ public:
+ ServerRequest(const std::string& url, const std::string& method);
+
+ // Get the actual request URL. Does not include the query string or fragment.
+ const std::string& GetURL() const { return url_; }
+ // Get the request method.
+ const std::string& GetMethod() const { return method_; }
+ // Get the POST/GET request parameters. These are parsed query string
+ // parameters from the URL. In addition, for POST requests with
+ // application/x-www-form-urlencoded content type, the request body is also
+ // parsed and individual fields can be accessed through this method.
+ std::string GetFormField(const std::string& field_name) const;
+
+ private:
+ // Request URL (without query string or URL fragment).
+ std::string url_;
+ // Request method
+ std::string method_;
+ // List of available request data form fields.
+ mutable std::map<std::string, std::string> form_fields_;
+ // Flag used on first request to GetFormField to parse the body of HTTP POST
+ // request with application/x-www-form-urlencoded content.
+ mutable bool form_fields_parsed_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(ServerRequest);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// A container class that encapsulates all the HTTP server response information.
+// The request handler will use this class to provide a response to the caller.
+// Call the Reply() or the appropriate ReplyNNN() specialization to provide
+// the response data. Additional calls to AddHeaders() can be made to provide
+// custom response headers. The Reply-methods will already provide the
+// following response headers:
+// Content-Length
+// Content-Type
+///////////////////////////////////////////////////////////////////////////////
+class ServerResponse : public ServerRequestResponseBase {
+ public:
+ ServerResponse() = default;
+
+ // Generic reply method.
+ void Reply(int status_code, const void* data, size_t data_size,
+ const char* mime_type);
+ // Reply with text body.
+ void ReplyText(int status_code, const std::string& text,
+ const char* mime_type);
+ // Reply with JSON object. The content type will be "application/json".
+ void ReplyJson(int status_code, const base::Value* json);
+ // Special form for JSON response for simple objects that have a flat
+ // list of key-value pairs of string type.
+ void ReplyJson(int status_code, const FormFieldList& fields);
+
+ // Specialized overload to send the binary data as an array of simple
+ // data elements. Only trivial data types (scalars, POD structures, etc)
+ // can be used.
+ template<typename T>
+ void Reply(int status_code, const std::vector<T>& data,
+ const char* mime_type) {
+ // Make sure T doesn't have virtual functions, custom constructors, etc.
+ static_assert(std::is_trivial<T>::value, "Only simple data is supported");
+ Reply(status_code, data.data(), data.size() * sizeof(T), mime_type);
+ }
+
+ // Specialized overload to send the binary data.
+ // Only trivial data types (scalars, POD structures, etc) can be used.
+ template<typename T>
+ void Reply(int status_code, const T& data, const char* mime_type) {
+ // Make sure T doesn't have virtual functions, custom constructors, etc.
+ static_assert(std::is_trivial<T>::value, "Only simple data is supported");
+ Reply(status_code, &data, sizeof(T), mime_type);
+ }
+
+ // For handlers that want to simulate versions of HTTP protocol other
+ // than HTTP/1.1, call this method with the custom version string,
+ // for example "HTTP/1.0".
+ void SetProtocolVersion(const std::string& protocol_version) {
+ protocol_version_ = protocol_version;
+ }
+
+ protected:
+ // These methods are helpers to implement corresponding functionality
+ // of fake::Connection.
+ friend class Connection;
+ // Helper for fake::Connection::GetResponseStatusCode().
+ int GetStatusCode() const { return status_code_; }
+ // Helper for fake::Connection::GetResponseStatusText().
+ std::string GetStatusText() const;
+ // Helper for fake::Connection::GetProtocolVersion().
+ std::string GetProtocolVersion() const { return protocol_version_; }
+
+ private:
+ int status_code_ = 0;
+ std::string protocol_version_ = "HTTP/1.1";
+
+ DISALLOW_COPY_AND_ASSIGN(ServerResponse);
+};
+
+} // namespace fake
+} // namespace http
+} // namespace buffet
+
+#endif // BUFFET_HTTP_TRANSPORT_FAKE_H_
diff --git a/buffet/http_utils.cc b/buffet/http_utils.cc
new file mode 100644
index 0000000..f7f1190
--- /dev/null
+++ b/buffet/http_utils.cc
@@ -0,0 +1,167 @@
+// Copyright 2014 The Chromium OS 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 "buffet/http_utils.h"
+
+#include <algorithm>
+
+#include <base/json/json_reader.h>
+#include <base/json/json_writer.h>
+#include <base/values.h>
+
+#include "buffet/mime_utils.h"
+#include "buffet/data_encoding.h"
+
+namespace buffet {
+namespace http {
+
+const char kErrorDomainJSON[] = "json_parser";
+
+std::unique_ptr<Response> Get(const std::string& url,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ ErrorPtr* error) {
+ return SendRequest(request_type::kGet, url, nullptr, 0, nullptr,
+ headers, transport, error);
+}
+
+std::string GetAsString(const std::string& url,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ ErrorPtr* error) {
+ auto resp = Get(url, headers, transport, error);
+ return resp ? resp->GetDataAsString() : std::string();
+}
+
+std::unique_ptr<Response> Head(const std::string& url,
+ std::shared_ptr<Transport> transport,
+ ErrorPtr* error) {
+ Request request(url, request_type::kHead, transport);
+ return request.GetResponse(error);
+}
+
+std::unique_ptr<Response> PostText(const std::string& url,
+ const char* data,
+ const char* mime_type,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ ErrorPtr* error) {
+ if (mime_type == nullptr) {
+ mime_type = mime::application::kWwwFormUrlEncoded;
+ }
+
+ return PostBinary(url, data, strlen(data), mime_type, headers, transport,
+ error);
+}
+
+std::unique_ptr<Response> SendRequest(const char * method,
+ const std::string& url,
+ const void* data,
+ size_t data_size,
+ const char* mime_type,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ ErrorPtr* error) {
+ Request request(url, method, transport);
+ request.AddHeaders(headers);
+ if (data_size > 0) {
+ if (mime_type == nullptr) {
+ mime_type = mime::application::kOctet_stream;
+ }
+ request.SetContentType(mime_type);
+ if (!request.AddRequestBody(data, data_size, error))
+ return std::unique_ptr<Response>();
+ }
+ return request.GetResponse(error);
+}
+
+std::unique_ptr<Response> PostBinary(const std::string & url, const void* data,
+ size_t data_size, const char* mime_type,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ ErrorPtr* error) {
+ return SendRequest(request_type::kPost, url,
+ data, data_size, mime_type, headers, transport, error);
+}
+
+std::unique_ptr<Response> PostFormData(const std::string& url,
+ const FormFieldList& data,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ ErrorPtr* error) {
+ std::string encoded_data = data_encoding::WebParamsEncode(data);
+ return PostBinary(url, encoded_data.c_str(), encoded_data.size(),
+ mime::application::kWwwFormUrlEncoded,
+ headers, transport, error);
+}
+
+
+std::unique_ptr<Response> PostJson(const std::string& url,
+ const base::Value* json,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ ErrorPtr* error) {
+ std::string data;
+ if (json)
+ base::JSONWriter::Write(json, &data);
+ std::string mime_type = mime::AppendParameter(mime::application::kJson,
+ mime::parameters::kCharset,
+ "utf-8");
+ return PostBinary(url, data.c_str(), data.size(),
+ mime_type.c_str(), headers, transport, error);
+}
+
+std::unique_ptr<Response> PatchJson(const std::string& url,
+ const base::Value* json,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ ErrorPtr* error) {
+ std::string data;
+ if (json)
+ base::JSONWriter::Write(json, &data);
+ std::string mime_type = mime::AppendParameter(mime::application::kJson,
+ mime::parameters::kCharset,
+ "utf-8");
+ return SendRequest(request_type::kPatch, url, data.c_str(), data.size(),
+ mime_type.c_str(), headers, transport, error);
+}
+
+std::unique_ptr<base::DictionaryValue> ParseJsonResponse(
+ const Response* response, int* status_code, ErrorPtr* error) {
+ if (!response)
+ return std::unique_ptr<base::DictionaryValue>();
+
+ if (status_code)
+ *status_code = response->GetStatusCode();
+
+ // Make sure we have a correct content type. Do not try to parse
+ // binary files, or HTML output. Limit to application/json and text/plain.
+ auto content_type = mime::RemoveParameters(response->GetContentType());
+ if (content_type != mime::application::kJson &&
+ content_type != mime::text::kPlain) {
+ Error::AddTo(error, kErrorDomainJSON, "non_json_content_type",
+ "Unexpected response content type: " + content_type);
+ return std::unique_ptr<base::DictionaryValue>();
+ }
+
+ std::string json = response->GetDataAsString();
+ std::string error_message;
+ base::Value* value = base::JSONReader::ReadAndReturnError(
+ json, base::JSON_PARSE_RFC, nullptr, &error_message);
+ if (!value) {
+ Error::AddTo(error, kErrorDomainJSON, "json_parse_error", error_message);
+ return std::unique_ptr<base::DictionaryValue>();
+ }
+ base::DictionaryValue* dict_value = nullptr;
+ if (!value->GetAsDictionary(&dict_value)) {
+ delete value;
+ Error::AddTo(error, kErrorDomainJSON, "json_object_error",
+ "Response is not a valid JSON object");
+ return std::unique_ptr<base::DictionaryValue>();
+ }
+ return std::unique_ptr<base::DictionaryValue>(dict_value);
+}
+
+} // namespace http
+} // namespace buffet
diff --git a/buffet/http_utils.h b/buffet/http_utils.h
new file mode 100644
index 0000000..20e255b
--- /dev/null
+++ b/buffet/http_utils.h
@@ -0,0 +1,181 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_HTTP_UTILS_H_
+#define BUFFET_HTTP_UTILS_H_
+
+#include "buffet/error.h"
+#include "buffet/http_request.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace base {
+ class Value;
+ class DictionaryValue;
+} // namespace base
+
+namespace buffet {
+namespace http {
+
+extern const char kErrorDomainJSON[];
+
+typedef std::vector<std::pair<std::string, std::string>> FormFieldList;
+
+////////////////////////////////////////////////////////////////////////////////
+// The following are simple utility helper functions for common HTTP operations
+// that use http::Request object behind the scenes and set it up accordingly.
+//
+// For more advanced functionality you need to use Request/Response objects
+// directly.
+////////////////////////////////////////////////////////////////////////////////
+
+// Performs a generic HTTP request with binary data. Success status,
+// returned data and additional information (such as returned HTTP headers)
+// can be obtained from the returned Response object.
+// If data MIME type is not specified, "application/octet-stream" is assumed.
+std::unique_ptr<Response> SendRequest(
+ const char* method, const std::string& url,
+ const void* data, size_t data_size, const char* mime_type,
+ const HeaderList& headers, std::shared_ptr<Transport> transport,
+ ErrorPtr* error);
+
+// Performs a simple GET request and returns the data as a string.
+std::string GetAsString(const std::string& url, const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ ErrorPtr* error);
+inline std::string GetAsString(const std::string& url,
+ std::shared_ptr<Transport> transport,
+ ErrorPtr* error) {
+ return GetAsString(url, HeaderList(), transport, error);
+}
+
+// Performs a GET request. Success status, returned data and additional
+// information (such as returned HTTP headers) can be obtained from
+// the returned Response object.
+std::unique_ptr<Response> Get(const std::string& url,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ ErrorPtr* error);
+inline std::unique_ptr<Response> Get(
+ const std::string& url, std::shared_ptr<Transport> transport,
+ ErrorPtr* error) {
+ return Get(url, HeaderList(), transport, error);
+}
+
+// Performs a HEAD request. Success status and additional
+// information (such as returned HTTP headers) can be obtained from
+// the returned Response object.
+std::unique_ptr<Response> Head(const std::string& url,
+ std::shared_ptr<Transport> transport,
+ ErrorPtr* error);
+
+// Performs a POST request with binary data. Success status, returned data
+// and additional information (such as returned HTTP headers) can be obtained
+// from the returned Response object.
+// If data MIME type is not specified, "application/octet-stream" is assumed
+std::unique_ptr<Response> PostBinary(const std::string& url,
+ const void* data,
+ size_t data_size,
+ const char* mime_type,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ ErrorPtr* error);
+
+inline std::unique_ptr<Response> PostBinary(
+ const std::string& url, const void* data, size_t data_size,
+ const char* mime_type, std::shared_ptr<Transport> transport,
+ ErrorPtr* error) {
+ return PostBinary(url, data, data_size, mime_type, HeaderList(), transport,
+ error);
+}
+
+inline std::unique_ptr<Response> PostBinary(
+ const std::string& url, const void* data, size_t data_size,
+ std::shared_ptr<Transport> transport, ErrorPtr* error) {
+ return PostBinary(url, data, data_size, nullptr, transport, error);
+}
+
+// Performs a POST request with text data. Success status, returned data
+// and additional information (such as returned HTTP headers) can be obtained
+// from the returned Response object.
+// If data MIME type is not specified, "application/x-www-form-urlencoded"
+// is assumed.
+std::unique_ptr<Response> PostText(const std::string& url,
+ const char* data,
+ const char* mime_type,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ ErrorPtr* error);
+
+inline std::unique_ptr<Response> PostText(
+ const std::string& url, const char* data, const char* mime_type,
+ std::shared_ptr<Transport> transport, ErrorPtr* error) {
+ return PostText(url, data, mime_type, HeaderList(), transport, error);
+}
+
+inline std::unique_ptr<Response> PostText(
+ const std::string& url, const char* data,
+ std::shared_ptr<Transport> transport, ErrorPtr* error) {
+ return PostText(url, data, nullptr, transport, error);
+}
+
+// Performs a POST request with form data. Success status, returned data
+// and additional information (such as returned HTTP headers) can be obtained
+// from the returned Response object. The form data is a list of key/value
+// pairs. The data is posed as "application/x-www-form-urlencoded".
+std::unique_ptr<Response> PostFormData(
+ const std::string& url, const FormFieldList& data,
+ const HeaderList& headers, std::shared_ptr<Transport> transport,
+ ErrorPtr* error);
+
+inline std::unique_ptr<Response> PostFormData(
+ const std::string& url, const FormFieldList& data,
+ std::shared_ptr<Transport> transport, ErrorPtr* error) {
+ return PostFormData(url, data, HeaderList(), transport, error);
+}
+
+// Performs a POST request with JSON data. Success status, returned data
+// and additional information (such as returned HTTP headers) can be obtained
+// from the returned Response object. If a JSON response is expected,
+// use ParseJsonResponse() method on the returned Response object.
+std::unique_ptr<Response> PostJson(const std::string& url,
+ const base::Value* json,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ ErrorPtr* error);
+
+inline std::unique_ptr<Response> PostJson(
+ const std::string& url, const base::Value* json,
+ std::shared_ptr<Transport> transport, ErrorPtr* error) {
+ return PostJson(url, json, HeaderList(), transport, error);
+}
+
+// Performs a PATCH request with JSON data. Success status, returned data
+// and additional information (such as returned HTTP headers) can be obtained
+// from the returned Response object. If a JSON response is expected,
+// use ParseJsonResponse() method on the returned Response object.
+std::unique_ptr<Response> PatchJson(const std::string& url,
+ const base::Value* json,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ ErrorPtr* error);
+
+inline std::unique_ptr<Response> PatchJson(
+ const std::string& url, const base::Value* json,
+ std::shared_ptr<Transport> transport, ErrorPtr* error) {
+ return PatchJson(url, json, HeaderList(), transport, error);
+}
+
+// Given an http::Response object, parse the body data into Json object.
+// Returns null if failed. Optional |error| can be passed in to
+// get the extended error information as to why the parse failed.
+std::unique_ptr<base::DictionaryValue> ParseJsonResponse(
+ const Response* response, int* status_code, ErrorPtr* error);
+
+} // namespace http
+} // namespace buffet
+
+#endif // BUFFET_HTTP_UTILS_H_
diff --git a/buffet/http_utils_unittest.cc b/buffet/http_utils_unittest.cc
new file mode 100644
index 0000000..fc86d31
--- /dev/null
+++ b/buffet/http_utils_unittest.cc
@@ -0,0 +1,338 @@
+// Copyright 2014 The Chromium OS 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 <string>
+#include <vector>
+
+#include <base/values.h>
+#include <gtest/gtest.h>
+
+#include "buffet/bind_lambda.h"
+#include "buffet/http_utils.h"
+#include "buffet/http_transport_fake.h"
+#include "buffet/mime_utils.h"
+#include "buffet/string_utils.h"
+#include "buffet/url_utils.h"
+
+using namespace buffet; // NOLINT(build/namespaces)
+using namespace buffet::http; // NOLINT(build/namespaces)
+
+static const char kFakeUrl[] = "http://localhost";
+static const char kEchoUrl[] = "http://localhost/echo";
+static const char kMethodEchoUrl[] = "http://localhost/echo/method";
+
+///////////////////// Generic helper request handlers /////////////////////////
+// Returns the request data back with the same content type.
+static void EchoDataHandler(const fake::ServerRequest& request,
+ fake::ServerResponse* response) {
+ response->Reply(status_code::Ok, request.GetData(),
+ request.GetHeader(request_header::kContentType).c_str());
+};
+
+// Returns the request method as a plain text response.
+static void EchoMethodHandler(const fake::ServerRequest& request,
+ fake::ServerResponse* response) {
+ response->ReplyText(status_code::Ok, request.GetMethod(), mime::text::kPlain);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+TEST(HttpUtils, SendRequest_BinaryData) {
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ transport->AddHandler(kEchoUrl, request_type::kPost,
+ base::Bind(EchoDataHandler));
+
+ // Test binary data round-tripping.
+ std::vector<unsigned char> custom_data({0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F});
+ auto response = http::SendRequest(request_type::kPost, kEchoUrl,
+ custom_data.data(), custom_data.size(),
+ mime::application::kOctet_stream,
+ HeaderList(), transport, nullptr);
+ EXPECT_TRUE(response->IsSuccessful());
+ EXPECT_EQ(mime::application::kOctet_stream, response->GetContentType());
+ EXPECT_EQ(custom_data.size(), response->GetData().size());
+ EXPECT_EQ(custom_data, response->GetData());
+}
+
+TEST(HttpUtils, SendRequest_Post) {
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler));
+
+ // Test binary data round-tripping.
+ std::vector<unsigned char> custom_data({0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F});
+
+ // Check the correct HTTP method used.
+ auto response = http::SendRequest(request_type::kPost, kMethodEchoUrl,
+ custom_data.data(), custom_data.size(),
+ mime::application::kOctet_stream,
+ HeaderList(), transport, nullptr);
+ EXPECT_TRUE(response->IsSuccessful());
+ EXPECT_EQ(mime::text::kPlain, response->GetContentType());
+ EXPECT_EQ(request_type::kPost, response->GetDataAsString());
+}
+
+TEST(HttpUtils, SendRequest_Get) {
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler));
+
+ auto response = http::SendRequest(request_type::kGet, kMethodEchoUrl,
+ nullptr, 0, nullptr,
+ HeaderList(), transport, nullptr);
+ EXPECT_TRUE(response->IsSuccessful());
+ EXPECT_EQ(mime::text::kPlain, response->GetContentType());
+ EXPECT_EQ(request_type::kGet, response->GetDataAsString());
+}
+
+TEST(HttpUtils, SendRequest_Put) {
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler));
+
+ auto response = http::SendRequest(request_type::kPut, kMethodEchoUrl,
+ nullptr, 0, nullptr,
+ HeaderList(), transport, nullptr);
+ EXPECT_TRUE(response->IsSuccessful());
+ EXPECT_EQ(mime::text::kPlain, response->GetContentType());
+ EXPECT_EQ(request_type::kPut, response->GetDataAsString());
+}
+
+TEST(HttpUtils, SendRequest_NotFound) {
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ // Test failed response (URL not found).
+ auto response = http::SendRequest(request_type::kGet, "http://blah.com",
+ nullptr, 0, nullptr,
+ HeaderList(), transport, nullptr);
+ EXPECT_FALSE(response->IsSuccessful());
+ EXPECT_EQ(status_code::NotFound, response->GetStatusCode());
+}
+
+TEST(HttpUtils, SendRequest_Headers) {
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+
+ static const char json_echo_url[] = "http://localhost/echo/json";
+ auto JsonEchoHandler = [](const fake::ServerRequest& request,
+ fake::ServerResponse* response) {
+ base::DictionaryValue json;
+ json.SetString("method", request.GetMethod());
+ json.SetString("data", request.GetDataAsString());
+ for (auto&& pair : request.GetHeaders()) {
+ json.SetString("header." + pair.first, pair.second);
+ }
+ response->ReplyJson(status_code::Ok, &json);
+ };
+ transport->AddHandler(json_echo_url, "*",
+ base::Bind(JsonEchoHandler));
+ auto response = http::SendRequest(
+ request_type::kPost, json_echo_url, "abcd", 4,
+ mime::application::kOctet_stream, {
+ {request_header::kCookie, "flavor=vanilla"},
+ {request_header::kIfMatch, "*"},
+ }, transport, nullptr);
+ EXPECT_TRUE(response->IsSuccessful());
+ EXPECT_EQ(mime::application::kJson,
+ mime::RemoveParameters(response->GetContentType()));
+ auto json = ParseJsonResponse(response.get(), nullptr, nullptr);
+ std::string value;
+ EXPECT_TRUE(json->GetString("method", &value));
+ EXPECT_EQ(request_type::kPost, value);
+ EXPECT_TRUE(json->GetString("data", &value));
+ EXPECT_EQ("abcd", value);
+ EXPECT_TRUE(json->GetString("header.Cookie", &value));
+ EXPECT_EQ("flavor=vanilla", value);
+ EXPECT_TRUE(json->GetString("header.Content-Type", &value));
+ EXPECT_EQ(mime::application::kOctet_stream, value);
+ EXPECT_TRUE(json->GetString("header.Content-Length", &value));
+ EXPECT_EQ("4", value);
+ EXPECT_TRUE(json->GetString("header.If-Match", &value));
+ EXPECT_EQ("*", value);
+}
+
+TEST(HttpUtils, Get) {
+ // Sends back the "?test=..." portion of URL.
+ // So if we do GET "http://localhost?test=blah", this handler responds
+ // with "blah" as text/plain.
+ auto GetHandler = [](const fake::ServerRequest& request,
+ fake::ServerResponse* response) {
+ EXPECT_EQ(request_type::kGet, request.GetMethod());
+ EXPECT_EQ("0", request.GetHeader(request_header::kContentLength));
+ EXPECT_EQ("", request.GetHeader(request_header::kContentType));
+ response->ReplyText(status_code::Ok, request.GetFormField("test"),
+ mime::text::kPlain);
+ };
+
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ transport->AddHandler(kFakeUrl, request_type::kGet, base::Bind(GetHandler));
+ transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler));
+
+ // Make sure Get/GetAsString actually do the GET request
+ auto response = http::Get(kMethodEchoUrl, transport, nullptr);
+ EXPECT_TRUE(response->IsSuccessful());
+ EXPECT_EQ(mime::text::kPlain, response->GetContentType());
+ EXPECT_EQ(request_type::kGet, response->GetDataAsString());
+ EXPECT_EQ(request_type::kGet,
+ http::GetAsString(kMethodEchoUrl, transport, nullptr));
+
+ for (std::string data : {"blah", "some data", ""}) {
+ std::string url = url::AppendQueryParam(kFakeUrl, "test", data);
+ EXPECT_EQ(data, http::GetAsString(url, transport, nullptr));
+ }
+}
+
+TEST(HttpUtils, Head) {
+ auto HeadHandler = [](const fake::ServerRequest& request,
+ fake::ServerResponse* response) {
+ EXPECT_EQ(request_type::kHead, request.GetMethod());
+ EXPECT_EQ("0", request.GetHeader(request_header::kContentLength));
+ EXPECT_EQ("", request.GetHeader(request_header::kContentType));
+ response->ReplyText(status_code::Ok, "blah",
+ mime::text::kPlain);
+ };
+
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ transport->AddHandler(kFakeUrl, request_type::kHead, base::Bind(HeadHandler));
+
+ auto response = http::Head(kFakeUrl, transport, nullptr);
+ EXPECT_TRUE(response->IsSuccessful());
+ EXPECT_EQ(mime::text::kPlain, response->GetContentType());
+ EXPECT_EQ("", response->GetDataAsString()); // Must not have actual body.
+ EXPECT_EQ("4", response->GetHeader(request_header::kContentLength));
+}
+
+TEST(HttpUtils, PostBinary) {
+ auto Handler = [](const fake::ServerRequest& request,
+ fake::ServerResponse* response) {
+ EXPECT_EQ(request_type::kPost, request.GetMethod());
+ EXPECT_EQ("256", request.GetHeader(request_header::kContentLength));
+ EXPECT_EQ(mime::application::kOctet_stream,
+ request.GetHeader(request_header::kContentType));
+ auto&& data = request.GetData();
+ EXPECT_EQ(256, data.size());
+
+ // Sum up all the bytes.
+ int sum = std::accumulate(data.begin(), data.end(), 0);
+ EXPECT_EQ(32640, sum); // sum(i, i => [0, 255]) = 32640.
+ response->ReplyText(status_code::Ok, "", mime::text::kPlain);
+ };
+
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ transport->AddHandler(kFakeUrl, request_type::kPost, base::Bind(Handler));
+
+ /// Fill the data buffer with bytes from 0x00 to 0xFF.
+ std::vector<unsigned char> data(256);
+ unsigned char counter = 0xFF;
+ std::generate(data.begin(), data.end(), [&counter]() { return ++counter; });
+
+ auto response = http::PostBinary(kFakeUrl, data.data(), data.size(),
+ transport, nullptr);
+ EXPECT_TRUE(response->IsSuccessful());
+}
+
+TEST(HttpUtils, PostText) {
+ std::string fake_data = "Some data";
+ auto PostHandler = [fake_data](const fake::ServerRequest& request,
+ fake::ServerResponse* response) {
+ EXPECT_EQ(request_type::kPost, request.GetMethod());
+ EXPECT_EQ(fake_data.size(),
+ std::stoul(request.GetHeader(request_header::kContentLength)));
+ EXPECT_EQ(mime::text::kPlain,
+ request.GetHeader(request_header::kContentType));
+ response->ReplyText(status_code::Ok, request.GetDataAsString(),
+ mime::text::kPlain);
+ };
+
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ transport->AddHandler(kFakeUrl, request_type::kPost, base::Bind(PostHandler));
+
+ auto response = http::PostText(kFakeUrl, fake_data.c_str(),
+ mime::text::kPlain, transport, nullptr);
+ EXPECT_TRUE(response->IsSuccessful());
+ EXPECT_EQ(mime::text::kPlain, response->GetContentType());
+ EXPECT_EQ(fake_data, response->GetDataAsString());
+}
+
+TEST(HttpUtils, PostFormData) {
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ transport->AddHandler(kFakeUrl, request_type::kPost,
+ base::Bind(EchoDataHandler));
+
+ auto response = http::PostFormData(kFakeUrl, {
+ {"key", "value"},
+ {"field", "field value"},
+ }, transport, nullptr);
+ EXPECT_TRUE(response->IsSuccessful());
+ EXPECT_EQ(mime::application::kWwwFormUrlEncoded, response->GetContentType());
+ EXPECT_EQ("key=value&field=field+value", response->GetDataAsString());
+}
+
+TEST(HttpUtils, PostPatchJson) {
+ auto JsonHandler = [](const fake::ServerRequest& request,
+ fake::ServerResponse* response) {
+ auto mime_type = mime::RemoveParameters(
+ request.GetHeader(request_header::kContentType));
+ EXPECT_EQ(mime::application::kJson, mime_type);
+ response->ReplyJson(status_code::Ok, {
+ {"method", request.GetMethod()},
+ {"data", request.GetDataAsString()},
+ });
+ };
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ transport->AddHandler(kFakeUrl, "*", base::Bind(JsonHandler));
+
+ base::DictionaryValue json;
+ json.SetString("key1", "val1");
+ json.SetString("key2", "val2");
+ std::string value;
+
+ // Test POST
+ auto response = http::PostJson(kFakeUrl, &json, transport, nullptr);
+ auto resp_json = http::ParseJsonResponse(response.get(), nullptr, nullptr);
+ EXPECT_NE(nullptr, resp_json.get());
+ EXPECT_TRUE(resp_json->GetString("method", &value));
+ EXPECT_EQ(request_type::kPost, value);
+ EXPECT_TRUE(resp_json->GetString("data", &value));
+ EXPECT_EQ("{\"key1\":\"val1\",\"key2\":\"val2\"}", value);
+
+ // Test PATCH
+ response = http::PatchJson(kFakeUrl, &json, transport, nullptr);
+ resp_json = http::ParseJsonResponse(response.get(), nullptr, nullptr);
+ EXPECT_NE(nullptr, resp_json.get());
+ EXPECT_TRUE(resp_json->GetString("method", &value));
+ EXPECT_EQ(request_type::kPatch, value);
+ EXPECT_TRUE(resp_json->GetString("data", &value));
+ EXPECT_EQ("{\"key1\":\"val1\",\"key2\":\"val2\"}", value);
+}
+
+TEST(HttpUtils, ParseJsonResponse) {
+ auto JsonHandler = [](const fake::ServerRequest& request,
+ fake::ServerResponse* response) {
+ int status_code = std::stoi(request.GetFormField("code"));
+ response->ReplyJson(status_code, {{"data", request.GetFormField("value")}});
+ };
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ transport->AddHandler(kFakeUrl, request_type::kPost, base::Bind(JsonHandler));
+
+ // Test valid JSON responses (with success or error codes).
+ for (auto&& item : {"200;data", "400;wrong", "500;Internal Server error"}) {
+ auto pair = string_utils::SplitAtFirst(item, ';');
+ auto response = http::PostFormData(kFakeUrl, {
+ {"code", pair.first},
+ {"value", pair.second},
+ }, transport, nullptr);
+ int code = 0;
+ auto json = http::ParseJsonResponse(response.get(), &code, nullptr);
+ EXPECT_NE(nullptr, json.get());
+ std::string value;
+ EXPECT_TRUE(json->GetString("data", &value));
+ EXPECT_EQ(pair.first, std::to_string(code));
+ EXPECT_EQ(pair.second, value);
+ }
+
+ // Test invalid (non-JSON) response.
+ auto response = http::Get("http://bad.url", transport, nullptr);
+ EXPECT_EQ(status_code::NotFound, response->GetStatusCode());
+ EXPECT_EQ(mime::text::kHtml, response->GetContentType());
+ int code = 0;
+ auto json = http::ParseJsonResponse(response.get(), &code, nullptr);
+ EXPECT_EQ(nullptr, json.get());
+ EXPECT_EQ(status_code::NotFound, code);
+}
+
diff --git a/buffet/main.cc b/buffet/main.cc
new file mode 100644
index 0000000..43ba4ab
--- /dev/null
+++ b/buffet/main.cc
@@ -0,0 +1,141 @@
+// Copyright 2014 The Chromium OS 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 <string>
+
+#include <base/at_exit.h>
+#include <base/command_line.h>
+#include <base/file_util.h>
+#include <base/files/file_path.h>
+#include <base/logging.h>
+#include <base/message_loop/message_loop.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <dbus/bus.h>
+#include <sysexits.h>
+
+#include "buffet/async_event_sequencer.h"
+#include "buffet/dbus_constants.h"
+#include "buffet/exported_object_manager.h"
+#include "buffet/manager.h"
+
+using buffet::dbus_utils::AsyncEventSequencer;
+using buffet::dbus_utils::ExportedObjectManager;
+
+namespace {
+
+static const char kLogRoot[] = "logroot";
+static const char kHelp[] = "help";
+static const char kDefaultLogRoot[] = "/var/log";
+
+// The help message shown if help flag is passed to the program.
+static const char kHelpMessage[] = "\n"
+ "Available Switches: \n"
+ " --logroot=/path/to/logroot\n"
+ " Specifies parent directory to put buffet logs in.\n";
+
+// Returns |utime| as a string
+std::string GetTimeAsString(time_t utime) {
+ struct tm tm;
+ CHECK_EQ(localtime_r(&utime, &tm), &tm);
+ char str[16];
+ CHECK_EQ(strftime(str, sizeof(str), "%Y%m%d-%H%M%S", &tm), 15UL);
+ return std::string(str);
+}
+
+// Sets up a symlink to point to log file.
+void SetupLogSymlink(const std::string& symlink_path,
+ const std::string& log_path) {
+ base::DeleteFile(base::FilePath(symlink_path), true);
+ if (symlink(log_path.c_str(), symlink_path.c_str()) == -1) {
+ LOG(ERROR) << "Unable to create symlink " << symlink_path
+ << " pointing at " << log_path;
+ }
+}
+
+// Creates new log file based on timestamp in |log_root|/buffet.
+std::string SetupLogFile(const std::string& log_root) {
+ const auto log_symlink = log_root + "/buffet.log";
+ const auto logs_dir = log_root + "/buffet";
+ const auto log_path =
+ base::StringPrintf("%s/buffet.%s",
+ logs_dir.c_str(),
+ GetTimeAsString(::time(NULL)).c_str());
+ mkdir(logs_dir.c_str(), 0755);
+ SetupLogSymlink(log_symlink, log_path);
+ return log_symlink;
+}
+
+// Sets up logging for buffet.
+void SetupLogging(const std::string& log_root) {
+ const auto log_file = SetupLogFile(log_root);
+ logging::LoggingSettings settings;
+ settings.logging_dest = logging::LOG_TO_ALL;
+ settings.log_file = log_file.c_str();
+ settings.lock_log = logging::DONT_LOCK_LOG_FILE;
+ settings.delete_old = logging::APPEND_TO_OLD_LOG_FILE;
+ logging::InitLogging(settings);
+}
+
+void TakeServiceOwnership(scoped_refptr<dbus::Bus> bus, bool success) {
+ // Success should always be true since we've said that failures are
+ // fatal.
+ CHECK(success) << "Init of one or more objects has failed.";
+ CHECK(bus->RequestOwnershipAndBlock(buffet::dbus_constants::kServiceName,
+ dbus::Bus::REQUIRE_PRIMARY))
+ << "Unable to take ownership of " << buffet::dbus_constants::kServiceName;
+}
+
+void EnterMainLoop(base::MessageLoopForIO* message_loop,
+ scoped_refptr<dbus::Bus> bus) {
+ scoped_refptr<AsyncEventSequencer> sequencer(new AsyncEventSequencer());
+ ExportedObjectManager object_manager(
+ bus, dbus::ObjectPath(buffet::dbus_constants::kRootServicePath));
+ buffet::Manager manager(bus, object_manager.AsWeakPtr());
+ object_manager.Init(
+ sequencer->GetHandler("ObjectManager.Init() failed.", true));
+ manager.Init(sequencer->GetHandler("Manager.Init() failed.", true));
+ sequencer->OnAllTasksCompletedCall(
+ {base::Bind(&TakeServiceOwnership, bus)});
+ // Release our handle on the sequencer so that it gets deleted after
+ // both callbacks return.
+ sequencer = nullptr;
+ LOG(INFO) << "Entering mainloop.";
+ message_loop->Run();
+}
+
+} // namespace
+
+int main(int argc, char* argv[]) {
+ // Parse the args and check for extra args.
+ CommandLine::Init(argc, argv);
+ CommandLine* cl = CommandLine::ForCurrentProcess();
+
+ if (cl->HasSwitch(kHelp)) {
+ LOG(INFO) << kHelpMessage;
+ return EX_USAGE;
+ }
+
+ std::string log_root = std::string(kDefaultLogRoot);
+ if (cl->HasSwitch(kLogRoot)) {
+ log_root = cl->GetSwitchValueASCII(kLogRoot);
+ }
+
+ SetupLogging(log_root);
+
+ base::AtExitManager at_exit_manager;
+ base::MessageLoopForIO message_loop;
+
+ dbus::Bus::Options options;
+ // TODO(sosa): Should this be on the system bus?
+ options.bus_type = dbus::Bus::SYSTEM;
+ scoped_refptr<dbus::Bus> bus(new dbus::Bus(options));
+ CHECK(bus->Connect());
+ // Our top level objects expect the bus to exist in a connected state for
+ // the duration of their lifetimes.
+ EnterMainLoop(&message_loop, bus);
+ bus->ShutdownAndBlock();
+
+ return EX_OK;
+}
diff --git a/buffet/manager.cc b/buffet/manager.cc
new file mode 100644
index 0000000..3639803
--- /dev/null
+++ b/buffet/manager.cc
@@ -0,0 +1,301 @@
+// Copyright 2014 The Chromium OS 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 "buffet/manager.h"
+
+#include <map>
+#include <string>
+
+#include <base/bind.h>
+#include <base/bind_helpers.h>
+#include <base/json/json_writer.h>
+#include <dbus/bus.h>
+#include <dbus/object_path.h>
+#include <dbus/values_util.h>
+
+#include "buffet/async_event_sequencer.h"
+#include "buffet/dbus_constants.h"
+#include "buffet/dbus_utils.h"
+#include "buffet/error.h"
+#include "buffet/exported_object_manager.h"
+
+using buffet::dbus_utils::AsyncEventSequencer;
+using buffet::dbus_utils::GetBadArgsError;
+using buffet::dbus_utils::GetDBusError;
+
+namespace buffet {
+
+Manager::Manager(
+ scoped_refptr<dbus::Bus> bus,
+ base::WeakPtr<dbus_utils::ExportedObjectManager> object_manager)
+ : bus_(bus),
+ exported_object_(bus->GetExportedObject(
+ dbus::ObjectPath(dbus_constants::kManagerServicePath))),
+ object_manager_(object_manager) { }
+
+Manager::~Manager() {
+ object_manager_->ReleaseInterface(
+ dbus::ObjectPath(dbus_constants::kManagerServicePath),
+ dbus_constants::kManagerInterface);
+ // Prevent the properties object from making calls to the exported object.
+ properties_.reset(nullptr);
+ // Unregister ourselves from the Bus. This prevents the bus from calling
+ // our callbacks in between the Manager's death and the bus unregistering
+ // our exported object on shutdown. Unretained makes no promises of memory
+ // management.
+ exported_object_->Unregister();
+ exported_object_ = nullptr;
+}
+
+void Manager::Init(const OnInitFinish& cb) {
+ scoped_refptr<AsyncEventSequencer> sequencer(
+ new AsyncEventSequencer());
+ exported_object_->ExportMethod(
+ dbus_constants::kManagerInterface,
+ dbus_constants::kManagerCheckDeviceRegistered,
+ dbus_utils::GetExportableDBusMethod(
+ base::Bind(&Manager::HandleCheckDeviceRegistered,
+ base::Unretained(this))),
+ sequencer->GetExportHandler(
+ dbus_constants::kManagerInterface,
+ dbus_constants::kManagerCheckDeviceRegistered,
+ "Failed exporting CheckDeviceRegistered method",
+ true));
+ exported_object_->ExportMethod(
+ dbus_constants::kManagerInterface,
+ dbus_constants::kManagerGetDeviceInfo,
+ dbus_utils::GetExportableDBusMethod(
+ base::Bind(&Manager::HandleGetDeviceInfo,
+ base::Unretained(this))),
+ sequencer->GetExportHandler(
+ dbus_constants::kManagerInterface,
+ dbus_constants::kManagerGetDeviceInfo,
+ "Failed exporting GetDeviceInfo method",
+ true));
+ exported_object_->ExportMethod(
+ dbus_constants::kManagerInterface,
+ dbus_constants::kManagerStartRegisterDevice,
+ dbus_utils::GetExportableDBusMethod(
+ base::Bind(&Manager::HandleStartRegisterDevice,
+ base::Unretained(this))),
+ sequencer->GetExportHandler(
+ dbus_constants::kManagerInterface,
+ dbus_constants::kManagerStartRegisterDevice,
+ "Failed exporting StartRegisterDevice method",
+ true));
+ exported_object_->ExportMethod(
+ dbus_constants::kManagerInterface,
+ dbus_constants::kManagerFinishRegisterDevice,
+ dbus_utils::GetExportableDBusMethod(
+ base::Bind(&Manager::HandleFinishRegisterDevice,
+ base::Unretained(this))),
+ sequencer->GetExportHandler(
+ dbus_constants::kManagerInterface,
+ dbus_constants::kManagerFinishRegisterDevice,
+ "Failed exporting FinishRegisterDevice method",
+ true));
+ exported_object_->ExportMethod(
+ dbus_constants::kManagerInterface,
+ dbus_constants::kManagerUpdateStateMethod,
+ dbus_utils::GetExportableDBusMethod(
+ base::Bind(&Manager::HandleUpdateState,
+ base::Unretained(this))),
+ sequencer->GetExportHandler(
+ dbus_constants::kManagerInterface,
+ dbus_constants::kManagerUpdateStateMethod,
+ "Failed exporting UpdateState method",
+ true));
+ exported_object_->ExportMethod(
+ dbus_constants::kManagerInterface, dbus_constants::kManagerTestMethod,
+ dbus_utils::GetExportableDBusMethod(
+ base::Bind(&Manager::HandleTestMethod, base::Unretained(this))),
+ sequencer->GetExportHandler(
+ dbus_constants::kManagerInterface, dbus_constants::kManagerTestMethod,
+ "Failed exporting TestMethod method",
+ true));
+ properties_.reset(new Properties(bus_));
+ // TODO(wiley): Initialize all properties appropriately before claiming
+ // the properties interface.
+ properties_->state_.SetValue("{}");
+ properties_->Init(
+ sequencer->GetHandler("Manager properties export failed.", true));
+ auto claim_interface_task = sequencer->WrapCompletionTask(
+ base::Bind(&dbus_utils::ExportedObjectManager::ClaimInterface,
+ object_manager_->AsWeakPtr(),
+ dbus::ObjectPath(dbus_constants::kManagerServicePath),
+ dbus_constants::kManagerInterface,
+ properties_->GetPropertyWriter(
+ dbus_constants::kManagerInterface)));
+ sequencer->OnAllTasksCompletedCall({claim_interface_task, cb});
+ device_info_.Load();
+}
+
+scoped_ptr<dbus::Response> Manager::HandleCheckDeviceRegistered(
+ dbus::MethodCall* method_call) {
+ // Read the parameters to the method.
+ dbus::MessageReader reader(method_call);
+ if (reader.HasMoreData()) {
+ return GetBadArgsError(method_call,
+ "Too many parameters to CheckDeviceRegistered");
+ }
+
+ LOG(INFO) << "Received call to Manager.CheckDeviceRegistered()";
+
+ buffet::ErrorPtr error;
+ bool registered = device_info_.CheckRegistration(&error);
+ // If it fails due to any reason other than 'device not registered',
+ // treat it as a real error and report it to the caller.
+ if (!registered &&
+ !error->HasError(kErrorDomainGCD, "device_not_registered")) {
+ return GetDBusError(method_call, error.get());
+ }
+
+ std::string device_id;
+ if (registered) {
+ device_id = device_info_.GetDeviceId(&error);
+ if (device_id.empty())
+ return GetDBusError(method_call, error.get());
+ }
+ // Send back our response.
+ scoped_ptr<dbus::Response> response(
+ dbus::Response::FromMethodCall(method_call));
+ dbus::MessageWriter writer(response.get());
+ writer.AppendString(device_id);
+ return response.Pass();
+}
+
+scoped_ptr<dbus::Response> Manager::HandleGetDeviceInfo(
+ dbus::MethodCall* method_call) {
+ // Read the parameters to the method.
+ dbus::MessageReader reader(method_call);
+ if (reader.HasMoreData()) {
+ return GetBadArgsError(method_call,
+ "Too many parameters to GetDeviceInfo");
+ }
+
+ LOG(INFO) << "Received call to Manager.GetDeviceInfo()";
+
+ std::string device_info_str;
+ buffet::ErrorPtr error;
+ auto device_info = device_info_.GetDeviceInfo(&error);
+ if (!device_info)
+ return GetDBusError(method_call, error.get());
+
+ base::JSONWriter::Write(device_info.get(), &device_info_str);
+
+ // Send back our response.
+ scoped_ptr<dbus::Response> response(
+ dbus::Response::FromMethodCall(method_call));
+ dbus::MessageWriter writer(response.get());
+ writer.AppendString(device_info_str);
+ return response.Pass();
+}
+
+scoped_ptr<dbus::Response> Manager::HandleStartRegisterDevice(
+ dbus::MethodCall* method_call) {
+ // Read the parameters to the method.
+ dbus::MessageReader reader(method_call);
+ if (!reader.HasMoreData()) {
+ return GetBadArgsError(method_call, "No parameters to StartRegisterDevice");
+ }
+
+ dbus::MessageReader array_reader(nullptr);
+ if (!reader.PopArray(&array_reader))
+ return GetBadArgsError(method_call, "Failed to read the parameter array");
+ std::map<std::string, std::shared_ptr<base::Value>> params;
+ while (array_reader.HasMoreData()) {
+ dbus::MessageReader dict_entry_reader(nullptr);
+ if (!array_reader.PopDictEntry(&dict_entry_reader))
+ return GetBadArgsError(method_call, "Failed to get a call parameter");
+ std::string key;
+ if (!dict_entry_reader.PopString(&key))
+ return GetBadArgsError(method_call, "Failed to read parameter key");
+ base::Value* value = dbus::PopDataAsValue(&dict_entry_reader);
+ if (!value)
+ return GetBadArgsError(method_call, "Failed to read parameter value");
+ params.insert(std::make_pair(key, std::shared_ptr<base::Value>(value)));
+ }
+ if (reader.HasMoreData())
+ return GetBadArgsError(method_call,
+ "Too many parameters to StartRegisterDevice");
+
+ LOG(INFO) << "Received call to Manager.StartRegisterDevice()";
+
+ buffet::ErrorPtr error;
+ std::string id = device_info_.StartRegistration(params, &error);
+ if (id.empty())
+ return GetDBusError(method_call, error.get());
+
+ // Send back our response.
+ scoped_ptr<dbus::Response> response(
+ dbus::Response::FromMethodCall(method_call));
+ dbus::MessageWriter writer(response.get());
+ writer.AppendString(id);
+ return response.Pass();
+}
+
+scoped_ptr<dbus::Response> Manager::HandleFinishRegisterDevice(
+ dbus::MethodCall* method_call) {
+ // Read the parameters to the method.
+ dbus::MessageReader reader(method_call);
+ if (!reader.HasMoreData()) {
+ return GetBadArgsError(method_call,
+ "No parameters to FinishRegisterDevice");
+ }
+ std::string user_auth_code;
+ if (!reader.PopString(&user_auth_code)) {
+ return GetBadArgsError(method_call, "Failed to read UserAuthCode");
+ }
+ if (reader.HasMoreData()) {
+ return GetBadArgsError(method_call,
+ "Too many parameters to FinishRegisterDevice");
+ }
+
+ LOG(INFO) << "Received call to Manager.FinishRegisterDevice()";
+ buffet::ErrorPtr error;
+ if (!device_info_.FinishRegistration(user_auth_code, &error))
+ return GetDBusError(method_call, error.get());
+
+ std::string device_id = device_info_.GetDeviceId(&error);
+ if (device_id.empty())
+ return GetDBusError(method_call, error.get());
+
+ // Send back our response.
+ scoped_ptr<dbus::Response> response(
+ dbus::Response::FromMethodCall(method_call));
+ dbus::MessageWriter writer(response.get());
+ writer.AppendString(device_id);
+ return response.Pass();
+}
+
+scoped_ptr<dbus::Response> Manager::HandleUpdateState(
+ dbus::MethodCall *method_call) {
+ // Read the parameters to the method.
+ dbus::MessageReader reader(method_call);
+ if (!reader.HasMoreData()) {
+ return GetBadArgsError(method_call, "No parameters to UpdateState");
+ }
+ std::string json_state_fragment;
+ if (!reader.PopString(&json_state_fragment)) {
+ return GetBadArgsError(method_call, "Failed to read json_state_fragment");
+ }
+ if (reader.HasMoreData()) {
+ return GetBadArgsError(method_call, "Too many parameters to UpdateState");
+ }
+
+ LOG(INFO) << "Received call to Manager.UpdateState()";
+ // TODO(wiley): Merge json state blobs intelligently.
+ properties_->state_.SetValue(json_state_fragment);
+
+ // Send back our response.
+ return dbus::Response::FromMethodCall(method_call);
+}
+
+scoped_ptr<dbus::Response> Manager::HandleTestMethod(
+ dbus::MethodCall* method_call) {
+ LOG(INFO) << "Received call to test method.";
+ return scoped_ptr<dbus::Response>();
+}
+
+} // namespace buffet
diff --git a/buffet/manager.h b/buffet/manager.h
new file mode 100644
index 0000000..4498c50
--- /dev/null
+++ b/buffet/manager.h
@@ -0,0 +1,83 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_MANAGER_H_
+#define BUFFET_MANAGER_H_
+
+#include <memory>
+#include <string>
+
+#include <base/basictypes.h>
+#include <base/memory/scoped_ptr.h>
+#include <base/memory/weak_ptr.h>
+#include <base/values.h>
+#include <dbus/message.h>
+#include <dbus/object_path.h>
+
+#include "buffet/dbus_constants.h"
+#include "buffet/exported_property_set.h"
+#include "buffet/device_registration_info.h"
+
+namespace buffet {
+
+namespace dbus_utils {
+class ExportedObjectManager;
+} // namespace dbus_utils
+
+// The Manager is responsible for global state of Buffet. It exposes
+// interfaces which affect the entire device such as device registration and
+// device state.
+class Manager {
+ public:
+ typedef base::Callback<void(bool success)> OnInitFinish;
+
+ Manager(scoped_refptr<dbus::Bus> bus,
+ base::WeakPtr<dbus_utils::ExportedObjectManager> object_manager);
+ ~Manager();
+ void Init(const OnInitFinish& cb);
+
+ private:
+ struct Properties: public dbus_utils::ExportedPropertySet {
+ public:
+ dbus_utils::ExportedProperty<std::string> state_;
+ explicit Properties(dbus::Bus* bus)
+ : dbus_utils::ExportedPropertySet(
+ bus, dbus::ObjectPath(dbus_constants::kManagerServicePath)) {
+ RegisterProperty(dbus_constants::kManagerInterface, "State", &state_);
+ }
+ virtual ~Properties() {}
+ };
+
+ // Handles calls to org.chromium.Buffet.Manager.CheckDeviceRegistered().
+ scoped_ptr<dbus::Response> HandleCheckDeviceRegistered(
+ dbus::MethodCall* method_call);
+ // Handles calls to org.chromium.Buffet.Manager.GetDeviceInfo().
+ scoped_ptr<dbus::Response> HandleGetDeviceInfo(
+ dbus::MethodCall* method_call);
+ // Handles calls to org.chromium.Buffet.Manager.StartRegisterDevice().
+ scoped_ptr<dbus::Response> HandleStartRegisterDevice(
+ dbus::MethodCall* method_call);
+ // Handles calls to org.chromium.Buffet.Manager.FinishRegisterDevice().
+ scoped_ptr<dbus::Response> HandleFinishRegisterDevice(
+ dbus::MethodCall* method_call);
+ // Handles calls to org.chromium.Buffet.Manager.UpdateState().
+ scoped_ptr<dbus::Response> HandleUpdateState(
+ dbus::MethodCall* method_call);
+ // Handles calls to org.chromium.Buffet.Manager.Test()
+ scoped_ptr<::dbus::Response> HandleTestMethod(
+ ::dbus::MethodCall* method_call);
+
+ scoped_refptr<dbus::Bus> bus_;
+ dbus::ExportedObject* exported_object_; // weak; owned by the Bus object.
+ base::WeakPtr<dbus_utils::ExportedObjectManager> object_manager_;
+ scoped_ptr<Properties> properties_;
+
+ DeviceRegistrationInfo device_info_;
+
+ DISALLOW_COPY_AND_ASSIGN(Manager);
+};
+
+} // namespace buffet
+
+#endif // BUFFET_MANAGER_H_
diff --git a/buffet/map_utils.h b/buffet/map_utils.h
new file mode 100644
index 0000000..87d8d37
--- /dev/null
+++ b/buffet/map_utils.h
@@ -0,0 +1,47 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_MAP_UTILS_H_
+#define BUFFET_MAP_UTILS_H_
+
+#include <map>
+#include <utility>
+#include <vector>
+
+namespace buffet {
+
+// Given an STL map returns a vector containing all keys from the map
+template<typename T>
+std::vector<typename T::key_type> GetMapKeys(const T& map) {
+ std::vector<typename T::key_type> keys;
+ keys.reserve(map.size());
+ for (auto&& pair : map)
+ keys.push_back(pair.first);
+ return keys;
+}
+
+// Given an STL map returns a vector containing all values from the map
+template<typename T>
+std::vector<typename T::mapped_type> GetMapValues(const T& map) {
+ std::vector<typename T::mapped_type> values;
+ values.reserve(map.size());
+ for (auto&& pair : map)
+ values.push_back(pair.second);
+ return values;
+}
+
+// Given an STL map returns a vector of key-value pairs from the map
+template<typename T>
+std::vector<std::pair<typename T::key_type,
+ typename T::mapped_type>> MapToVector(const T& map) {
+ std::vector<std::pair<typename T::key_type, typename T::mapped_type>> vector;
+ vector.reserve(map.size());
+ for (auto&& pair : map)
+ vector.push_back(pair);
+ return vector;
+}
+
+} // namespace buffet
+
+#endif // BUFFET_MAP_UTILS_H_
diff --git a/buffet/mime_utils.cc b/buffet/mime_utils.cc
new file mode 100644
index 0000000..ca221dc
--- /dev/null
+++ b/buffet/mime_utils.cc
@@ -0,0 +1,156 @@
+// Copyright 2014 The Chromium OS 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 "buffet/mime_utils.h"
+
+#include <algorithm>
+#include <base/strings/string_util.h>
+
+#include "buffet/string_utils.h"
+
+namespace buffet {
+
+// ***************************************************************************
+// ******************************* MIME types ********************************
+// ***************************************************************************
+const char mime::types::kApplication[] = "application";
+const char mime::types::kAudio[] = "audio";
+const char mime::types::kImage[] = "image";
+const char mime::types::kMessage[] = "message";
+const char mime::types::kMultipart[] = "multipart";
+const char mime::types::kText[] = "text";
+const char mime::types::kVideo[] = "video";
+
+const char mime::parameters::kCharset[] = "charset";
+
+const char mime::image::kJpeg[] = "image/jpeg";
+const char mime::image::kPng[] = "image/png";
+const char mime::image::kBmp[] = "image/bmp";
+const char mime::image::kTiff[] = "image/tiff";
+const char mime::image::kGif[] = "image/gif";
+
+const char mime::text::kPlain[] = "text/plain";
+const char mime::text::kHtml[] = "text/html";
+const char mime::text::kXml[] = "text/xml";
+
+const char mime::application::kOctet_stream[] = "application/octet-stream";
+const char mime::application::kJson[] = "application/json";
+const char mime::application::kWwwFormUrlEncoded[] =
+ "application/x-www-form-urlencoded";
+
+// ***************************************************************************
+// **************************** Utility Functions ****************************
+// ***************************************************************************
+static std::string EncodeParam(const std::string& param) {
+ // If the string contains one of "tspecials" characters as
+ // specified in RFC 1521, enclose it in quotes.
+ if (param.find_first_of("()<>@,;:\\\"/[]?=") != std::string::npos) {
+ return '"' + param + '"';
+ }
+ return param;
+}
+
+static std::string DecodeParam(const std::string& param) {
+ if (param.size() > 1 && param.front() == '"' && param.back() == '"') {
+ return param.substr(1, param.size() - 2);
+ }
+ return param;
+}
+
+// ***************************************************************************
+// ******************** Main MIME manipulation functions *********************
+// ***************************************************************************
+
+bool mime::Split(const std::string& mime_string,
+ std::string* type, std::string* subtype,
+ mime::Parameters* parameters) {
+ std::vector<std::string> parts = string_utils::Split(mime_string, ';');
+ if (parts.empty())
+ return false;
+
+ if (!mime::Split(parts.front(), type, subtype))
+ return false;
+
+ if (parameters) {
+ parameters->clear();
+ parameters->reserve(parts.size() - 1);
+ for (size_t i = 1; i < parts.size(); i++) {
+ auto pair = string_utils::SplitAtFirst(parts[i], '=');
+ pair.second = DecodeParam(pair.second);
+ parameters->push_back(pair);
+ }
+ }
+ return true;
+}
+
+bool mime::Split(const std::string& mime_string,
+ std::string* type, std::string* subtype) {
+ std::string mime = mime::RemoveParameters(mime_string);
+ auto types = string_utils::SplitAtFirst(mime, '/');
+
+ if (type)
+ *type = types.first;
+
+ if (subtype)
+ *subtype = types.second;
+
+ return !types.first.empty() && !types.second.empty();
+}
+
+std::string mime::Combine(const std::string& type, const std::string& subtype,
+ const mime::Parameters& parameters) {
+ std::vector<std::string> parts;
+ parts.push_back(string_utils::Join('/', type, subtype));
+ for (auto&& pair : parameters) {
+ parts.push_back(string_utils::Join('=', pair.first,
+ EncodeParam(pair.second)));
+ }
+ return string_utils::Join("; ", parts);
+}
+
+std::string mime::GetType(const std::string& mime_string) {
+ std::string mime = mime::RemoveParameters(mime_string);
+ return string_utils::SplitAtFirst(mime, '/').first;
+}
+
+std::string mime::GetSubtype(const std::string& mime_string) {
+ std::string mime = mime::RemoveParameters(mime_string);
+ return string_utils::SplitAtFirst(mime, '/').second;
+}
+
+mime::Parameters mime::GetParameters(const std::string& mime_string) {
+ std::string type;
+ std::string subtype;
+ mime::Parameters parameters;
+
+ if (mime::Split(mime_string, &type, &subtype, ¶meters))
+ return std::move(parameters);
+
+ return mime::Parameters();
+}
+
+std::string mime::RemoveParameters(const std::string& mime_string) {
+ return string_utils::SplitAtFirst(mime_string, ';').first;
+}
+
+std::string mime::AppendParameter(const std::string& mime_string,
+ const std::string& paramName,
+ const std::string& paramValue) {
+ std::string mime(mime_string);
+ mime += "; ";
+ mime += string_utils::Join('=', paramName, EncodeParam(paramValue));
+ return mime;
+}
+
+std::string mime::GetParameterValue(const std::string& mime_string,
+ const std::string& paramName) {
+ mime::Parameters params = mime::GetParameters(mime_string);
+ for (auto&& pair : params) {
+ if (base::strcasecmp(pair.first.c_str(), paramName.c_str()) == 0)
+ return pair.second;
+ }
+ return std::string();
+}
+
+} // namespace buffet
diff --git a/buffet/mime_utils.h b/buffet/mime_utils.h
new file mode 100644
index 0000000..999b677
--- /dev/null
+++ b/buffet/mime_utils.h
@@ -0,0 +1,105 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_MIME_UTILS_H_
+#define BUFFET_MIME_UTILS_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/basictypes.h>
+
+namespace buffet {
+
+namespace mime {
+
+namespace types {
+ // Main MIME type categories
+ extern const char kApplication[]; // application
+ extern const char kAudio[]; // audio
+ extern const char kImage[]; // image
+ extern const char kMessage[]; // message
+ extern const char kMultipart[]; // multipart
+ extern const char kText[]; // test
+ extern const char kVideo[]; // video
+}
+
+namespace parameters {
+ // Common MIME parameters
+ extern const char kCharset[]; // charset=...
+}
+
+namespace image {
+ // Common image MIME types
+ extern const char kJpeg[]; // image/jpeg
+ extern const char kPng[]; // image/png
+ extern const char kBmp[]; // image/bmp
+ extern const char kTiff[]; // image/tiff
+ extern const char kGif[]; // image/gif
+}
+
+namespace text {
+ // Common text MIME types
+ extern const char kPlain[]; // text/plain
+ extern const char kHtml[]; // text/html
+ extern const char kXml[]; // text/xml
+}
+
+namespace application {
+ // Common application MIME types
+ extern const char kOctet_stream[]; // application/octet-stream
+ extern const char kJson[]; // application/json
+ extern const char kWwwFormUrlEncoded[]; // application/x-www-form-urlencoded
+}
+
+typedef std::vector<std::pair<std::string, std::string>> Parameters;
+
+// Combine a MIME type, subtype and parameters into a MIME string.
+// e.g. Combine("text", "plain", {{"charset", "utf-8"}}) will give:
+// "text/plain; charset=utf-8"
+std::string Combine(const std::string& type, const std::string& subtype,
+ const Parameters& parameters = {}) WARN_UNUSED_RESULT;
+
+// Splits a MIME string into type and subtype.
+// "text/plain;charset=utf-8" => ("text", "plain")
+bool Split(const std::string& mime_string,
+ std::string* type, std::string* subtype);
+
+// Splits a MIME string into type, subtype, and parameters.
+// "text/plain;charset=utf-8" => ("text", "plain", {{"charset","utf-8"}})
+bool Split(const std::string& mime_string,
+ std::string* type, std::string* subtype, Parameters* parameters);
+
+// Returns the MIME type from MIME string.
+// "text/plain;charset=utf-8" => "text"
+std::string GetType(const std::string& mime_string);
+
+// Returns the MIME sub-type from MIME string.
+// "text/plain;charset=utf-8" => "plain"
+std::string GetSubtype(const std::string& mime_string);
+
+// Returns the MIME parameters from MIME string.
+// "text/plain;charset=utf-8" => {{"charset","utf-8"}}
+Parameters GetParameters(const std::string& mime_string);
+
+// Removes parameters from a MIME string
+// "text/plain;charset=utf-8" => "text/plain"
+std::string RemoveParameters(const std::string& mime_string) WARN_UNUSED_RESULT;
+
+// Appends a parameter to a MIME string.
+// "text/plain" => "text/plain; charset=utf-8"
+std::string AppendParameter(const std::string& mime_string,
+ const std::string& paramName,
+ const std::string& paramValue) WARN_UNUSED_RESULT;
+
+// Returns the value of a parameter on a MIME string (empty string if missing).
+// ("text/plain;charset=utf-8","charset") => "utf-8"
+std::string GetParameterValue(const std::string& mime_string,
+ const std::string& paramName);
+
+} // namespace mime
+} // namespace buffet
+
+#endif // BUFFET_MIME_UTILS_H_
diff --git a/buffet/mime_utils_unittest.cc b/buffet/mime_utils_unittest.cc
new file mode 100644
index 0000000..a705dfa
--- /dev/null
+++ b/buffet/mime_utils_unittest.cc
@@ -0,0 +1,65 @@
+// Copyright (c) 2014 The Chromium OS 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 "buffet/mime_utils.h"
+
+#include <gtest/gtest.h>
+
+using namespace buffet; // NOLINT(build/namespaces)
+
+TEST(MimeUtils, Combine) {
+ std::string mime_string = mime::Combine(mime::types::kText, "xml");
+ EXPECT_EQ(mime::text::kXml, mime_string);
+ EXPECT_EQ("application/json; charset=utf-8",
+ mime::Combine(mime::types::kApplication, "json",
+ {{"charset", "utf-8"}}));
+}
+
+TEST(MimeUtils, Split) {
+ std::string s1, s2;
+ EXPECT_TRUE(mime::Split(mime::image::kJpeg, &s1, &s2));
+ EXPECT_EQ(mime::types::kImage, s1);
+ EXPECT_EQ("jpeg", s2);
+
+ mime::Parameters parameters;
+ EXPECT_TRUE(mime::Split("application/json;charset=utf-8",
+ &s1, &s2, ¶meters));
+ EXPECT_EQ(mime::types::kApplication, s1);
+ EXPECT_EQ("json", s2);
+ EXPECT_EQ(mime::application::kJson, mime::Combine(s1, s2));
+ EXPECT_EQ(1, parameters.size());
+ EXPECT_EQ(mime::parameters::kCharset, parameters.front().first);
+ EXPECT_EQ("utf-8", parameters.front().second);
+ EXPECT_EQ("application/json; charset=utf-8",
+ mime::Combine(s1, s2, parameters));
+}
+
+TEST(MimeUtils, ExtractParts) {
+ mime::Parameters parameters;
+
+ EXPECT_EQ(mime::types::kText, mime::GetType(mime::text::kPlain));
+ EXPECT_EQ("plain", mime::GetSubtype(mime::text::kPlain));
+
+ parameters = mime::GetParameters("text/plain; charset=iso-8859-1;foo=bar");
+ EXPECT_EQ(2, parameters.size());
+ EXPECT_EQ(mime::parameters::kCharset, parameters[0].first);
+ EXPECT_EQ("iso-8859-1", parameters[0].second);
+ EXPECT_EQ("foo", parameters[1].first);
+ EXPECT_EQ("bar", parameters[1].second);
+}
+
+TEST(MimeUtils, AppendRemoveParams) {
+ std::string mime_string = mime::AppendParameter(mime::text::kXml,
+ mime::parameters::kCharset,
+ "utf-8");
+ EXPECT_EQ("text/xml; charset=utf-8", mime_string);
+ mime_string = mime::AppendParameter(mime_string, "foo", "bar");
+ EXPECT_EQ("text/xml; charset=utf-8; foo=bar", mime_string);
+ EXPECT_EQ("utf-8", mime::GetParameterValue(mime_string,
+ mime::parameters::kCharset));
+ EXPECT_EQ("bar", mime::GetParameterValue(mime_string, "foo"));
+ EXPECT_EQ("", mime::GetParameterValue(mime_string, "baz"));
+ mime_string = mime::RemoveParameters(mime_string);
+ EXPECT_EQ(mime::text::kXml, mime_string);
+}
diff --git a/buffet/storage_impls.cc b/buffet/storage_impls.cc
new file mode 100644
index 0000000..d95538a
--- /dev/null
+++ b/buffet/storage_impls.cc
@@ -0,0 +1,44 @@
+// Copyright 2014 The Chromium OS 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 "buffet/storage_impls.h"
+
+#include <string>
+
+#include <base/files/important_file_writer.h>
+#include <base/json/json_reader.h>
+#include <base/json/json_writer.h>
+
+namespace buffet {
+
+FileStorage::FileStorage(const base::FilePath& file_path)
+ : file_path_(file_path) { }
+
+std::unique_ptr<base::Value> FileStorage::Load() {
+ std::string json;
+ if (!base::ReadFileToString(file_path_, &json))
+ return std::unique_ptr<base::Value>();
+
+ return std::unique_ptr<base::Value>(base::JSONReader::Read(json));
+}
+
+bool FileStorage::Save(const base::Value* config) {
+ std::string json;
+ base::JSONWriter::WriteWithOptions(
+ config, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
+ return base::ImportantFileWriter::WriteFileAtomically(file_path_, json);
+}
+
+
+std::unique_ptr<base::Value> MemStorage::Load() {
+ return std::unique_ptr<base::Value>(cache_->DeepCopy());
+}
+
+bool MemStorage::Save(const base::Value* config) {
+ cache_.reset(config->DeepCopy());
+ ++save_count_;
+ return true;
+}
+
+} // namespace buffet
diff --git a/buffet/storage_impls.h b/buffet/storage_impls.h
new file mode 100644
index 0000000..6e69084
--- /dev/null
+++ b/buffet/storage_impls.h
@@ -0,0 +1,47 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_STORAGE_IMPLS_H_
+#define BUFFET_STORAGE_IMPLS_H_
+
+#include <base/basictypes.h>
+#include <base/file_util.h>
+#include <base/values.h>
+
+#include "buffet/storage_interface.h"
+
+namespace buffet {
+
+// Persists the given Value to an atomically written file.
+class FileStorage : public StorageInterface {
+ public:
+ explicit FileStorage(const base::FilePath& file_path);
+ virtual ~FileStorage() = default;
+ virtual std::unique_ptr<base::Value> Load() override;
+ virtual bool Save(const base::Value* config) override;
+
+ private:
+ base::FilePath file_path_;
+ DISALLOW_COPY_AND_ASSIGN(FileStorage);
+};
+
+// StorageInterface for testing. Just stores the values in memory.
+class MemStorage : public StorageInterface {
+ public:
+ MemStorage() = default;
+ virtual ~MemStorage() = default;
+ virtual std::unique_ptr<base::Value> Load() override;
+ virtual bool Save(const base::Value* config) override;
+ int save_count() { return save_count_; }
+ void reset_save_count() { save_count_ = 0; }
+
+ private:
+ int save_count_ = 0;
+ std::unique_ptr<base::Value> cache_;
+ DISALLOW_COPY_AND_ASSIGN(MemStorage);
+};
+
+} // namespace buffet
+
+#endif // BUFFET_STORAGE_IMPLS_H_
diff --git a/buffet/storage_interface.h b/buffet/storage_interface.h
new file mode 100644
index 0000000..26d71c1
--- /dev/null
+++ b/buffet/storage_interface.h
@@ -0,0 +1,32 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_STORAGE_INTERFACE_H_
+#define BUFFET_STORAGE_INTERFACE_H_
+
+#include <memory>
+
+#include <base/values.h>
+
+namespace buffet {
+
+// We need to persist data in a couple places, and it is convenient to hide
+// the details of this storage behind an interface for test purposes.
+class StorageInterface {
+ public:
+ // Load the device registration configuration from storage.
+ // If it fails (e.g. the storage container [file?] doesn't exist), then
+ // it returns empty unique_ptr (aka nullptr).
+ virtual std::unique_ptr<base::Value> Load() = 0;
+
+ // Save the device registration configuration to storage.
+ // If saved successfully, returns true. Could fail when writing to
+ // physical storage like file system for various reasons (out of disk space,
+ // access permissions, etc).
+ virtual bool Save(const base::Value* config) = 0;
+};
+
+} // namespace buffet
+
+#endif // BUFFET_STORAGE_INTERFACE_H_
diff --git a/buffet/string_utils.cc b/buffet/string_utils.cc
new file mode 100644
index 0000000..55c7d3f
--- /dev/null
+++ b/buffet/string_utils.cc
@@ -0,0 +1,90 @@
+// Copyright 2014 The Chromium OS 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 "buffet/string_utils.h"
+
+#include <algorithm>
+#include <string.h>
+#include <utility>
+
+#include <base/strings/string_util.h>
+
+namespace buffet {
+namespace string_utils {
+
+std::vector<std::string> Split(const std::string& str,
+ char delimiter,
+ bool trim_whitespaces,
+ bool purge_empty_strings) {
+ std::vector<std::string> tokens;
+ if (delimiter == 0)
+ return tokens;
+
+ const char* sz = str.c_str();
+ if (sz) {
+ const char* szNext = strchr(sz, delimiter);
+ while (szNext) {
+ if (szNext != sz || !purge_empty_strings)
+ tokens.emplace_back(sz, szNext - sz);
+ sz = szNext + 1;
+ szNext = strchr(sz, delimiter);
+ }
+ if (*sz != 0 || !purge_empty_strings)
+ tokens.emplace_back(sz);
+ }
+
+ if (trim_whitespaces) {
+ std::for_each(tokens.begin(), tokens.end(),
+ [](std::string& str) { // NOLINT(runtime/references)
+ base::TrimWhitespaceASCII(str, base::TRIM_ALL, &str); });
+ }
+
+ return tokens;
+}
+
+std::pair<std::string, std::string> SplitAtFirst(const std::string& str,
+ char delimiter,
+ bool trim_whitespaces) {
+ std::pair<std::string, std::string> pair;
+ if (delimiter == 0)
+ return pair;
+
+ const char* sz = str.c_str();
+ const char* szNext = strchr(sz, delimiter);
+ if (szNext) {
+ pair.first = std::string(sz, szNext);
+ pair.second = std::string(szNext + 1);
+ } else {
+ pair.first = str;
+ }
+
+ if (trim_whitespaces) {
+ base::TrimWhitespaceASCII(pair.first, base::TRIM_ALL, &pair.first);
+ base::TrimWhitespaceASCII(pair.second, base::TRIM_ALL, &pair.second);
+ }
+
+ return pair;
+}
+
+std::string Join(char delimiter, const std::vector<std::string>& strings) {
+ return JoinString(strings, delimiter);
+}
+
+std::string Join(const std::string& delimiter,
+ const std::vector<std::string>& strings) {
+ return JoinString(strings, delimiter);
+}
+
+std::string Join(char delimiter,
+ const std::string& str1, const std::string& str2) {
+ return str1 + delimiter + str2;
+}
+
+std::string Join(const std::string& delimiter,
+ const std::string& str1, const std::string& str2) {
+ return str1 + delimiter + str2;
+}
+
+} // namespace string_utils
+} // namespace buffet
diff --git a/buffet/string_utils.h b/buffet/string_utils.h
new file mode 100644
index 0000000..95da72d
--- /dev/null
+++ b/buffet/string_utils.h
@@ -0,0 +1,42 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_STRING_UTILS_H_
+#define BUFFET_STRING_UTILS_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace buffet {
+namespace string_utils {
+
+// Treats the string as a delimited list of substrings and returns the array
+// of original elements of the list.
+// By default, empty elements from the original string are omitted and
+// each element has all whitespaces trimmed off.
+std::vector<std::string> Split(const std::string& str,
+ char delimiter,
+ bool trim_whitespaces = true,
+ bool purge_empty_strings = true);
+
+// Splits the string into two pieces at the first position of the specified
+// delimiter. By default, each part has all whitespaces trimmed off.
+std::pair<std::string, std::string> SplitAtFirst(const std::string& str,
+ char delimiter,
+ bool trim_whitespaces = true);
+
+// Joins an array of strings into a single string separated by |delimiter|.
+std::string Join(char delimiter, const std::vector<std::string>& strings);
+std::string Join(const std::string& delimiter,
+ const std::vector<std::string>& strings);
+std::string Join(char delimiter,
+ const std::string& str1, const std::string& str2);
+std::string Join(const std::string& delimiter,
+ const std::string& str1, const std::string& str2);
+
+} // namespace string_utils
+} // namespace buffet
+
+#endif // BUFFET_STRING_UTILS_H_
diff --git a/buffet/string_utils_unittest.cc b/buffet/string_utils_unittest.cc
new file mode 100644
index 0000000..4a021f4
--- /dev/null
+++ b/buffet/string_utils_unittest.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 2014 The Chromium OS 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 "buffet/string_utils.h"
+
+#include <gtest/gtest.h>
+
+using namespace buffet; // NOLINT(build/namespaces)
+
+TEST(StringUtils, Split) {
+ std::vector<std::string> parts;
+
+ parts = string_utils::Split(",a,bc , d,,e,", ',', true, true);
+ EXPECT_EQ(4, parts.size());
+ EXPECT_EQ("a", parts[0]);
+ EXPECT_EQ("bc", parts[1]);
+ EXPECT_EQ("d", parts[2]);
+ EXPECT_EQ("e", parts[3]);
+
+ parts = string_utils::Split(",a,bc , d,,e,", ',', false, true);
+ EXPECT_EQ(4, parts.size());
+ EXPECT_EQ("a", parts[0]);
+ EXPECT_EQ("bc ", parts[1]);
+ EXPECT_EQ(" d", parts[2]);
+ EXPECT_EQ("e", parts[3]);
+
+ parts = string_utils::Split(",a,bc , d,,e,", ',', true, false);
+ EXPECT_EQ(7, parts.size());
+ EXPECT_EQ("", parts[0]);
+ EXPECT_EQ("a", parts[1]);
+ EXPECT_EQ("bc", parts[2]);
+ EXPECT_EQ("d", parts[3]);
+ EXPECT_EQ("", parts[4]);
+ EXPECT_EQ("e", parts[5]);
+ EXPECT_EQ("", parts[6]);
+
+ parts = string_utils::Split(",a,bc , d,,e,", ',', false, false);
+ EXPECT_EQ(7, parts.size());
+ EXPECT_EQ("", parts[0]);
+ EXPECT_EQ("a", parts[1]);
+ EXPECT_EQ("bc ", parts[2]);
+ EXPECT_EQ(" d", parts[3]);
+ EXPECT_EQ("", parts[4]);
+ EXPECT_EQ("e", parts[5]);
+ EXPECT_EQ("", parts[6]);
+}
+
+TEST(StringUtils, SplitAtFirst) {
+ std::pair<std::string, std::string> pair;
+
+ pair = string_utils::SplitAtFirst(" 123 : 4 : 56 : 789 ", ':', true);
+ EXPECT_EQ("123", pair.first);
+ EXPECT_EQ("4 : 56 : 789", pair.second);
+
+ pair = string_utils::SplitAtFirst(" 123 : 4 : 56 : 789 ", ':', false);
+ EXPECT_EQ(" 123 ", pair.first);
+ EXPECT_EQ(" 4 : 56 : 789 ", pair.second);
+
+ pair = string_utils::SplitAtFirst("", '=');
+ EXPECT_EQ("", pair.first);
+ EXPECT_EQ("", pair.second);
+
+ pair = string_utils::SplitAtFirst("=", '=');
+ EXPECT_EQ("", pair.first);
+ EXPECT_EQ("", pair.second);
+
+ pair = string_utils::SplitAtFirst("a=", '=');
+ EXPECT_EQ("a", pair.first);
+ EXPECT_EQ("", pair.second);
+
+ pair = string_utils::SplitAtFirst("abc=", '=');
+ EXPECT_EQ("abc", pair.first);
+ EXPECT_EQ("", pair.second);
+
+ pair = string_utils::SplitAtFirst("=a", '=');
+ EXPECT_EQ("", pair.first);
+ EXPECT_EQ("a", pair.second);
+
+ pair = string_utils::SplitAtFirst("=abc=", '=');
+ EXPECT_EQ("", pair.first);
+ EXPECT_EQ("abc=", pair.second);
+
+ pair = string_utils::SplitAtFirst("abc", '=');
+ EXPECT_EQ("abc", pair.first);
+ EXPECT_EQ("", pair.second);
+}
+
+TEST(StringUtils, Join_Char) {
+ EXPECT_EQ("", string_utils::Join(',', {}));
+ EXPECT_EQ("abc", string_utils::Join(',', {"abc"}));
+ EXPECT_EQ("abc,defg", string_utils::Join(',', {"abc", "defg"}));
+ EXPECT_EQ("1:2:3", string_utils::Join(':', {"1", "2", "3"}));
+ EXPECT_EQ("192.168.0.1", string_utils::Join('.', {"192", "168", "0", "1"}));
+ EXPECT_EQ("ff02::1", string_utils::Join(':', {"ff02", "", "1"}));
+}
+
+TEST(StringUtils, Join_String) {
+ EXPECT_EQ("", string_utils::Join(",", {}));
+ EXPECT_EQ("abc", string_utils::Join(",", {"abc"}));
+ EXPECT_EQ("abc,defg", string_utils::Join(",", {"abc", "defg"}));
+ EXPECT_EQ("1 : 2 : 3", string_utils::Join(" : ", {"1", "2", "3"}));
+ EXPECT_EQ("123", string_utils::Join("", {"1", "2", "3"}));
+}
+
+TEST(StringUtils, Join_Pair) {
+ EXPECT_EQ("ab,cd", string_utils::Join(',', "ab", "cd"));
+ EXPECT_EQ("key = value", string_utils::Join(" = ", "key", "value"));
+}
diff --git a/buffet/url_utils.cc b/buffet/url_utils.cc
new file mode 100644
index 0000000..0395094
--- /dev/null
+++ b/buffet/url_utils.cc
@@ -0,0 +1,167 @@
+// Copyright 2014 The Chromium OS 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 "buffet/url_utils.h"
+
+#include <algorithm>
+
+namespace {
+// Given a URL string, determine where the query string starts and ends.
+// URLs have schema, domain and path (along with possible user name, password
+// and port number which are of no interest for us here) which could optionally
+// have a query string that is separated from the path by '?'. Finally, the URL
+// could also have a '#'-separated URL fragment which is usually used by the
+// browser as a bookmark element. So, for example:
+// http://server.com/path/to/object?k=v&foo=bar#fragment
+// Here:
+// http://server.com/path/to/object - is the URL of the object,
+// ?k=v&foo=bar - URL query string
+// #fragment - URL fragment string
+// If |exclude_fragment| is true, the function returns the start character and
+// the length of the query string alone. If it is false, the query string length
+// will include both the query string and the fragment.
+bool GetQueryStringPos(const std::string& url, bool exclude_fragment,
+ size_t* query_pos, size_t* query_len) {
+ size_t query_start = url.find_first_of("?#");
+ if (query_start == std::string::npos) {
+ *query_pos = url.size();
+ if (query_len)
+ *query_len = 0;
+ return false;
+ }
+
+ *query_pos = query_start;
+ if (query_len) {
+ size_t query_end = url.size();
+
+ if (exclude_fragment) {
+ if (url[query_start] == '?') {
+ size_t pos_fragment = url.find('#', query_start);
+ if (pos_fragment != std::string::npos)
+ query_end = pos_fragment;
+ } else {
+ query_end = query_start;
+ }
+ }
+ *query_len = query_end - query_start;
+ }
+ return true;
+}
+} // anonymous namespace
+
+namespace buffet {
+
+std::string url::TrimOffQueryString(std::string* url) {
+ size_t query_pos;
+ if (!GetQueryStringPos(*url, false, &query_pos, nullptr))
+ return std::string();
+ std::string query_string = url->substr(query_pos);
+ url->resize(query_pos);
+ return query_string;
+}
+
+std::string url::Combine(
+ const std::string& url, const std::string& subpath) {
+ return CombineMultiple(url, {subpath});
+}
+
+std::string url::CombineMultiple(
+ const std::string& url, const std::vector<std::string>& parts) {
+ std::string result = url;
+ if (!parts.empty()) {
+ std::string query_string = TrimOffQueryString(&result);
+ for (auto&& part : parts) {
+ if (!part.empty()) {
+ if (!result.empty() && result.back() != '/')
+ result += '/';
+ size_t non_slash_pos = part.find_first_not_of('/');
+ if (non_slash_pos != std::string::npos)
+ result += part.substr(non_slash_pos);
+ }
+ }
+ result += query_string;
+ }
+ return result;
+}
+
+std::string url::GetQueryString(
+ const std::string& url, bool remove_fragment) {
+ std::string query_string;
+ size_t query_pos, query_len;
+ if (GetQueryStringPos(url, remove_fragment, &query_pos, &query_len)) {
+ query_string = url.substr(query_pos, query_len);
+ }
+ return query_string;
+}
+
+data_encoding::WebParamList url::GetQueryStringParameters(
+ const std::string& url) {
+ // Extract the query string and remove the leading '?'.
+ std::string query_string = GetQueryString(url, true);
+ if (!query_string.empty() && query_string.front() == '?')
+ query_string.erase(query_string.begin());
+ return data_encoding::WebParamsDecode(query_string);
+}
+
+std::string url::GetQueryStringValue(
+ const std::string& url, const std::string& name) {
+ return GetQueryStringValue(GetQueryStringParameters(url), name);
+}
+
+std::string url::GetQueryStringValue(
+ const data_encoding::WebParamList& params,
+ const std::string& name) {
+ for (auto&& pair : params) {
+ if (name.compare(pair.first) == 0)
+ return pair.second;
+ }
+ return std::string();
+}
+
+std::string url::RemoveQueryString(
+ const std::string& url, bool remove_fragment_too) {
+ size_t query_pos, query_len;
+ if (!GetQueryStringPos(url, !remove_fragment_too, &query_pos, &query_len))
+ return url;
+ std::string result = url.substr(0, query_pos);
+ size_t fragment_pos = query_pos + query_len;
+ if (fragment_pos < url.size()) {
+ result += url.substr(fragment_pos);
+ }
+ return result;
+}
+
+std::string url::AppendQueryParam(
+ const std::string& url, const std::string& name, const std::string& value) {
+ return AppendQueryParams(url, {{name, value}});
+}
+
+std::string url::AppendQueryParams(
+ const std::string& url,
+ const data_encoding::WebParamList& params) {
+ if (params.empty())
+ return url;
+ size_t query_pos, query_len;
+ GetQueryStringPos(url, true, &query_pos, &query_len);
+ size_t fragment_pos = query_pos + query_len;
+ std::string result = url.substr(0, fragment_pos);
+ if (query_len == 0) {
+ result += '?';
+ } else if (query_len > 1) {
+ result += '&';
+ }
+ result += data_encoding::WebParamsEncode(params);
+ if (fragment_pos < url.size()) {
+ result += url.substr(fragment_pos);
+ }
+ return result;
+}
+
+bool url::HasQueryString(const std::string& url) {
+ size_t query_pos, query_len;
+ GetQueryStringPos(url, true, &query_pos, &query_len);
+ return (query_len > 0);
+}
+
+} // namespace buffet
diff --git a/buffet/url_utils.h b/buffet/url_utils.h
new file mode 100644
index 0000000..d9bce25
--- /dev/null
+++ b/buffet/url_utils.h
@@ -0,0 +1,77 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_URL_UTILS_H_
+#define BUFFET_URL_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include <base/basictypes.h>
+
+#include "buffet/data_encoding.h"
+
+namespace buffet {
+
+namespace url {
+
+// Appends a subpath to url and delimiting then with '/' if the path doesn't
+// end with it already. Also handles URLs with query parameters/fragment.
+std::string Combine(const std::string& url,
+ const std::string& subpath) WARN_UNUSED_RESULT;
+std::string CombineMultiple(
+ const std::string& url,
+ const std::vector<std::string>& parts) WARN_UNUSED_RESULT;
+
+// Removes the query string/fragment from |url| and returns the query string.
+// This method actually modifies |url|. So, if you call it on this:
+// http://www.test.org/?foo=bar
+// it will modify |url| to "http://www.test.org/" and return "?foo=bar"
+std::string TrimOffQueryString(std::string* url);
+
+// Returns the query string, if available.
+// For example, for the following URL:
+// http://server.com/path/to/object?k=v&foo=bar#fragment
+// Here:
+// http://server.com/path/to/object - is the URL of the object,
+// ?k=v&foo=bar - URL query string
+// #fragment - URL fragment string
+// If |remove_fragment| is true, the function returns the query string without
+// the fragment. Otherwise the fragment is included as part of the result.
+std::string GetQueryString(const std::string& url, bool remove_fragment);
+
+// Parses the query string into a set of key-value pairs.
+data_encoding::WebParamList GetQueryStringParameters(const std::string& url);
+
+// Returns a value of the specified query parameter, or empty string if missing.
+std::string GetQueryStringValue(const std::string& url,
+ const std::string& name);
+std::string GetQueryStringValue(const data_encoding::WebParamList& params,
+ const std::string& name);
+
+// Removes the query string and/or a fragment part from URL.
+// If |remove_fragment| is specified, the fragment is also removed.
+// For example:
+// http://server.com/path/to/object?k=v&foo=bar#fragment
+// true -> http://server.com/path/to/object
+// false -> http://server.com/path/to/object#fragment
+std::string RemoveQueryString(const std::string& url,
+ bool remove_fragment) WARN_UNUSED_RESULT;
+
+// Appends a single query parameter to the URL.
+std::string AppendQueryParam(const std::string& url,
+ const std::string& name,
+ const std::string& value) WARN_UNUSED_RESULT;
+// Appends a list of query parameters to the URL.
+std::string AppendQueryParams(
+ const std::string& url,
+ const data_encoding::WebParamList& params) WARN_UNUSED_RESULT;
+
+// Checks if the URL has query parameters.
+bool HasQueryString(const std::string& url);
+
+} // namespace url
+} // namespace buffet
+
+#endif // BUFFET_URL_UTILS_H_
diff --git a/buffet/url_utils_unittest.cc b/buffet/url_utils_unittest.cc
new file mode 100644
index 0000000..7f4826d
--- /dev/null
+++ b/buffet/url_utils_unittest.cc
@@ -0,0 +1,152 @@
+// Copyright (c) 2014 The Chromium OS 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 "buffet/url_utils.h"
+
+#include <gtest/gtest.h>
+
+using namespace buffet; // NOLINT(build/namespaces)
+
+TEST(UrlUtils, Combine) {
+ EXPECT_EQ("http://sample.org/path",
+ url::Combine("http://sample.org", "path"));
+ EXPECT_EQ("http://sample.org/path",
+ url::Combine("http://sample.org/", "path"));
+ EXPECT_EQ("path1/path2", url::Combine("", "path1/path2"));
+ EXPECT_EQ("path1/path2", url::Combine("path1", "path2"));
+ EXPECT_EQ("http://sample.org",
+ url::Combine("http://sample.org", ""));
+ EXPECT_EQ("http://sample.org/path",
+ url::Combine("http://sample.org/", "/path"));
+ EXPECT_EQ("http://sample.org/path",
+ url::Combine("http://sample.org", "//////path"));
+ EXPECT_EQ("http://sample.org/",
+ url::Combine("http://sample.org", "///"));
+ EXPECT_EQ("http://sample.org/obj/path1/path2",
+ url::Combine("http://sample.org/obj", "path1/path2"));
+ EXPECT_EQ("http://sample.org/obj/path1/path2#tag",
+ url::Combine("http://sample.org/obj#tag", "path1/path2"));
+ EXPECT_EQ("http://sample.org/obj/path1/path2?k1=v1&k2=v2",
+ url::Combine("http://sample.org/obj?k1=v1&k2=v2", "path1/path2"));
+ EXPECT_EQ("http://sample.org/obj/path1/path2?k1=v1#k2=v2",
+ url::Combine("http://sample.org/obj/?k1=v1#k2=v2", "path1/path2"));
+ EXPECT_EQ("http://sample.org/obj/path1/path2#tag?",
+ url::Combine("http://sample.org/obj#tag?", "path1/path2"));
+ EXPECT_EQ("path1/path2", url::CombineMultiple("", {"path1", "path2"}));
+ EXPECT_EQ("http://sample.org/obj/part1/part2",
+ url::CombineMultiple("http://sample.org",
+ {"obj", "", "/part1/", "part2"}));
+}
+
+TEST(UrlUtils, GetQueryString) {
+ EXPECT_EQ("", url::GetQueryString("http://sample.org", false));
+ EXPECT_EQ("", url::GetQueryString("http://sample.org", true));
+ EXPECT_EQ("", url::GetQueryString("", false));
+ EXPECT_EQ("", url::GetQueryString("", true));
+
+ EXPECT_EQ("?q=v&b=2#tag?2",
+ url::GetQueryString("http://s.com/?q=v&b=2#tag?2", false));
+ EXPECT_EQ("?q=v&b=2",
+ url::GetQueryString("http://s.com/?q=v&b=2#tag?2", true));
+
+ EXPECT_EQ("#tag?a=2",
+ url::GetQueryString("http://s.com/#tag?a=2", false));
+ EXPECT_EQ("",
+ url::GetQueryString("http://s.com/#tag?a=2", true));
+
+ EXPECT_EQ("?a=2&b=2",
+ url::GetQueryString("?a=2&b=2", false));
+ EXPECT_EQ("?a=2&b=2",
+ url::GetQueryString("?a=2&b=2", true));
+
+ EXPECT_EQ("#s#?d#?f?#s?#d",
+ url::GetQueryString("#s#?d#?f?#s?#d", false));
+ EXPECT_EQ("",
+ url::GetQueryString("#s#?d#?f?#s?#d", true));
+}
+
+TEST(UrlUtils, GetQueryStringParameters) {
+ auto params = url::GetQueryStringParameters(
+ "http://sample.org/path?k=v&&%3Dkey%3D=val%26&r#blah");
+
+ EXPECT_EQ(3, params.size());
+ EXPECT_EQ("k", params[0].first);
+ EXPECT_EQ("v", params[0].second);
+ EXPECT_EQ("=key=", params[1].first);
+ EXPECT_EQ("val&", params[1].second);
+ EXPECT_EQ("r", params[2].first);
+ EXPECT_EQ("", params[2].second);
+}
+
+TEST(UrlUtils, GetQueryStringValue) {
+ std::string url = "http://url?key1=val1&&key2=val2";
+ EXPECT_EQ("val1", url::GetQueryStringValue(url, "key1"));
+ EXPECT_EQ("val2", url::GetQueryStringValue(url, "key2"));
+ EXPECT_EQ("", url::GetQueryStringValue(url, "key3"));
+
+ auto params = url::GetQueryStringParameters(url);
+ EXPECT_EQ("val1", url::GetQueryStringValue(params, "key1"));
+ EXPECT_EQ("val2", url::GetQueryStringValue(params, "key2"));
+ EXPECT_EQ("", url::GetQueryStringValue(params, "key3"));
+}
+
+TEST(UrlUtils, TrimOffQueryString) {
+ std::string url = "http://url?key1=val1&key2=val2#fragment";
+ std::string query = url::TrimOffQueryString(&url);
+ EXPECT_EQ("http://url", url);
+ EXPECT_EQ("?key1=val1&key2=val2#fragment", query);
+
+ url = "http://url#fragment";
+ query = url::TrimOffQueryString(&url);
+ EXPECT_EQ("http://url", url);
+ EXPECT_EQ("#fragment", query);
+
+ url = "http://url";
+ query = url::TrimOffQueryString(&url);
+ EXPECT_EQ("http://url", url);
+ EXPECT_EQ("", query);
+}
+
+TEST(UrlUtils, RemoveQueryString) {
+ std::string url = "http://url?key1=val1&key2=val2#fragment";
+ EXPECT_EQ("http://url", url::RemoveQueryString(url, true));
+ EXPECT_EQ("http://url#fragment", url::RemoveQueryString(url, false));
+}
+
+TEST(UrlUtils, AppendQueryParam) {
+ std::string url = "http://server.com/path";
+ url = url::AppendQueryParam(url, "param", "value");
+ EXPECT_EQ("http://server.com/path?param=value", url);
+ url = url::AppendQueryParam(url, "param2", "v");
+ EXPECT_EQ("http://server.com/path?param=value¶m2=v", url);
+
+ url = "http://server.com/path#fragment";
+ url = url::AppendQueryParam(url, "param", "value");
+ EXPECT_EQ("http://server.com/path?param=value#fragment", url);
+ url = url::AppendQueryParam(url, "param2", "v");
+ EXPECT_EQ("http://server.com/path?param=value¶m2=v#fragment", url);
+
+ url = url::AppendQueryParam("http://server.com/path?", "param", "value");
+ EXPECT_EQ("http://server.com/path?param=value", url);
+}
+
+TEST(UrlUtils, AppendQueryParams) {
+ std::string url = "http://server.com/path";
+ url = url::AppendQueryParams(url, {});
+ EXPECT_EQ("http://server.com/path", url);
+ url = url::AppendQueryParams(url, {{"param", "value"}, {"q", "="}});
+ EXPECT_EQ("http://server.com/path?param=value&q=%3D", url);
+ url += "#fr?";
+ url = url::AppendQueryParams(url, {{"p", "1"}, {"s&", "\n"}});
+ EXPECT_EQ("http://server.com/path?param=value&q=%3D&p=1&s%26=%0A#fr?", url);
+}
+
+TEST(UrlUtils, HasQueryString) {
+ EXPECT_FALSE(url::HasQueryString("http://server.com/path"));
+ EXPECT_FALSE(url::HasQueryString("http://server.com/path#blah?v=1"));
+ EXPECT_TRUE(url::HasQueryString("http://server.com/path?v=1#blah"));
+ EXPECT_TRUE(url::HasQueryString("http://server.com/path?v=1"));
+ EXPECT_FALSE(url::HasQueryString(""));
+ EXPECT_TRUE(url::HasQueryString("?ss"));
+}