#include #include #include #include "client.h" #include "models.h" MastodonClient mastodon_client; static void share_lock(CURL* curl, curl_lock_data data, curl_lock_access access, void* clientp); static void share_unlock(CURL* curl, curl_lock_data data, void* clientp); static size_t curl_write_cb(char* ptr, size_t size, size_t nmemb, void* userdata); template void setopt(CURLSH* share, CURLSHoption option, A parameter) { CURLSHcode code = curl_share_setopt(share, option, parameter); if (code) { throw CurlShareException(code); } } template void setopt(CURL* curl, CURLoption option, A parameter) { CURLcode code = curl_easy_setopt(curl, option, parameter); if (code) { throw CurlException(code); } } MastodonClient::MastodonClient() { this->_share = curl_share_init(); if (!this->_share) { throw std::bad_alloc(); } try { setopt(this->_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); setopt(this->_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION); setopt(this->_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT); setopt(this->_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL); setopt(this->_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_HSTS); setopt(this->_share, CURLSHOPT_LOCKFUNC, share_lock); setopt(this->_share, CURLSHOPT_UNLOCKFUNC, share_unlock); setopt(this->_share, CURLSHOPT_USERDATA, this->_share_locks); } catch (const std::exception& e) { curl_share_cleanup(this->_share); throw; } int err = pthread_key_create(&this->_easy_key, curl_easy_cleanup); if (err) { curl_share_cleanup(this->_share); throw std::system_error(err, std::generic_category(), "pthread_key_create()"); } } MastodonClient::~MastodonClient() { curl_share_cleanup(this->_share); int err = pthread_key_delete(this->_easy_key); if (err) { perror("pthread_key_delete()"); } } std::optional MastodonClient::get_account_by_username(const std::string& host, const std::string& username) { using namespace std::string_literals; try { std::string resp = this->_send_request("https://"s + host + "/api/v1/accounts/lookup?acct=" + username); Account res = nlohmann::json::parse(std::move(resp)); res.same_server = host == res.server; return res; } catch (const CurlException& e) { if (e.code != CURLE_HTTP_RETURNED_ERROR || this->_response_status_code() != 404) { throw; } return std::nullopt; } } std::optional MastodonClient::get_post(const std::string& host, const std::string& id) { using namespace std::string_literals; try { std::string resp = this->_send_request("https://"s + host + "/api/v1/statuses/" + id); Post post = nlohmann::json::parse(std::move(resp)); post.account.same_server = host == post.account.server; if (post.reblog) { post.reblog->account.same_server = host == post.reblog->account.server; } return post; } catch (const CurlException& e) { if (e.code != CURLE_HTTP_RETURNED_ERROR || this->_response_status_code() != 404) { throw; } return std::nullopt; } } CURL* MastodonClient::_get_easy() { CURL* curl = pthread_getspecific(this->_easy_key); if (!curl) { curl = curl_easy_init(); if (!curl) { throw std::bad_alloc(); } try { setopt(curl, CURLOPT_FAILONERROR, 1L); setopt(curl, CURLOPT_TIMEOUT_MS, 10000L); setopt(curl, CURLOPT_PROTOCOLS_STR, "https"); setopt(curl, CURLOPT_USERAGENT, "Coyote (https://gitlab.com/blankX/coyote; blankie@nixnetmail.com)"); setopt(curl, CURLOPT_SHARE, this->_share); } catch (const std::exception& e) { curl_easy_cleanup(curl); throw; } int err = pthread_setspecific(this->_easy_key, curl); if (err) { curl_easy_cleanup(curl); throw std::system_error(err, std::generic_category(), "pthread_setspecific()"); } } return curl; } std::string MastodonClient::_send_request(const std::string& url) { std::string res; CURL* curl = this->_get_easy(); setopt(curl, CURLOPT_URL, url.c_str()); setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_cb); setopt(curl, CURLOPT_WRITEDATA, &res); CURLcode code = curl_easy_perform(curl); if (code) { throw CurlException(code); } return res; } long MastodonClient::_response_status_code() { long response_code; CURLcode code = curl_easy_getinfo(this->_get_easy(), CURLINFO_RESPONSE_CODE, &response_code); if (code) { throw CurlException(code); } return response_code; } static void share_lock(CURL* curl, curl_lock_data data, curl_lock_access access, void* clientp) { (void)curl; (void)access; std::mutex* mutexes = reinterpret_cast(clientp); assert(CURL_LOCK_DATA_LAST > data); mutexes[data].lock(); } static void share_unlock(CURL* curl, curl_lock_data data, void* clientp) { (void)curl; std::mutex* mutexes = reinterpret_cast(clientp); assert(CURL_LOCK_DATA_LAST > data); mutexes[data].unlock(); } static size_t curl_write_cb(char* ptr, size_t size, size_t nmemb, void* userdata) { size_t realsize = size * nmemb; std::string* str = reinterpret_cast(userdata); // XXX should we bother with exceptions str->append(ptr, realsize); return realsize; }