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