blob: 292bfd8e07dd352f064c7ae9be0601e072b96f3a [file] [log] [blame]
// Copyright 2019 The Fuchsia 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 "src/developer/debug/zxdb/client/curl.h"
#include "src/developer/debug/shared/fd_watcher.h"
#include "src/developer/debug/shared/message_loop.h"
#include "src/lib/fxl/logging.h"
namespace zxdb {
namespace {
class CurlFDWatcher : public debug_ipc::FDWatcher {
public:
static CurlFDWatcher instance;
static std::map<int, debug_ipc::MessageLoop::WatchHandle> watches;
static bool cleanup_pending;
void OnFDReady(int fd, bool read, bool write, bool err) override;
private:
CurlFDWatcher() = default;
};
bool CurlFDWatcher::cleanup_pending = false;
CurlFDWatcher CurlFDWatcher::instance;
std::map<int, debug_ipc::MessageLoop::WatchHandle> CurlFDWatcher::watches;
void CurlFDWatcher::OnFDReady(int fd, bool read, bool write, bool err) {
int _ignore;
int action = 0;
if (read)
action |= CURL_CSELECT_IN;
if (write)
action |= CURL_CSELECT_OUT;
if (err)
action |= CURL_CSELECT_ERR;
auto result =
curl_multi_socket_action(Curl::multi_handle, fd, action, &_ignore);
FXL_DCHECK(result == CURLM_OK);
if (cleanup_pending) {
return;
}
cleanup_pending = true;
debug_ipc::MessageLoop::Current()->PostTask(FROM_HERE, []() {
cleanup_pending = false;
int _ignore;
while (auto info = curl_multi_info_read(Curl::multi_handle, &_ignore)) {
if (info->msg != CURLMSG_DONE) {
// CURLMSG_DONE is the only value for msg, documented or otherwise,
// so this is mostly future-proofing at writing.
continue;
}
Curl* curl;
auto result =
curl_easy_getinfo(info->easy_handle, CURLINFO_PRIVATE, &curl);
FXL_DCHECK(result == CURLE_OK);
auto cb = curl->multi_cb_;
curl->multi_cb_ = nullptr;
curl->FreeSList();
auto rem_result =
curl_multi_remove_handle(Curl::multi_handle, info->easy_handle);
FXL_DCHECK(rem_result == CURLM_OK);
auto ref = curl->self_ref_;
curl->self_ref_ = nullptr;
cb(ref.get(), Curl::Error(info->data.result));
}
});
}
// Callback given to CURL which it uses to inform us it would like to do IO on
// a socket and that we should add it to our polling in the event loop.
int SocketCallback(CURL* easy, curl_socket_t s, int what, void*, void*) {
if (what == CURL_POLL_REMOVE || what == CURL_POLL_NONE) {
CurlFDWatcher::watches.erase(s);
} else {
debug_ipc::MessageLoop::WatchMode mode;
switch (what) {
case CURL_POLL_IN:
mode = debug_ipc::MessageLoop::WatchMode::kRead;
break;
case CURL_POLL_OUT:
mode = debug_ipc::MessageLoop::WatchMode::kWrite;
break;
case CURL_POLL_INOUT:
mode = debug_ipc::MessageLoop::WatchMode::kReadWrite;
break;
default:
FXL_NOTREACHED();
return -1;
};
CurlFDWatcher::watches[s] = debug_ipc::MessageLoop::Current()->WatchFD(
mode, s, &CurlFDWatcher::instance);
}
return 0;
}
// Callback given to CURL which it uses to inform us it would like to receive a
// timer notification at a given time in the future. If the callback is called
// twice before the timer expires it is expected to re-schedule the existing
// timer, not make a second timer. A timeout of -1 means to cancel the
// outstanding timer.
int TimerCallback(CURLM* multi, long timeout_ms, void*) {
static std::shared_ptr<bool> last_timer = std::make_shared<bool>();
*last_timer = false;
if (timeout_ms < 0) {
return 0;
}
last_timer = std::make_shared<bool>(true);
debug_ipc::MessageLoop::Current()->PostTimer(
FROM_HERE, timeout_ms, [multi, valid = last_timer]() {
if (!*valid) {
return;
}
int _ignore;
auto result =
curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, &_ignore);
FXL_DCHECK(result == CURLM_OK);
});
return 0;
}
template <typename T>
void curl_easy_setopt_CHECK(CURL* handle, CURLoption option, T t) {
auto result = curl_easy_setopt(handle, option, t);
FXL_DCHECK(result == CURLE_OK);
}
} // namespace
size_t Curl::global_init = 0;
CURLM* Curl::multi_handle = nullptr;
size_t DoHeaderCallback(char* data, size_t size, size_t nitems, void* curl) {
Curl* self = reinterpret_cast<Curl*>(curl);
return self->header_callback_(std::string(data, size * nitems));
}
size_t DoDataCallback(char* data, size_t size, size_t nitems, void* curl) {
Curl* self = reinterpret_cast<Curl*>(curl);
return self->data_callback_(std::string(data, size * nitems));
}
Curl::Curl() {
if (!global_init++) {
auto res = curl_global_init(CURL_GLOBAL_SSL);
FXL_DCHECK(!res);
}
curl_ = curl_easy_init();
FXL_DCHECK(curl_);
// The curl handle has a private pointer which we can stash the address of
// our wrapper class in. Then anywhere the curl handle appears in the API we
// can grab our wrapper.
curl_easy_setopt_CHECK(curl_, CURLOPT_PRIVATE, this);
}
Curl::~Curl() {
FXL_DCHECK(!multi_cb_);
if (!curl_) {
return;
}
curl_easy_cleanup(curl_);
if (!--global_init) {
if (multi_handle) {
auto result = curl_multi_cleanup(multi_handle);
FXL_DCHECK(result == CURLM_OK);
multi_handle = nullptr;
}
curl_global_cleanup();
}
}
std::shared_ptr<Curl> Curl::MakeShared() {
auto ret = std::make_shared<Curl>();
ret->weak_self_ref_ = ret;
return ret;
}
void Curl::set_post_data(const std::map<std::string, std::string>& items) {
std::string encoded;
for (const auto& item : items) {
if (!encoded.empty()) {
encoded += "&";
}
encoded += Escape(item.first);
encoded += "=";
encoded += Escape(item.second);
}
set_post_data(std::move(encoded));
}
std::string Curl::Escape(const std::string& input) {
auto escaped = curl_easy_escape(curl_, input.c_str(), input.size());
std::string ret(escaped);
curl_free(escaped);
return ret;
}
void Curl::PrepareToPerform() {
FXL_DCHECK(!multi_cb_);
curl_easy_setopt_CHECK(curl_, CURLOPT_HEADERFUNCTION, DoHeaderCallback);
curl_easy_setopt_CHECK(curl_, CURLOPT_HEADERDATA, this);
curl_easy_setopt_CHECK(curl_, CURLOPT_WRITEFUNCTION, DoDataCallback);
curl_easy_setopt_CHECK(curl_, CURLOPT_WRITEDATA, this);
// API documentation specifies "A long value of 1" enables this option, so we
// convert very specifically. Why take chances on sensible behavior?
curl_easy_setopt_CHECK(curl_, CURLOPT_NOBODY, get_body_ ? 0L : 1L);
if (post_data_.empty()) {
curl_easy_setopt_CHECK(curl_, CURLOPT_POST, 0);
} else {
curl_easy_setopt_CHECK(curl_, CURLOPT_POSTFIELDS, post_data_.data());
curl_easy_setopt_CHECK(curl_, CURLOPT_POSTFIELDSIZE, post_data_.size());
}
FXL_DCHECK(!slist_);
for (const auto& header : headers_) {
slist_ = curl_slist_append(slist_, header.c_str());
}
curl_easy_setopt_CHECK(curl_, CURLOPT_HTTPHEADER, slist_);
}
void Curl::FreeSList() {
if (slist_)
curl_slist_free_all(slist_);
slist_ = nullptr;
}
Curl::Error Curl::Perform() {
PrepareToPerform();
auto ret = Error(curl_easy_perform(curl_));
FreeSList();
return ret;
}
void Curl::Perform(std::function<void(Curl*, Curl::Error)> cb) {
self_ref_ = weak_self_ref_.lock();
FXL_DCHECK(self_ref_)
<< "To use async Curl::Perform you must construct with Curl::MakeShared";
PrepareToPerform();
InitMulti();
auto result = curl_multi_add_handle(multi_handle, curl_);
FXL_DCHECK(result == CURLM_OK);
multi_cb_ = cb;
int _ignore;
result =
curl_multi_socket_action(multi_handle, CURL_SOCKET_TIMEOUT, 0, &_ignore);
FXL_DCHECK(result == CURLM_OK);
}
void Curl::InitMulti() {
if (multi_handle) {
return;
}
multi_handle = curl_multi_init();
FXL_DCHECK(multi_handle);
auto result =
curl_multi_setopt(multi_handle, CURLMOPT_SOCKETFUNCTION, SocketCallback);
FXL_DCHECK(result == CURLM_OK);
result =
curl_multi_setopt(multi_handle, CURLMOPT_TIMERFUNCTION, TimerCallback);
FXL_DCHECK(result == CURLM_OK);
}
long Curl::ResponseCode() {
long ret;
auto result = curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &ret);
FXL_DCHECK(result == CURLE_OK);
return ret;
}
} // namespace zxdb