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/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;
+}