Buffet: Move buffet over to platform2 from src/platform/buffet. This change also open-sources buffet. The only change in this CL is the removal of the Makefile and addition of the buffet.gyp file. BUG=chromium:355180 TEST=USE=buffet emerge-gizmo platform2 Change-Id: Ibf8d3ac3f38313f82a9c07d79932b6f30130f9c5
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/buffet.conf b/buffet/buffet.conf new file mode 100644 index 0000000..7c3ee8c --- /dev/null +++ b/buffet/buffet.conf
@@ -0,0 +1,12 @@ +# 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 + +exec buffet
diff --git a/buffet/buffet.gyp b/buffet/buffet.gyp new file mode 100644 index 0000000..6b08e4c --- /dev/null +++ b/buffet/buffet.gyp
@@ -0,0 +1,77 @@ +{ + 'variables': { + 'libbase_ver': 242728, + }, + 'target_defaults': { + 'dependencies': [ + '../../metrics/libmetrics-<(libbase_ver).gyp:libmetrics-<(libbase_ver)', + ], + 'variables': { + 'deps': [ + 'dbus-1', + 'libchrome-<(libbase_ver)', + 'libcurl', + ], + }, + # TODO(sosa): Remove gflags: crbug.com/356745. + 'link_settings': { + 'libraries': [ + '-lgflags', + ], + }, + 'include_dirs': [ + '..' # To access all src/platform2 directories + ], + # TODO(sosa): Remove no-strict-aliasing: crbug.com/356745. + 'cflags_cc': [ + '-std=gnu++11', + '-fno-strict-aliasing', + ], + }, + 'targets': [ + { + 'target_name': 'buffet_common', + 'type': 'static_library', + 'sources': [ + 'data_encoding.cc', + 'dbus_manager.cc', + 'http_request.cc', + 'http_transport_curl.cc', + 'http_utils.cc', + 'mime_utils.cc', + 'string_utils.cc', + ], + }, + { + 'target_name': 'buffet', + 'type': 'executable', + 'sources': [ + 'main.cc', + ], + 'dependencies': [ + 'buffet_common', + ], + }, + { + 'target_name': 'buffet_client', + 'type': 'executable', + 'sources': [ + 'buffet_client.cc', + ], + }, + { + 'target_name': 'buffet_testrunner', + 'type': 'executable', + 'dependencies': [ + 'buffet_common', + ], + 'includes': ['../../common-mk/common_test.gypi'], + 'sources': [ + 'buffet_testrunner.cc', + 'data_encoding_unittest.cc', + 'mime_utils_unittest.cc', + 'string_utils_unittest.cc', + ], + }, + ], +}
diff --git a/buffet/buffet_client.cc b/buffet/buffet_client.cc new file mode 100755 index 0000000..83d5f9c --- /dev/null +++ b/buffet/buffet_client.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 <string> + +#include <base/logging.h> +#include <base/memory/scoped_ptr.h> +#include <dbus/bus.h> +#include <dbus/object_proxy.h> +#include <gflags/gflags.h> + +#include "buffet/dbus_manager.h" + +DEFINE_bool(testmethod, false, "Call the Buffet Test Method."); + +namespace { + +dbus::ObjectProxy* GetBuffetDBusProxy(dbus::Bus *bus) { + return bus->GetObjectProxy( + buffet::kBuffetServiceName, + dbus::ObjectPath(buffet::kBuffetServicePath)); +} + +void CallTestMethod(dbus::ObjectProxy* proxy) { + int timeout_ms = 1000; + dbus::MethodCall method_call(buffet::kBuffetInterface, + buffet::kTestMethod); + scoped_ptr<dbus::Response> response(proxy->CallMethodAndBlock(&method_call, + timeout_ms)); + if (!response) { + LOG(ERROR) << "Failed to receive a response."; + return; + } else { + LOG(INFO) << "Received a response."; + } +} + +} // end namespace + +int main(int argc, char** argv) { + google::ParseCommandLineFlags(&argc, &argv, true); + + dbus::Bus::Options options; + options.bus_type = dbus::Bus::SYSTEM; + scoped_refptr<dbus::Bus> bus = new dbus::Bus(options); + + auto proxy = GetBuffetDBusProxy(bus); + if (FLAGS_testmethod) { + CallTestMethod(proxy); + } + + LOG(INFO) << "Done."; + return 0; +} +
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..0e040ab --- /dev/null +++ b/buffet/data_encoding.cc
@@ -0,0 +1,102 @@ +// 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 chromeos { +namespace data_encoding { + +std::string UrlEncode(char const* 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", (unsigned char)c); // Encode as %NN + } + } + return result; +} + +std::string UrlDecode(char const* 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 = char((part1 << 4) | part2); + data += 2; + } else if (c == '+') { + c = ' '; + } + result += c; + } + return result; +} + +std::string WebParamsEncode( + std::vector<std::pair<std::string, std::string>> const& params, + bool encodeSpaceAsPlus) { + std::vector<std::string> pairs; + pairs.reserve(params.size()); + for (auto const& 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); +} + +std::vector<std::pair<std::string, std::string>> WebParamsDecode( + std::string const& data) { + std::vector<std::pair<std::string, std::string>> 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 chromeos
diff --git a/buffet/data_encoding.h b/buffet/data_encoding.h new file mode 100644 index 0000000..6c46e45 --- /dev/null +++ b/buffet/data_encoding.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_DATA_ENCODING__H_ +#define BUFFET_DATA_ENCODING__H_ + +#include <vector> +#include <string> + +namespace chromeos { +namespace data_encoding { + +// 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(char const* data, bool encodeSpaceAsPlus); + +inline std::string UrlEncode(char const* data) { + return UrlEncode(data, true); +} + +// Decodes/unescapes a URL. Replaces all %XX sequences with actual characters. +// Also replaces '+' with spaces. +std::string UrlDecode(char const* data); + +// Converts a list of key-value pairs into a string compatible with +// 'application/x-www-form-urlencoded' content encoding. +std::string WebParamsEncode( + std::vector<std::pair<std::string, std::string>> const& params, + bool encodeSpaceAsPlus); + +inline std::string WebParamsEncode( + std::vector<std::pair<std::string, std::string>> const& 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. +std::vector<std::pair<std::string, std::string>> WebParamsDecode( + std::string const& data); + +} // namespace data_encoding +} // namespace chromeos + +#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..ae4894d --- /dev/null +++ b/buffet/data_encoding_unittest.cc
@@ -0,0 +1,40 @@ +// 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 chromeos::data_encoding; + +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_manager.cc b/buffet/dbus_manager.cc new file mode 100644 index 0000000..c6031fc --- /dev/null +++ b/buffet/dbus_manager.cc
@@ -0,0 +1,80 @@ +// 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_manager.h" + +#include <string> + +#include <base/bind.h> + +namespace buffet { + +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 + +DBusManager::DBusManager() + : bus_(nullptr), + buffet_dbus_object_(nullptr) {} + +DBusManager::~DBusManager() {} + +void DBusManager::Init() { + InitDBus(); +} + +void DBusManager::Finalize() { + ShutDownDBus(); +} + +void DBusManager::InitDBus() { + dbus::Bus::Options options; + // TODO(sosa): Should this be on the system bus? + options.bus_type = dbus::Bus::SYSTEM; + bus_ = new dbus::Bus(options); + CHECK(bus_->Connect()); + + buffet_dbus_object_ = bus_->GetExportedObject( + dbus::ObjectPath(kBuffetServicePath)); + ExportDBusMethod(kTestMethod, &DBusManager::HandleTestMethod); + + CHECK(bus_->RequestOwnershipAndBlock(kBuffetServiceName, + dbus::Bus::REQUIRE_PRIMARY)) + << "Unable to take ownership of " << kBuffetServiceName; +} + +void DBusManager::ShutDownDBus() { + bus_->ShutdownAndBlock(); +} + +void DBusManager::ExportDBusMethod(const std::string& method_name, + DBusMethodCallMemberFunction member) { + DCHECK(buffet_dbus_object_); + CHECK(buffet_dbus_object_->ExportMethodAndBlock( + kBuffetInterface, method_name, + base::Bind(&HandleSynchronousDBusMethodCall, + base::Bind(member, base::Unretained(this))))); +} + +scoped_ptr<dbus::Response> DBusManager::HandleTestMethod( + dbus::MethodCall* method_call) { + LOG(INFO) << "Received call to test method."; + return scoped_ptr<dbus::Response>(); +} + +} // namespace buffet
diff --git a/buffet/dbus_manager.h b/buffet/dbus_manager.h new file mode 100644 index 0000000..da91391 --- /dev/null +++ b/buffet/dbus_manager.h
@@ -0,0 +1,61 @@ +// 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_MANAGER_H_ +#define BUFFET_DBUS_MANAGER_H_ + +#include <string> + +#include <base/memory/scoped_ptr.h> +#include <dbus/bus.h> +#include <dbus/exported_object.h> +#include <dbus/message.h> + +namespace buffet { + +// TODO(sosa): Move to chromeos/system_api once we're ready. +const char kBuffetInterface[] = "org.chromium.Buffet"; +const char kBuffetServicePath[] = "/org/chromium/Buffet"; +const char kBuffetServiceName[] = "org.chromium.Buffet"; + +// Methods exposed by buffet. +const char kTestMethod[] = "TestMethod"; + +class DBusManager; + +// Pointer to a member function for handling D-Bus method calls. If an empty +// scoped_ptr is returned, an empty (but successful) response will be sent. +typedef scoped_ptr<dbus::Response> (DBusManager::*DBusMethodCallMemberFunction)( + dbus::MethodCall*); + +// Class that manages dbus interactions in buffet. +class DBusManager { + public: + DBusManager(); + virtual ~DBusManager(); + + void Init(); + void Finalize(); + + private: + // Connects to the D-Bus system bus and exports methods. + void InitDBus(); + void ShutDownDBus(); + + // Exports |method_name| and uses |member| to handle calls. + void ExportDBusMethod(const std::string& method_name, + DBusMethodCallMemberFunction member); + + // Callbacks for handling D-Bus signals and method calls. + scoped_ptr<dbus::Response> HandleTestMethod(dbus::MethodCall* method_call); + + scoped_refptr<dbus::Bus> bus_; + dbus::ExportedObject* buffet_dbus_object_; // weak; owned by |bus_| + + DISALLOW_COPY_AND_ASSIGN(DBusManager); +}; + +} // namespace buffet + +#endif // BUFFET_DBUS_MANAGER_H_
diff --git a/buffet/http_request.cc b/buffet/http_request.cc new file mode 100644 index 0000000..5d82473 --- /dev/null +++ b/buffet/http_request.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_request.h" + +#include "buffet/http_transport_curl.h" +#include "buffet/mime_utils.h" + +using namespace chromeos; +using namespace chromeos::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(std::string const& url, char const* method) : + transport_(new curl::Transport(url, method)) { +} + +Request::Request(std::string const& url) : + transport_(new curl::Transport(url, nullptr)) { +} + +Request::Request(std::shared_ptr<TransportInterface> transport) : + transport_(transport) { +} + +void Request::AddRange(int64_t bytes) { + if (transport_) + transport_->AddRange(bytes); +} + +void Request::AddRange(uint64_t from_byte, uint64_t to_byte) { + if (transport_) + transport_->AddRange(from_byte, to_byte); +} + +std::unique_ptr<Response> Request::GetResponse() { + if (transport_) { + if (transport_->GetStage() == TransportInterface::Stage::initialized) { + if(transport_->Perform()) + return std::unique_ptr<Response>(new Response(transport_)); + } else if (transport_->GetStage() == + TransportInterface::Stage::response_received) { + return std::unique_ptr<Response>(new Response(transport_)); + } + } + return std::unique_ptr<Response>(); +} + +void Request::SetAccept(char const* accept_mime_types) { + if (transport_) + transport_->SetAccept(accept_mime_types); +} + +std::string Request::GetAccept() const { + return transport_ ? transport_->GetAccept() : std::string(); +} + +std::string Request::GetRequestURL() const { + return transport_ ? transport_->GetRequestURL() : std::string(); +} + +void Request::SetContentType(char const* contentType) { + if (transport_) + transport_->SetContentType(contentType); +} + +std::string Request::GetContentType() const { + return transport_ ? transport_->GetContentType() : std::string(); +} + +void Request::AddHeader(char const* header, char const* value) { + if (transport_) + transport_->AddHeader(header, value); +} + +bool Request::AddRequestBody(void const* data, size_t size) { + return transport_ && transport_->AddRequestBody(data, size); +} + +void Request::SetMethod(char const* method) { + if (transport_) + transport_->SetMethod(method); +} + +std::string Request::GetMethod() const { + return transport_ ? transport_->GetMethod() : std::string(); +} + +void Request::SetReferer(char const* referer) { + if (transport_) + transport_->SetReferer(referer); +} + +std::string Request::GetReferer() const { + return transport_ ? transport_->GetReferer() : std::string(); +} + +void Request::SetUserAgent(char const* user_agent) { + if (transport_) + transport_->SetUserAgent(user_agent); +} + +std::string Request::GetUserAgent() const { + return transport_ ? transport_->GetUserAgent() : std::string(); +} + +std::string Request::GetErrorMessage() const { + if (transport_ && + transport_->GetStage() == TransportInterface::Stage::failed) { + return transport_->GetErrorMessage(); + } + + return std::string(); +} + +//************************************************************************** +//********************** Response Class ********************** +//************************************************************************** +Response::Response(std::shared_ptr<TransportInterface> transport) : + transport_(transport) { +} + +bool Response::IsSuccessful() const { + if (transport_ && + transport_->GetStage() == TransportInterface::Stage::response_received) { + int code = GetStatusCode(); + return code >= status_code::Continue && code < status_code::BadRequest; + } + return false; +} + +int Response::GetStatusCode() const { + if (!transport_) + return -1; + + return transport_->GetResponseStatusCode(); +} + +std::string Response::GetStatusText() const { + if (!transport_) + return std::string(); + + return transport_->GetResponseStatusText(); +} + +std::string Response::GetContentType() const { + return GetHeader(response_header::kContentType); +} + +std::vector<unsigned char> Response::GetData() const { + if (transport_) + return transport_->GetResponseData(); + + return std::vector<unsigned char>(); +} + +std::string Response::GetDataAsString() const { + if (transport_) { + auto data = transport_->GetResponseData(); + char const* data_buf = reinterpret_cast<char const*>(data.data()); + return std::string(data_buf, data_buf + data.size()); + } + + return std::string(); +} + +std::string Response::GetHeader(char const* header_name) const { + if (transport_) + return transport_->GetResponseHeader(header_name); + + return std::string(); +} +
diff --git a/buffet/http_request.h b/buffet/http_request.h new file mode 100644 index 0000000..acdab89 --- /dev/null +++ b/buffet/http_request.h
@@ -0,0 +1,317 @@ +// 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 <vector> +#include <memory> +#include <string> +#include <base/basictypes.h> + +#include "buffet/transport_interface.h" + +namespace chromeos { +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 furfilled + 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-declarartion + +/////////////////////////////////////////////////////////////////////////////// +// 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 onject 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. Optional |method| is the HTTP request verb. If + // omitted, "GET" is used. + // Uses the default libcurl-based implementation of TransportInterface + Request(std::string const& url, char const* method); + Request(std::string const& url); + + // Custom constructor that allows non-default implementations + // of TransportInterface to be used. + Request(std::shared_ptr<TransportInterface> transport); + + // Gets/Sets "Accept:" header value. The default value is "*/*" if not set. + void SetAccept(char const* accept_mime_types); + std::string GetAccept() const; + + // Gets/Sets "Content-Type:" header value + void SetContentType(char const* content_type); + std::string GetContentType() const; + + // Adds additional HTTP request header + void AddHeader(char const* header, char const* value); + + // Removes HTTP request header + void RemoveHeader(char const* header); + + // Adds a request body. This is not to be used with GET method + bool AddRequestBody(void const* data, size_t size); + + // 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); + + // Gets/Sets an HTTP request verb to be used with request + void SetMethod(char const* method); + std::string GetMethod() const; + + // Returns the request URL + std::string GetRequestURL() const; + + // Gets/Sets a request referer URL (sent as "Referer:" request header). + void SetReferer(char const* referer); + std::string GetReferer() const; + + // Gets/Sets a user agent string (sent as "User-Agent:" request header). + void SetUserAgent(char const* 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). Calling GetErrorMessage() provides additional + // information in such as case. + std::unique_ptr<Response> GetResponse(); + + // If the request failed before reaching the server, returns additional + // information about the error occurred. + std::string GetErrorMessage() const; + + private: + std::shared_ptr<TransportInterface> transport_; + 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: + Response(std::shared_ptr<TransportInterface> transport); + + // 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 + 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(char const* header_name) const; + + private: + std::shared_ptr<TransportInterface> transport_; + DISALLOW_COPY_AND_ASSIGN(Response); +}; + +} // namespace http +} // namespace chromeos + +#endif // BUFFET_HTTP_REQUEST_H_
diff --git a/buffet/http_transport_curl.cc b/buffet/http_transport_curl.cc new file mode 100644 index 0000000..47090e9 --- /dev/null +++ b/buffet/http_transport_curl.cc
@@ -0,0 +1,259 @@ +// 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" + +#define __STDC_FORMAT_MACROS +#include <inttypes.h> +#include <string.h> +#include <base/logging.h> + +#include "buffet/mime_utils.h" +#include "buffet/string_utils.h" +#include "buffet/map_utils.h" + +using namespace chromeos; +using namespace chromeos::http::curl; + +Transport::Transport(std::string const& url, char const* method) : + request_url_(url), + method_(method ? method : request_type::kGet) { + stage_ = Stage::initialized; +} + +Transport::~Transport() { + Close(); +} + +void Transport::AddRange(int64_t bytes) { + if (bytes < 0) { + ranges_.emplace_back(Transport::range_value_omitted, -bytes); + } else { + ranges_.emplace_back(bytes, Transport::range_value_omitted); + } +} + +void Transport::AddRange(uint64_t fromByte, uint64_t toByte) { + ranges_.emplace_back(fromByte, toByte); +} + +std::string Transport::GetAccept() const { + return accept_; +} + +std::vector<std::pair<std::string, std::string>> Transport::GetHeaders() const { + auto 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()); + + return headers; +} + +void Transport::AddHeader(char const* header, char const* value) { + headers_[header] = value; +} + +void Transport::RemoveHeader(char const* header) { + AddHeader(header, ""); +} + +bool Transport::AddRequestBody(void const* data, size_t size) { + if (size == 0) + return true; + + if (data == nullptr) { + LOG(ERROR) << "Invalid request body data pointer"; + return false; + } + + unsigned char const* data_ptr = reinterpret_cast<unsigned char const*>(data); + request_data_.insert(request_data_.end(), data_ptr, data_ptr + size); + return true; +} + +bool Transport::Perform() { + if (stage_ != Stage::initialized) { + LOG(ERROR) << "Cannot call Perform() on unintialized transport object"; + return false; + } + + curl_handle_ = curl_easy_init(); + if (!curl_handle_) { + LOG(ERROR) << "Failed to initialize CURL"; + return false; + } + + curl_easy_setopt(curl_handle_, CURLOPT_URL, request_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::kPost) { + curl_easy_setopt(curl_handle_, CURLOPT_POST, 1L); + curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDS, nullptr); + if (!request_data_.empty()) { + curl_easy_setopt(curl_handle_, + CURLOPT_READFUNCTION, &Transport::read_callback); + curl_easy_setopt(curl_handle_, CURLOPT_READDATA, this); + } + curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDSIZE_LARGE, + curl_off_t(request_data_.size())); + } else if (method_ == request_type::kPut) { + curl_easy_setopt(curl_handle_, CURLOPT_UPLOAD, 1L); + curl_easy_setopt(curl_handle_, CURLOPT_INFILESIZE_LARGE, + curl_off_t(request_data_.size())); + curl_easy_setopt(curl_handle_, + CURLOPT_READFUNCTION, &Transport::read_callback); + curl_easy_setopt(curl_handle_, CURLOPT_READDATA, this); + } else { + curl_easy_setopt(curl_handle_, CURLOPT_CUSTOMREQUEST, method_.c_str()); + if (!request_data_.empty()) { + curl_easy_setopt(curl_handle_, + CURLOPT_READFUNCTION, &Transport::read_callback); + curl_easy_setopt(curl_handle_, CURLOPT_READDATA, this); + } + } + + // Setup HTTP response data. + if (method_ != request_type::kHead) { + curl_easy_setopt(curl_handle_, + CURLOPT_WRITEFUNCTION, &Transport::write_callback); + curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, this); + } + + // HTTP request headers + auto headers = GetHeaders(); + if (method_ != request_type::kGet && method_ != request_type::kHead) { + if (!content_type_.empty()) + headers.emplace_back(request_header::kContentType, content_type_); + } + + curl_slist* header_list = nullptr; + if (!headers.empty()) { + for (auto pair : headers) { + std::string header = string_utils::Join(": ", pair.first, pair.second); + header_list = curl_slist_append(header_list, header.c_str()); + } + curl_easy_setopt(curl_handle_, CURLOPT_HTTPHEADER, header_list); + } + + headers.clear(); + + // HTTP response headers + curl_easy_setopt(curl_handle_, + CURLOPT_HEADERFUNCTION, &Transport::header_callback); + curl_easy_setopt(curl_handle_, CURLOPT_HEADERDATA, this); + + CURLcode ret = curl_easy_perform(curl_handle_); + if (ret != CURLE_OK) { + error_ = curl_easy_strerror(ret); + stage_ = Stage::failed; + LOG(ERROR) << "CURL request failed: " << error_; + } else { + stage_ = Stage::response_received; + } + curl_slist_free_all(header_list); + return (ret == CURLE_OK); +} + +int Transport::GetResponseStatusCode() const { + if (stage_ != Stage::response_received) + return 0; + long status_code = 0; + curl_easy_getinfo(curl_handle_, CURLINFO_RESPONSE_CODE, &status_code); + return status_code; +} + +std::string Transport::GetResponseHeader(char const* headerName) const { + auto p = headers_.find(headerName); + return p != headers_.end() ? p->second : std::string(); +} + + +void Transport::Close() { + if (curl_handle_) { + curl_easy_cleanup(curl_handle_); + curl_handle_ = nullptr; + } + stage_ = Stage::closed; +} + +size_t Transport::write_callback(char* ptr, size_t size, + size_t num, void* data) { + Transport* me = reinterpret_cast<Transport*>(data); + size_t data_len = size * num; + me->response_data_.insert(me->response_data_.end(), ptr, ptr + data_len); + return data_len; +} + +size_t Transport::read_callback(char* ptr, size_t size, + size_t num, void* data) { + Transport* me = reinterpret_cast<Transport*>(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 Transport::header_callback(char* ptr, size_t size, + size_t num, void* data) { + Transport* me = reinterpret_cast<Transport*>(data); + size_t hdr_len = size * num; + std::string header(ptr, int(hdr_len)); + if (!me->status_text_set_) { + // First header - response code as "HTTP/1.1 200 OK". + // Need to extract the OK part + size_t pos = header.find(' '); + if(pos != std::string::npos) + pos = header.find(' ', pos + 1); + if (pos != std::string::npos) + me->status_text_ = header.substr(pos + 1); + me->status_text_set_ = true; + } else { + auto pair = string_utils::SplitAtFirst(header, ':'); + if (!pair.second.empty()) + me->headers_.insert(pair); + } + return hdr_len; +}
diff --git a/buffet/http_transport_curl.h b/buffet/http_transport_curl.h new file mode 100644 index 0000000..ab2ea2b --- /dev/null +++ b/buffet/http_transport_curl.h
@@ -0,0 +1,166 @@ +// 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 <map> +#include <curl/curl.h> + +#include "buffet/http_request.h" + +namespace chromeos { +namespace http { +namespace curl { + +/////////////////////////////////////////////////////////////////////////////// +// A particular implementation of TransportInterface that uses libcurl for +// HTTP communications. This class (as TransportInterface interface) +// is used by http::Request and http::Response classes to provide HTTP +// functionality to the clients. +/////////////////////////////////////////////////////////////////////////////// +class Transport : public TransportInterface { + public: + // Standard constructor. |url| is the full request URL with protocol + // schema, host address, resource path as well as optional query parameters + // and/or user name/password. |method| is one of HTTP request verbs such as + // "GET", "POST", etc. If nullptr is specified, "GET" is assumed. + Transport(std::string const& url, char const* method); + ~Transport(); + + // Returns the current request/response stage. + virtual Stage GetStage() const override { return stage_; } + + // Implementation of Request::AddRange. + virtual void AddRange(int64_t bytes) override; + virtual void AddRange(uint64_t from_byte, uint64_t to_byte) override; + + // Implementation of Request::SetAccept/Request::GetAccept. + virtual void SetAccept(char const* acceptMimeTypes) override { + accept_ = acceptMimeTypes; + } + virtual std::string GetAccept() const override; + + // Implementation of Request::GetRequestURL. + virtual std::string GetRequestURL() const override { return request_url_; } + + // Implementation of Request::SetContentType/Request::GetContentType. + virtual void SetContentType(char const* content_type) override { + content_type_ = content_type; + } + virtual std::string GetContentType() const override { return content_type_; } + + // Implementation of Request::AddHeader. + virtual void AddHeader(char const* header, char const* value) override; + + // Implementation of Request::RemoveHeader. + virtual void RemoveHeader(char const* header) override; + + // Implementation of Request::AddRequestBody. + virtual bool AddRequestBody(void const* data, size_t size) override; + + // Implementation of Request::SetMethod/Request::GetMethod. + virtual void SetMethod(char const* method) override { method_ = method; } + virtual std::string GetMethod() const override { return method_; } + + // Implementation of Request::SetReferer/Request::GetReferer. + virtual void SetReferer(char const* referer) override { referer_ = referer; } + virtual std::string GetReferer() const override { return referer_; } + + // Implementation of Request::SetUserAgent/Request::GetUserAgent. + virtual void SetUserAgent(char const* user_agent) override { + user_agent_ = user_agent; + } + virtual std::string GetUserAgent() const override { return user_agent_; } + + // Sends the HTTP request to the server. Used by Request::GetResponse(). + virtual bool Perform() override; + + // Implementation of Response::GetStatusCode. + virtual int GetResponseStatusCode() const override; + + // Implementation of Response::GetStatusText. + virtual std::string GetResponseStatusText() const override { + return status_text_; + } + + // Implementation of Response::GetHeader. + virtual std::string GetResponseHeader(char const* header_name) const override; + + // Implementation of Response::GetData. + virtual std::vector<unsigned char> const& GetResponseData() const override { + return response_data_; + } + + // Implementation of Response::GetErrorMessage. + virtual std::string GetErrorMessage() const override { return error_; } + + // Closes the connection and frees up internal data + virtual void Close() override; + + private: + std::vector<std::pair<std::string, std::string>> GetHeaders() const; + + // 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); + + // 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 thr server as "Range: " header. + std::vector<std::pair<uint64_t, uint64_t>> ranges_; + // 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_; + + // Current progress stage. + Stage stage_ = Stage::failed; + // CURL error message in case request fails completely. + std::string error_; + // Reponse 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; + + // 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. + static const uint64_t range_value_omitted = (uint64_t)-1; + + CURL* curl_handle_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(Transport); +}; + +} // namespace curl +} // namespace http +} // namespace chromeos + +#endif // BUFFET_HTTP_TRANSPORT_CURL_H_
diff --git a/buffet/http_utils.cc b/buffet/http_utils.cc new file mode 100644 index 0000000..a921386 --- /dev/null +++ b/buffet/http_utils.cc
@@ -0,0 +1,88 @@ +// 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 <string.h> +#include <base/values.h> +#include <base/json/json_reader.h> +#include <base/json/json_writer.h> + +#include "mime_utils.h" + +namespace chromeos { +namespace http { + +std::unique_ptr<Response> Get(char const* url) { + Request request(url); + return request.GetResponse(); +} + +std::string GetAsString(char const* url) { + auto resp = Get(url); + return resp ? resp->GetDataAsString() : std::string(); +} + +std::unique_ptr<Response> Head(char const* url) { + Request request(url, request_type::kHead); + return request.GetResponse(); +} + +std::unique_ptr<Response> PostText(char const* url, + char const* data, + char const* mime_type) { + if (mime_type == nullptr) { + mime_type = chromeos::mime::application::kWwwFormUrlEncoded; + } + + return PostBinary(url, data, strlen(data), mime_type); +} + +std::unique_ptr<Response> PostBinary(char const* url, void const* data, + size_t data_size, char const* mime_type) { + if (mime_type == nullptr) { + mime_type = chromeos::mime::application::kOctet_stream; + } + + Request request(url, request_type::kPost); + request.SetContentType(mime_type); + request.AddRequestBody(data, data_size); + return request.GetResponse(); +} + +std::unique_ptr<Response> PostJson(char const* url, base::Value const* json) { + std::string data; + base::JSONWriter::Write(json, &data); + return PostBinary(url, data.c_str(), data.size(), mime::application::kJson); +} + +std::unique_ptr<base::Value> ParseJsonResponse(Response const* response, + std::string* error_message) { + std::unique_ptr<base::Value> value; + if (response) { + if (response->IsSuccessful()) { + // 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) { + std::string json = response->GetDataAsString(); + value.reset(base::JSONReader::ReadAndReturnError(json, + base::JSON_PARSE_RFC, + nullptr, + error_message)); + } + else if (error_message) { + *error_message = "Unexpected response content type: " + content_type; + } + } + } else if (error_message) { + *error_message = "NULL response."; + } + return value; +} + +} // namespace http +} // namespace chromeos
diff --git a/buffet/http_utils.h b/buffet/http_utils.h new file mode 100644 index 0000000..0fff847 --- /dev/null +++ b/buffet/http_utils.h
@@ -0,0 +1,79 @@ +// 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/http_request.h" + +namespace base { class Value; } + +namespace chromeos { +namespace http { + +//////////////////////////////////////////////////////////////////////////////// +// 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 simple GET request and returns the data as a string. +std::string GetAsString(char const* url); + +// 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(char const* url); + +// 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(char const* url); + +// 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(char const* url, + void const* data, + size_t data_size, + char const* mime_type); + +inline std::unique_ptr<Response> PostBinary(char const* url, + void const* data, + size_t data_size) { + return PostBinary(url, data, data_size, nullptr); +} + +// 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(char const* url, + char const* data, + char const* mime_type); + +inline std::unique_ptr<Response> PostText(char const* url, char const* data) { + return PostText(url, data, nullptr); +} + +// 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(char const* url, base::Value const* json); + +// Given an http::Response object, parse the body data into Json object. +// Returns null if failed. Optional |error_message| can be passed in to +// get the extended error information as to why the parse failed. +std::unique_ptr<base::Value> ParseJsonResponse(Response const* response, + std::string* error_message); + +} // namespace http +} // namespace chromeos + +#endif // BUFFET_HTTP_UTILS_H_
diff --git a/buffet/main.cc b/buffet/main.cc new file mode 100644 index 0000000..0b2e493 --- /dev/null +++ b/buffet/main.cc
@@ -0,0 +1,87 @@ +// 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 <gflags/gflags.h> + +#include "buffet/dbus_manager.h" + +DEFINE_string(logsroot, "/var/log", "Root directory for buffet logs."); + +namespace { + +// 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 |logs_root|/buffet. +std::string SetupLogFile(const std::string& logs_root) { + const auto log_symlink = logs_root + "/buffet.log"; + const auto logs_dir = logs_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& logs_root) { + const auto log_file = SetupLogFile(logs_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); +} + +} // namespace + +int main(int argc, char* argv[]) { + // Parse the args and check for extra args. + CommandLine::Init(argc, argv); + google::ParseCommandLineFlags(&argc, &argv, true); + CHECK_EQ(argc, 1) << "Unexpected arguments. Try --help"; + + SetupLogging(FLAGS_logsroot); + + base::AtExitManager at_exit_manager; + base::MessageLoopForIO message_loop; + + // Initialize the dbus_manager. + buffet::DBusManager dbus_manager; + dbus_manager.Init(); + + message_loop.Run(); + + dbus_manager.Finalize(); + return 0; +}
diff --git a/buffet/map_utils.h b/buffet/map_utils.h new file mode 100644 index 0000000..676c558 --- /dev/null +++ b/buffet/map_utils.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_MAP_UTILS_H_ +#define BUFFET_MAP_UTILS_H_ + +#include <map> +#include <vector> + +namespace chromeos { + +// Given an STL map returns a vector containing all keys from the map +template<typename T> +std::vector<typename T::key_type> GetMapKeys(T const& 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(T const& 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(T const& 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 chromeos + +#endif // BUFFET_MAP_UTILS_H_
diff --git a/buffet/mime_utils.cc b/buffet/mime_utils.cc new file mode 100644 index 0000000..a4f29ca --- /dev/null +++ b/buffet/mime_utils.cc
@@ -0,0 +1,154 @@ +// 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" + +using namespace chromeos; + +//*************************************************************************** +//******************************* 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(std::string const& 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(std::string const& 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(std::string const& 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(std::string const& 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(std::string const& type, std::string const& subtype, + mime::Parameters const& parameters) { + std::vector<std::string> parts; + parts.push_back(string_utils::Join('/', type, subtype)); + for (std::pair<std::string, std::string> const& pair : parameters) { + parts.push_back(string_utils::Join('=', pair.first, + EncodeParam(pair.second))); + } + return string_utils::Join("; ", parts); +} + +std::string mime::GetType(std::string const& mime_string) { + std::string mime = mime::RemoveParameters(mime_string); + return string_utils::SplitAtFirst(mime, '/').first; +} + +std::string mime::GetSubtype(std::string const& mime_string) { + std::string mime = mime::RemoveParameters(mime_string); + return string_utils::SplitAtFirst(mime, '/').second; +} + +mime::Parameters mime::GetParameters(std::string const& 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(std::string const& mime_string) { + return string_utils::SplitAtFirst(mime_string, ';').first; +} + +std::string mime::AppendParameter(std::string const& mime_string, + std::string const& paramName, + std::string const& paramValue) { + std::string mime(mime_string); + mime += "; "; + mime += string_utils::Join('=', paramName, EncodeParam(paramValue)); + return mime; +} + +std::string mime::GetParameterValue(std::string const& mime_string, + std::string const& paramName) { + mime::Parameters params = mime::GetParameters(mime_string); + for(auto const& pair : params) { + if (base::strcasecmp(pair.first.c_str(), paramName.c_str()) == 0) + return pair.second; + } + return std::string(); +}
diff --git a/buffet/mime_utils.h b/buffet/mime_utils.h new file mode 100644 index 0000000..e1dc7da --- /dev/null +++ b/buffet/mime_utils.h
@@ -0,0 +1,102 @@ +// 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 <vector> + +namespace chromeos { + +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(std::string const& type, std::string const& subtype, + Parameters const& parameters = Parameters()); + +// Splits a MIME string into type and subtype. +// "text/plain;charset=utf-8" => ("text", "plain") +bool Split(std::string const& 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(std::string const& 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(std::string const& mime_string); + +// Returns the MIME sub-type from MIME string. +// "text/plain;charset=utf-8" => "plain" +std::string GetSubtype(std::string const& mime_string); + +// Returns the MIME parameters from MIME string. +// "text/plain;charset=utf-8" => {{"charset","utf-8"}} +Parameters GetParameters(std::string const& mime_string); + +// Removes parameters from a MIME string +// "text/plain;charset=utf-8" => "text/plain" +std::string RemoveParameters(std::string const& mime_string); + +// Appends a parameter to a MIME string. +// "text/plain" => "text/plain; charset=utf-8" +std::string AppendParameter(std::string const& mime_string, + std::string const& paramName, + std::string const& paramValue); + +// 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(std::string const& mime_string, + std::string const& paramName); + +} // namespace mime +} // namespace chromeos + +#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..893976a --- /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 chromeos; + +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/string_utils.cc b/buffet/string_utils.cc new file mode 100644 index 0000000..49a7d84 --- /dev/null +++ b/buffet/string_utils.cc
@@ -0,0 +1,87 @@ +// 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 <base/strings/string_util.h> + +namespace chromeos { +namespace string_utils { + +std::vector<std::string> Split(std::string const& str, + char delimiter, + bool trim_whitespaces, + bool purge_empty_strings) { + std::vector<std::string> tokens; + if (delimiter == 0) + return tokens; + + char const* sz = str.c_str(); + if (sz) { + char const* 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) { + TrimWhitespaceASCII(str, TRIM_ALL, &str); }); + } + + return tokens; +} + +std::pair<std::string, std::string> SplitAtFirst(std::string const& str, + char delimiter, + bool trim_whitespaces) { + std::pair<std::string, std::string> pair; + if (delimiter == 0) + return pair; + + char const* sz = str.c_str(); + char const* 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) { + TrimWhitespaceASCII(pair.first, TRIM_ALL, &pair.first); + TrimWhitespaceASCII(pair.second, TRIM_ALL, &pair.second); + } + + return pair; +} + +std::string Join(char delimiter, std::vector<std::string> const& strings) { + return JoinString(strings, delimiter); +} + +std::string Join(std::string const& delimiter, + std::vector<std::string> const& strings) { + return JoinString(strings, delimiter); +} + +std::string Join(char delimiter, + std::string const& str1, std::string const& str2) { + return str1 + delimiter + str2; +} + +std::string Join(std::string const& delimiter, + std::string const& str1, std::string const& str2) { + return str1 + delimiter + str2; +} + +} // namespace string_utils +} // namespace chromeos
diff --git a/buffet/string_utils.h b/buffet/string_utils.h new file mode 100644 index 0000000..bad62f3 --- /dev/null +++ b/buffet/string_utils.h
@@ -0,0 +1,41 @@ +// 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 <vector> + +namespace chromeos { +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(std::string const& 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(std::string const& str, + char delimiter, + bool trim_whitespaces = true); + +// Joins an array of strings into a single string separated by |delimiter|. +std::string Join(char delimiter, std::vector<std::string> const& strings); +std::string Join(std::string const& delimiter, + std::vector<std::string> const& strings); +std::string Join(char delimiter, + std::string const& str1, std::string const& str2); +std::string Join(std::string const& delimiter, + std::string const& str1, std::string const& str2); + +} // namespace string_utils +} // namespace chromeos + +#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..23bcef1 --- /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 chromeos; + +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/transport_interface.h b/buffet/transport_interface.h new file mode 100644 index 0000000..fa858c6 --- /dev/null +++ b/buffet/transport_interface.h
@@ -0,0 +1,79 @@ +// 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_TRANSPORT_INTERFACE_H_ +#define BUFFET_TRANSPORT_INTERFACE_H_ + +#include <vector> +#include <string> +#include <base/basictypes.h> + +namespace chromeos { +namespace http { + +/////////////////////////////////////////////////////////////////////////////// +// TransportInterface is an interface to abstract specific implementation +// of HTTP communication. This interface (and its underlying implementation) +// is used by http::Request and http::Response classes to provide HTTP +// functionality to the clients. This interface should be of no interest +// to the clients unless they want to implement/use their own network library. +/////////////////////////////////////////////////////////////////////////////// +class TransportInterface { + public: + enum class Stage { + initialized, + response_received, + failed, + closed + }; + + virtual ~TransportInterface() {} + + virtual Stage GetStage() const = 0; + + virtual void AddRange(int64_t bytes) = 0; + virtual void AddRange(uint64_t from_byte, uint64_t to_byte) = 0; + + virtual void SetAccept(char const* accept_mime_types) = 0; + virtual std::string GetAccept() const = 0; + + virtual std::string GetRequestURL() const = 0; + + virtual void SetContentType(char const* content_type) = 0; + virtual std::string GetContentType() const = 0; + + virtual void AddHeader(char const* header, char const* value) = 0; + virtual void RemoveHeader(char const* header) = 0; + + virtual bool AddRequestBody(void const* data, size_t size) = 0; + + virtual void SetMethod(char const* method) = 0; + virtual std::string GetMethod() const = 0; + + virtual void SetReferer(char const* referer) = 0; + virtual std::string GetReferer() const = 0; + + virtual void SetUserAgent(char const* user_agent) = 0; + virtual std::string GetUserAgent() const = 0; + + virtual bool Perform() = 0; + + virtual int GetResponseStatusCode() const = 0; + virtual std::string GetResponseStatusText() const = 0; + + virtual std::string GetResponseHeader(char const* header_name) const = 0; + virtual std::vector<unsigned char> const& GetResponseData() const = 0; + virtual std::string GetErrorMessage() const = 0; + + virtual void Close() = 0; + + protected: + TransportInterface() {} + DISALLOW_COPY_AND_ASSIGN(TransportInterface); +}; + +} // namespace http +} // namespace chromeos + +#endif // BUFFET_TRANSPORT_INTERFACE_H_