From 20410ece28b3ad523f1b4cd7fcb9ca99390b75e3 Mon Sep 17 00:00:00 2001 From: tastytea Date: Sun, 12 Jan 2020 15:24:05 +0100 Subject: [PATCH 01/13] Removed excess comma. --- examples/example06_update_name.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example06_update_name.cpp b/examples/example06_update_name.cpp index 699fc32..5232362 100644 --- a/examples/example06_update_name.cpp +++ b/examples/example06_update_name.cpp @@ -51,7 +51,7 @@ int main(int argc, char *argv[]) const auto answer{connection.patch( masto::API::v1::accounts_update_credentials, { - {"display_name", name}, + {"display_name", name} })}; if (answer) { From 7fc19639b16f4f2c08a30f15ab44b9efaba65835 Mon Sep 17 00:00:00 2001 From: tastytea Date: Sun, 12 Jan 2020 15:25:04 +0100 Subject: [PATCH 02/13] Add missing header. --- include/answer.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/answer.hpp b/include/answer.hpp index b0cafdf..b820a70 100644 --- a/include/answer.hpp +++ b/include/answer.hpp @@ -18,6 +18,7 @@ #define MASTODONPP_ANSWER_HPP #include +#include #include #include @@ -26,6 +27,7 @@ namespace mastodonpp using std::uint8_t; using std::uint16_t; +using std::ostream; using std::string; using std::string_view; @@ -97,8 +99,7 @@ struct answer_type * * @since 0.1.0 */ - friend std::ostream &operator <<(std::ostream &out, - const answer_type &answer); + friend ostream &operator <<(ostream &out, const answer_type &answer); /*! * @brief Returns the value of a header field. From 79c5087ca57de629badb72580a296231b242d849 Mon Sep 17 00:00:00 2001 From: tastytea Date: Sun, 12 Jan 2020 15:27:11 +0100 Subject: [PATCH 03/13] Marked some functions noexcept. --- include/instance.hpp | 14 +++--- src/instance.cpp | 114 +++++++++++++++++++++++++++---------------- 2 files changed, 79 insertions(+), 49 deletions(-) diff --git a/include/instance.hpp b/include/instance.hpp index 0667a83..7f74c46 100644 --- a/include/instance.hpp +++ b/include/instance.hpp @@ -61,7 +61,7 @@ public: * @since 0.1.0 */ [[nodiscard]] - inline string_view get_hostname() const + inline string_view get_hostname() const noexcept { return _hostname; } @@ -74,7 +74,7 @@ public: * @since 0.1.0 */ [[nodiscard]] - inline string_view get_baseuri() const + inline string_view get_baseuri() const noexcept { return _baseuri; } @@ -85,7 +85,7 @@ public: * @since 0.1.0 */ [[nodiscard]] - inline string_view get_access_token() const + inline string_view get_access_token() const noexcept { return _access_token; } @@ -115,7 +115,7 @@ public: * @since 0.1.0 */ [[nodiscard]] - uint64_t get_max_chars(); + uint64_t get_max_chars() noexcept; /*! @copydoc CURLWrapper::set_proxy(string_view) * @@ -136,7 +136,7 @@ public: * @since 0.1.0 */ [[nodiscard]] - string_view get_proxy() const + string_view get_proxy() const noexcept { return _proxy; } @@ -164,7 +164,7 @@ public: * * @since 0.3.0 */ - vector get_post_formats(); + vector get_post_formats() noexcept; /*! * @brief Set path to Certificate Authority (CA) bundle. @@ -187,7 +187,7 @@ public: * * @since 0.2.1 */ - string_view get_cainfo() + string_view get_cainfo() const noexcept { return _cainfo; } diff --git a/src/instance.cpp b/src/instance.cpp index 172489c..cc8ffeb 100644 --- a/src/instance.cpp +++ b/src/instance.cpp @@ -35,46 +35,48 @@ Instance::Instance(const string_view hostname, const string_view access_token) , _max_chars{0} {} -uint64_t Instance::get_max_chars() +uint64_t Instance::get_max_chars() noexcept { constexpr uint64_t default_max_chars{500}; - if (_max_chars == 0) + if (_max_chars != 0) { - try - { - debuglog << "Querying " << _hostname << " for max_toot_chars…\n"; - const auto answer{make_request(http_method::GET, - _baseuri + "/api/v1/instance", {})}; - if (!answer) - { - debuglog << "Could not get instance info.\n"; - return default_max_chars; - } + return _max_chars; + } - _max_chars = [&answer] - { - auto &body{answer.body}; - size_t pos_start{body.find("max_toot_chars")}; - if (pos_start == string::npos) - { - debuglog << "max_toot_chars not found.\n"; - return default_max_chars; - } - pos_start = body.find(':', pos_start) + 1; - const size_t pos_end{body.find(',', pos_start)}; - - const auto max_toot_chars{body.substr(pos_start, - pos_end - pos_start)}; - return static_cast(stoull(max_toot_chars)); - }(); - debuglog << "Set _max_chars to: " << _max_chars << '\n'; - } - catch (const exception &e) + try + { + debuglog << "Querying " << _hostname << " for max_toot_chars…\n"; + const auto answer{make_request(http_method::GET, + _baseuri + "/api/v1/instance", {})}; + if (!answer) { - debuglog << "Unexpected exception: " << e.what() << '\n'; + debuglog << "Could not get instance info.\n"; return default_max_chars; } + + _max_chars = [&answer] + { + auto &body{answer.body}; + size_t pos_start{body.find("max_toot_chars")}; + if (pos_start == string::npos) + { + debuglog << "max_toot_chars not found.\n"; + return default_max_chars; + } + pos_start = body.find(':', pos_start) + 1; + const size_t pos_end{body.find(',', pos_start)}; + + const auto max_toot_chars{body.substr(pos_start, + pos_end - pos_start)}; + return static_cast(stoull(max_toot_chars)); + }(); + debuglog << "Set _max_chars to: " << _max_chars << '\n'; + } + catch (const exception &e) + { + debuglog << "Unexpected exception: " << e.what() << '\n'; + return default_max_chars; } return _max_chars; @@ -107,7 +109,7 @@ answer_type Instance::get_nodeinfo() return make_request(http_method::GET, hrefs.back(), {}); } -vector Instance::get_post_formats() +vector Instance::get_post_formats() noexcept { constexpr auto default_value{"text/plain"}; @@ -116,18 +118,46 @@ vector Instance::get_post_formats() return _post_formats; } - debuglog << "Querying " << _hostname << " for postFormats…\n"; - const auto answer{get_nodeinfo()}; - if (!answer) + try { - debuglog << "Couldn't get NodeInfo.\n"; - _post_formats = {default_value}; - return _post_formats; + debuglog << "Querying " << _hostname << " for postFormats…\n"; + const auto answer{get_nodeinfo()}; + if (!answer) + { + debuglog << "Couldn't get NodeInfo.\n"; + _post_formats = {default_value}; + return _post_formats; + } + + constexpr string_view searchstring{R"("postFormats":[)"}; + auto pos{answer.body.find(searchstring)}; + if (pos == string::npos) + { + debuglog << "Couldn't find metadata.postFormats.\n"; + _post_formats = {default_value}; + return _post_formats; + } + pos += searchstring.size(); + auto endpos{answer.body.find("],", pos)}; + string formats{answer.body.substr(pos, endpos - pos)}; + debuglog << "Extracted postFormats: " << formats << '\n'; + + while ((pos = formats.find('"', 1)) != string::npos) + { + _post_formats.push_back(formats.substr(1, pos - 1)); + formats.erase(0, pos + 2); // 2 is the length of: ", + debuglog << "Found postFormat: " << _post_formats.back() << '\n'; + } + } + catch (const std::exception &e) + { + debuglog << "Unexpected exception: " << e.what() << '\n'; + return {default_value}; } - constexpr string_view searchstring{R"("postFormats":[)"}; - auto pos{answer.body.find(searchstring)}; - if (pos == string::npos) + return _post_formats; +} + { debuglog << "Couldn't find metadata.postFormats.\n"; _post_formats = {default_value}; From deeffc5adfbc48177bb983de701f598bef0cdae8 Mon Sep 17 00:00:00 2001 From: tastytea Date: Sun, 12 Jan 2020 16:06:44 +0100 Subject: [PATCH 04/13] Bugfix in endpoint translation. --- src/api.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api.cpp b/src/api.cpp index f395ef3..9a0dae6 100644 --- a/src/api.cpp +++ b/src/api.cpp @@ -28,7 +28,7 @@ API::API(const endpoint_type &endpoint) const map API::_endpoint_map { {v1::apps, "/api/v1/apps"}, - {v1::apps_verify_credentials, "/api/v1/apps/verify/credentials"}, + {v1::apps_verify_credentials, "/api/v1/apps/verify_credentials"}, {v1::accounts, "/api/v1/accounts"}, {v1::accounts_verify_credentials, "/api/v1/accounts/verify_credentials"}, From 02b0b05aa1ee694ef3940a2245989f31f2e8dc16 Mon Sep 17 00:00:00 2001 From: tastytea Date: Sun, 12 Jan 2020 16:28:04 +0100 Subject: [PATCH 05/13] Add Instance::ObtainToken. --- include/instance.hpp | 78 ++++++++++++++++++++++++++++ src/instance.cpp | 118 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 181 insertions(+), 15 deletions(-) diff --git a/include/instance.hpp b/include/instance.hpp index 7f74c46..874ea6a 100644 --- a/include/instance.hpp +++ b/include/instance.hpp @@ -192,6 +192,84 @@ public: return _cainfo; } + /*! + * @brief Simplifies obtaining an OAuth 2.0 Bearer Access Token. + * + * * Create an Instance() and initialize this class with it. + * * Call step_1() to get the URI your user has to visit. + * * Get the authorization code from your user. + * * Call step_2() with the code. + * + * Example: + * @code + * mastodonpp::Instance instance("example.com", {}); + * mastodonpp::Instance::ObtainToken token(instance); + * auto answer{token.step1("Good program", "read:blocks read:mutes", "")}; + * if (answer) + * { + * std::cout << "Please visit " << answer << "\nand paste the code: "; + * std::string code; + * std::cin >> code; + * answer = access_token{token.step2(code)}; + * if (answer) + * { + * std::cout << "Success!\n"; + * } + * } + * @endcode + * + * @since 0.3.0 + */ + class ObtainToken : public CURLWrapper + { + public: + ObtainToken(Instance &instance); + + /*! + * @brief Creates an application via `/api/v1/apps`. + * + * The `body` of the returned @link answer_type answer @endlink + * contains only the URI, not the whole JSON response. + * + * @param client_name The name of your application. + * @param scopes Space separated list of scopes. Defaults to + * “read” if empty. + * @param website The URI to the homepage of your application. Can + * be an empty string. + * + * @return The URI your user has to visit. + * + * @since 0.3.0 + */ + [[nodiscard]] + answer_type step_1(string_view client_name, string_view scopes, + string_view website); + + /*! + * @brief Creates a token via `/oauth/token`. + * + * The `body` of the returned @link answer_type answer @endlink + * contains only the access token, not the whole JSON response. + * + * The access token will be set in the parent Instance. + * + * @param code The authorization code you got from the user. + * + * @return The access token. + * + * @since 0.3.0 + */ + [[nodiscard]] + answer_type step_2(string_view code); + + private: + Instance &_instance; + const string _baseuri; + string _scopes; + string _client_id; + string _client_secret; + }; + private: const string _hostname; const string _baseuri; diff --git a/src/instance.cpp b/src/instance.cpp index cc8ffeb..19c6b8b 100644 --- a/src/instance.cpp +++ b/src/instance.cpp @@ -20,6 +20,7 @@ #include #include +#include namespace mastodonpp { @@ -27,6 +28,9 @@ namespace mastodonpp using std::sort; using std::stoull; using std::exception; +using std::regex; +using std::regex_search; +using std::smatch; Instance::Instance(const string_view hostname, const string_view access_token) : _hostname{hostname} @@ -158,24 +162,108 @@ vector Instance::get_post_formats() noexcept return _post_formats; } +Instance::ObtainToken::ObtainToken(Instance &instance) + : _instance{instance} + , _baseuri{instance.get_baseuri()} +{ + auto proxy{_instance.get_proxy()}; + if (!proxy.empty()) { - debuglog << "Couldn't find metadata.postFormats.\n"; - _post_formats = {default_value}; - return _post_formats; - } - pos += searchstring.size(); - auto endpos{answer.body.find("],", pos)}; - string formats{answer.body.substr(pos, endpos - pos)}; - debuglog << "Extracted postFormats: " << formats << '\n'; - - while ((pos = formats.find('"', 1)) != string::npos) - { - _post_formats.push_back(formats.substr(1, pos - 1)); - formats.erase(0, pos + 2); // 2 is the length of: ", - debuglog << "Found postFormat: " << _post_formats.back() << '\n'; + CURLWrapper::set_proxy(proxy); } - return _post_formats; + if (!_instance.get_access_token().empty()) + { + CURLWrapper::set_access_token(_instance.get_access_token()); + } + if (!_instance.get_cainfo().empty()) + { + CURLWrapper::set_cainfo(_instance.get_cainfo()); + } +} + +answer_type Instance::ObtainToken::step_1(const string_view client_name, + const string_view scopes, + const string_view website) +{ + parametermap parameters + { + {"client_name", client_name}, + {"redirect_uris", "urn:ietf:wg:oauth:2.0:oob"} + }; + if (!scopes.empty()) + { + _scopes = scopes; + parameters.insert({"scopes", scopes}); + } + if (!website.empty()) + { + parameters.insert({"website", website}); + } + + auto answer{make_request(http_method::POST, _baseuri + "/api/v1/apps", + parameters)}; + if (answer) + { + const regex re_id{R"("client_id"\s*:\s*"([^"]+)\")"}; + const regex re_secret{R"("client_secret"\s*:\s*"([^"]+)\")"}; + smatch match; + + if (regex_search(answer.body, match, re_id)) + { + _client_id = match[1].str(); + } + if (regex_search(answer.body, match, re_secret)) + { + _client_secret = match[1].str(); + } + + string uri{_baseuri + "/oauth/authorize?scope=" + escape_url(scopes) + + "&response_type=code" + "&redirect_uri=" + escape_url("urn:ietf:wg:oauth:2.0:oob") + + "&client_id=" + _client_id}; + if (!website.empty()) + { + uri += "&website=" + escape_url(website); + } + answer.body = uri; + debuglog << "Built URI."; + } + + return answer; +} + +answer_type Instance::ObtainToken::step_2(const string_view code) +{ + parametermap parameters + { + {"client_id", _client_id}, + {"client_secret", _client_secret}, + {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"}, + {"code", code}, + {"grant_type", "client_credentials"} + }; + if (!_scopes.empty()) + { + parameters.insert({"scope", _scopes}); + } + + auto answer{make_request(http_method::POST, _baseuri + "/oauth/token", + parameters)}; + if (answer) + { + const regex re_token{R"("access_token"\s*:\s*"([^"]+)\")"}; + smatch match; + + if (regex_search(answer.body, match, re_token)) + { + answer.body = match[1].str(); + debuglog << "Got access token.\n"; + _instance.set_access_token(answer.body); + } + } + + return answer; } } // namespace mastodonpp From 7af01ffb1771e5e5e16f21e885924a8e744049f5 Mon Sep 17 00:00:00 2001 From: tastytea Date: Sun, 12 Jan 2020 16:28:11 +0100 Subject: [PATCH 06/13] Add ObtainToken-example. --- examples/example08_obtain_token.cpp | 111 ++++++++++++++++++++++++++++ include/mastodonpp.hpp | 1 + 2 files changed, 112 insertions(+) create mode 100644 examples/example08_obtain_token.cpp diff --git a/examples/example08_obtain_token.cpp b/examples/example08_obtain_token.cpp new file mode 100644 index 0000000..8c78aef --- /dev/null +++ b/examples/example08_obtain_token.cpp @@ -0,0 +1,111 @@ +/* This file is part of mastodonpp. + * Copyright © 2020 tastytea + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +// Obtain an access token and verify that it works. + +#include "mastodonpp.hpp" + +#include +#include +#include +#include +#include + +namespace masto = mastodonpp; +using std::exit; +using std::cout; +using std::cerr; +using std::endl; +using std::cin; +using std::string; +using std::to_string; +using std::string_view; +using std::vector; + +void handle_error(const masto::answer_type &answer); + +int main(int argc, char *argv[]) +{ + const vector args(argv, argv + argc); + if (args.size() <= 1) + { + cerr << "Usage: " << args[0] << " \n"; + return 1; + } + + try + { + // Initialize Instance and Instance::ObtainToken. + masto::Instance instance{args[1], {}}; + masto::Instance::ObtainToken token{instance}; + + // Create an “Application” (/api/v1/apps), + // and get URI for the authorization code (/oauth/authorize). + auto answer{token.step_1("Testclient", "read:blocks read:mutes", + "https://tastytea.de/")}; + if (!answer) + { + handle_error(answer); + } + + cout << "Please visit " << answer << "\nand paste the code here: "; + string code; + cin >> code; + + // Obtain the token (/oauth/token). + answer = token.step_2(code); + if (!answer) + { + handle_error(answer); + } + + cout << "Your access token is: " << answer << endl; + + // Test if the token works. + masto::Connection connection{instance}; + answer = connection.get(masto::API::v1::apps_verify_credentials); + if (!answer) + { + handle_error(answer); + } + + cout << answer << endl; + } + catch (const masto::CURLException &e) + { + // Only libcurl errors that are not network errors will be thrown. + // There went probably something wrong with the initialization. + cerr << e.what() << endl; + } + + return 0; +} + +void handle_error(const masto::answer_type &answer) +{ + if (answer.curl_error_code == 0) + { + // If it is no libcurl error, it must be an HTTP error. + cerr << "HTTP status: " << answer.http_status << endl; + } + else + { + // Network errors like “Couldn't resolve host.”. + cerr << "libcurl error " << to_string(answer.curl_error_code) + << ": " << answer.error_message << endl; + } + + exit(1); +} diff --git a/include/mastodonpp.hpp b/include/mastodonpp.hpp index d6f8564..ca396d7 100644 --- a/include/mastodonpp.hpp +++ b/include/mastodonpp.hpp @@ -105,6 +105,7 @@ * @example example05_update_notification_settings.cpp * @example example06_update_name.cpp * @example example07_delete_status.cpp + * @example example08_obtain_token.cpp */ /*! From b192bc70c7b14bc32d1eed87714fa6a73096ab27 Mon Sep 17 00:00:00 2001 From: tastytea Date: Sun, 12 Jan 2020 16:30:20 +0100 Subject: [PATCH 07/13] Fix @since-tags that point to nonexisting versions. --- include/curl_wrapper.hpp | 4 ++-- include/instance.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/curl_wrapper.hpp b/include/curl_wrapper.hpp index cb5fc66..c20be33 100644 --- a/include/curl_wrapper.hpp +++ b/include/curl_wrapper.hpp @@ -259,7 +259,7 @@ protected: /*! * @brief Set path to Certificate Authority (CA) bundle. * - * @since 0.2.1 + * @since 0.3.0 */ void set_cainfo(string_view path); @@ -356,7 +356,7 @@ private: * @param data Data of the field. If it begins with `\@file:, the * rest of the ergument is treated as a filename. * - * @since 0.1.1 + * @since 0.2.0 */ void add_mime_part(curl_mime *mime, string_view name, string_view data) const; diff --git a/include/instance.hpp b/include/instance.hpp index 874ea6a..6185b7f 100644 --- a/include/instance.hpp +++ b/include/instance.hpp @@ -172,7 +172,7 @@ public: * Sets also the CA info for all Connection%s that are initialized with * this Instance afterwards. * - * @since 0.2.1 + * @since 0.3.0 */ void set_cainfo(string_view path) { @@ -185,7 +185,7 @@ public: * * This is used when initializing a Connection. * - * @since 0.2.1 + * @since 0.3.0 */ string_view get_cainfo() const noexcept { From 78ef177e58bf3b113d339038499544b0fa50284e Mon Sep 17 00:00:00 2001 From: tastytea Date: Sun, 12 Jan 2020 17:00:49 +0100 Subject: [PATCH 08/13] Hide CURLWrapper::set_proxy(). --- include/curl_wrapper.hpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/include/curl_wrapper.hpp b/include/curl_wrapper.hpp index c20be33..4b681c0 100644 --- a/include/curl_wrapper.hpp +++ b/include/curl_wrapper.hpp @@ -145,18 +145,6 @@ public: return _connection; } - /*! - * @brief Set the proxy to use. - * - * See [CURLOPT_PROXY(3)] - * (https://curl.haxx.se/libcurl/c/CURLOPT_PROXY.html). - * - * @param proxy Examples: "socks4a://127.0.0.1:9050", "http://[::1]:3128". - * - * @since 0.1.0 - */ - void set_proxy(string_view proxy); - /*! * @brief URL encodes the given string. * @@ -248,6 +236,18 @@ protected: _stream_cancelled = true; } + /*! + * @brief Set the proxy to use. + * + * See [CURLOPT_PROXY(3)] + * (https://curl.haxx.se/libcurl/c/CURLOPT_PROXY.html). + * + * @param proxy Examples: "socks4a://127.0.0.1:9050", "http://[::1]:3128". + * + * @since 0.1.0 + */ + void set_proxy(string_view proxy); + /*! * @brief Set OAuth 2.0 Bearer Access Token. * From b4428ed82379c4a9bcfa9c42d6980c267fce20d7 Mon Sep 17 00:00:00 2001 From: tastytea Date: Sun, 12 Jan 2020 17:01:09 +0100 Subject: [PATCH 09/13] Mark CURLWrapper::escape_url() and unescape_url() nodiscard. --- include/curl_wrapper.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/curl_wrapper.hpp b/include/curl_wrapper.hpp index 4b681c0..f8e567b 100644 --- a/include/curl_wrapper.hpp +++ b/include/curl_wrapper.hpp @@ -157,6 +157,7 @@ public: * * @since 0.3.0 */ + [[nodiscard]] inline string escape_url(const string_view url) const { char *cbuf{curl_easy_escape(_connection, url.data(), @@ -178,6 +179,7 @@ public: * * @since 0.3.0 */ + [[nodiscard]] inline string unescape_url(const string_view url) const { char *cbuf{curl_easy_unescape(_connection, url.data(), From 975fe576775aa67008ec0134337cd1cd2ba201e6 Mon Sep 17 00:00:00 2001 From: tastytea Date: Sun, 12 Jan 2020 17:21:58 +0100 Subject: [PATCH 10/13] Simplify connection setup. By adding CURLWrapper::setup_connection_properties. --- include/connection.hpp | 7 ++++++- include/curl_wrapper.hpp | 10 ++++++++++ include/instance.hpp | 30 ++++++++++++++++++++++++++++-- src/connection.cpp | 20 -------------------- src/curl_wrapper.cpp | 40 ++++++++++++++++++++++++++++++---------- src/instance.cpp | 27 --------------------------- 6 files changed, 74 insertions(+), 60 deletions(-) diff --git a/include/connection.hpp b/include/connection.hpp index 761f777..27119e4 100644 --- a/include/connection.hpp +++ b/include/connection.hpp @@ -82,7 +82,12 @@ public: * * @since 0.1.0 */ - explicit Connection(Instance &instance); + explicit Connection(Instance &instance) + : _instance{instance} + , _baseuri{instance.get_baseuri()} + { + _instance.copy_connection_properties(*this); + } /*! * @brief Make a HTTP GET call with parameters. diff --git a/include/curl_wrapper.hpp b/include/curl_wrapper.hpp index f8e567b..0759e01 100644 --- a/include/curl_wrapper.hpp +++ b/include/curl_wrapper.hpp @@ -189,6 +189,16 @@ public: return sbuf; } + /*! + * @brief Set some properties of the connection. + * + * Meant for internal use. See Instance::copy_connection_properties(). + * + * @since 0.3.0 + */ + void setup_connection_properties(string_view proxy, string_view access_token, + string_view cainfo); + protected: /*! * @brief Mutex for #get_buffer a.k.a. _curl_buffer_body. diff --git a/include/instance.hpp b/include/instance.hpp index 6185b7f..85756e9 100644 --- a/include/instance.hpp +++ b/include/instance.hpp @@ -53,7 +53,28 @@ public: * * @since 0.1.0 */ - explicit Instance(string_view hostname, string_view access_token); + explicit Instance(const string_view hostname, + const string_view access_token) + : _hostname{hostname} + , _baseuri{"https://" + _hostname} + , _access_token{access_token} + , _max_chars{0} + {} + + /*! + * @brief Set the properties of the connection of the calling class up. + * + * Meant for internal use. This aligns the properties of the connection of + * the calling class with the properties of connection of this class. + * + * @param curlwrapper The CURLWrapper parent of the calling class. + * + * @since 0.3.0 + */ + inline void copy_connection_properties(CURLWrapper &curlwrapper) + { + curlwrapper.setup_connection_properties(_proxy, _access_token, _cainfo); + } /*! * @brief Returns the hostname. @@ -223,7 +244,12 @@ public: class ObtainToken : public CURLWrapper { public: - ObtainToken(Instance &instance); + ObtainToken(Instance &instance) + : _instance{instance} + , _baseuri{instance.get_baseuri()} + { + _instance.copy_connection_properties(*this); + } /*! * @brief Creates an application via `/api/v1/apps`. diff --git a/src/connection.cpp b/src/connection.cpp index 0eba695..77e7ad0 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -21,26 +21,6 @@ namespace mastodonpp using std::holds_alternative; -Connection::Connection(Instance &instance) - : _instance{instance} - , _baseuri{instance.get_baseuri()} -{ - auto proxy{_instance.get_proxy()}; - if (!proxy.empty()) - { - CURLWrapper::set_proxy(proxy); - } - - if (!_instance.get_access_token().empty()) - { - CURLWrapper::set_access_token(_instance.get_access_token()); - } - if (!_instance.get_cainfo().empty()) - { - CURLWrapper::set_cainfo(_instance.get_cainfo()); - } -} - string Connection::endpoint_to_uri(const endpoint_variant &endpoint) const { if (holds_alternative(endpoint)) diff --git a/src/curl_wrapper.cpp b/src/curl_wrapper.cpp index 4cfb9b5..50e20ab 100644 --- a/src/curl_wrapper.cpp +++ b/src/curl_wrapper.cpp @@ -64,16 +64,6 @@ CURLWrapper::~CURLWrapper() noexcept } } -void CURLWrapper::set_proxy(const string_view proxy) -{ - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) - CURLcode code{curl_easy_setopt(_connection, CURLOPT_PROXY, proxy.data())}; - if (code != CURLE_OK) - { - throw CURLException{code, "Failed to set proxy", _curl_buffer_error}; - } -} - answer_type CURLWrapper::make_request(const http_method &method, string uri, const parametermap ¶meters) { @@ -197,6 +187,36 @@ answer_type CURLWrapper::make_request(const http_method &method, string uri, return answer; } +void CURLWrapper::setup_connection_properties(string_view proxy, + string_view access_token, + string_view cainfo) +{ + if (!proxy.empty()) + { + set_proxy(proxy); + } + + if (!access_token.empty()) + { + set_access_token(access_token); + } + + if (!cainfo.empty()) + { + set_cainfo(cainfo); + } +} + +void CURLWrapper::set_proxy(const string_view proxy) +{ + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + CURLcode code{curl_easy_setopt(_connection, CURLOPT_PROXY, proxy.data())}; + if (code != CURLE_OK) + { + throw CURLException{code, "Failed to set proxy", _curl_buffer_error}; + } +} + void CURLWrapper::set_access_token(const string_view access_token) { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg, hicpp-signed-bitwise) diff --git a/src/instance.cpp b/src/instance.cpp index 19c6b8b..cd99645 100644 --- a/src/instance.cpp +++ b/src/instance.cpp @@ -32,13 +32,6 @@ using std::regex; using std::regex_search; using std::smatch; -Instance::Instance(const string_view hostname, const string_view access_token) - : _hostname{hostname} - , _baseuri{"https://" + _hostname} - , _access_token{access_token} - , _max_chars{0} -{} - uint64_t Instance::get_max_chars() noexcept { constexpr uint64_t default_max_chars{500}; @@ -162,26 +155,6 @@ vector Instance::get_post_formats() noexcept return _post_formats; } -Instance::ObtainToken::ObtainToken(Instance &instance) - : _instance{instance} - , _baseuri{instance.get_baseuri()} -{ - auto proxy{_instance.get_proxy()}; - if (!proxy.empty()) - { - CURLWrapper::set_proxy(proxy); - } - - if (!_instance.get_access_token().empty()) - { - CURLWrapper::set_access_token(_instance.get_access_token()); - } - if (!_instance.get_cainfo().empty()) - { - CURLWrapper::set_cainfo(_instance.get_cainfo()); - } -} - answer_type Instance::ObtainToken::step_1(const string_view client_name, const string_view scopes, const string_view website) From 04526f37cc83bf13b1ba0715ede6826b3b053720 Mon Sep 17 00:00:00 2001 From: tastytea Date: Sun, 12 Jan 2020 17:27:40 +0100 Subject: [PATCH 11/13] Make argument of CURLWrapper::set_cafile() const. --- src/curl_wrapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/curl_wrapper.cpp b/src/curl_wrapper.cpp index 50e20ab..d5d96be 100644 --- a/src/curl_wrapper.cpp +++ b/src/curl_wrapper.cpp @@ -243,7 +243,7 @@ void CURLWrapper::set_access_token(const string_view access_token) debuglog << "Set authorization token.\n"; } -void CURLWrapper::set_cainfo(string_view path) +void CURLWrapper::set_cainfo(const string_view path) { CURLcode code{curl_easy_setopt(_connection, CURLOPT_CAINFO, path.data())}; if (code != CURLE_OK) From 1b4ad05acb1c3605c3a6949a746228d31da5e9da Mon Sep 17 00:00:00 2001 From: tastytea Date: Sun, 12 Jan 2020 17:35:11 +0100 Subject: [PATCH 12/13] Add set_useragent(). --- include/curl_wrapper.hpp | 8 ++++++-- include/instance.hpp | 10 +++++++++- src/curl_wrapper.cpp | 36 ++++++++++++++++++++++++------------ 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/include/curl_wrapper.hpp b/include/curl_wrapper.hpp index 0759e01..30fa1ce 100644 --- a/include/curl_wrapper.hpp +++ b/include/curl_wrapper.hpp @@ -196,8 +196,10 @@ public: * * @since 0.3.0 */ - void setup_connection_properties(string_view proxy, string_view access_token, - string_view cainfo); + void setup_connection_properties(string_view proxy, + string_view access_token, + string_view cainfo, + string_view useragent); protected: /*! @@ -275,6 +277,8 @@ protected: */ void set_cainfo(string_view path); + void set_useragent(string_view useragent); + private: CURL *_connection; char _curl_buffer_error[CURL_ERROR_SIZE]; diff --git a/include/instance.hpp b/include/instance.hpp index 85756e9..198746c 100644 --- a/include/instance.hpp +++ b/include/instance.hpp @@ -73,7 +73,8 @@ public: */ inline void copy_connection_properties(CURLWrapper &curlwrapper) { - curlwrapper.setup_connection_properties(_proxy, _access_token, _cainfo); + curlwrapper.setup_connection_properties(_proxy, _access_token, _cainfo, + _useragent); } /*! @@ -213,6 +214,12 @@ public: return _cainfo; } + void set_useragent(const string_view useragent) + { + _useragent = useragent; + CURLWrapper::set_useragent(useragent); + } + /*! * @brief Simplifies obtaining an OAuth 2.0 Bearer Access Token. * @@ -304,6 +311,7 @@ private: string _proxy; vector _post_formats; string _cainfo; + string _useragent; }; } // namespace mastodonpp diff --git a/src/curl_wrapper.cpp b/src/curl_wrapper.cpp index d5d96be..0fb0540 100644 --- a/src/curl_wrapper.cpp +++ b/src/curl_wrapper.cpp @@ -187,9 +187,10 @@ answer_type CURLWrapper::make_request(const http_method &method, string uri, return answer; } -void CURLWrapper::setup_connection_properties(string_view proxy, - string_view access_token, - string_view cainfo) +void CURLWrapper::setup_connection_properties(const string_view proxy, + const string_view access_token, + const string_view cainfo, + const string_view useragent) { if (!proxy.empty()) { @@ -205,6 +206,11 @@ void CURLWrapper::setup_connection_properties(string_view proxy, { set_cainfo(cainfo); } + + if (!useragent.empty()) + { + set_useragent(useragent); + } } void CURLWrapper::set_proxy(const string_view proxy) @@ -252,6 +258,19 @@ void CURLWrapper::set_cainfo(const string_view path) } } +void CURLWrapper::set_useragent(const string_view useragent) +{ + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + CURLcode code{curl_easy_setopt(_connection, CURLOPT_USERAGENT, + useragent.data())}; + if (code != CURLE_OK) + { + throw CURLException{code, "Failed to set User-Agent", + _curl_buffer_error}; + } + debuglog << "Set User-Agent to: " << useragent << '\n'; +} + size_t CURLWrapper::writer_body(char *data, size_t size, size_t nmemb) { if(data == nullptr) @@ -316,18 +335,11 @@ void CURLWrapper::setup_curl() // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) curl_easy_setopt(_connection, CURLOPT_NOPROGRESS, 0L); - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) - CURLcode code{curl_easy_setopt(_connection, CURLOPT_USERAGENT, - (string("mastodonpp/") += version).c_str())}; - if (code != CURLE_OK) - { - throw CURLException{code, "Failed to set User-Agent", - _curl_buffer_error}; - } + set_useragent((string("mastodonpp/") += version)); // The next 2 only fail if HTTP is not supported. // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) - code = curl_easy_setopt(_connection, CURLOPT_FOLLOWLOCATION, 1L); + CURLcode code{curl_easy_setopt(_connection, CURLOPT_FOLLOWLOCATION, 1L)}; if (code != CURLE_OK) { throw CURLException{code, "HTTP is not supported.", _curl_buffer_error}; From 9ae50917dd0f555424d2ca471d9ddfb74fe18042 Mon Sep 17 00:00:00 2001 From: tastytea Date: Sun, 12 Jan 2020 17:39:39 +0100 Subject: [PATCH 13/13] Remove Instance::get_proxy() and Instance::get_cainfo(). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We can't get the values that were set using environment variables or build-time options. We don't need them anymore for initializing “Connection”s. --- include/instance.hpp | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/include/instance.hpp b/include/instance.hpp index 198746c..df62646 100644 --- a/include/instance.hpp +++ b/include/instance.hpp @@ -150,19 +150,6 @@ public: CURLWrapper::set_proxy(proxy); } - /*! - * @brief Returns the proxy string that was previously set. - * - * Does not return the proxy if it was set from an environment variable. - * - * @since 0.1.0 - */ - [[nodiscard]] - string_view get_proxy() const noexcept - { - return _proxy; - } - /*! * @brief Returns the NodeInfo of the instance. * @@ -202,18 +189,6 @@ public: CURLWrapper::set_cainfo(path); } - /*! - * @brief Returns the cainfo path that was previously set. - * - * This is used when initializing a Connection. - * - * @since 0.3.0 - */ - string_view get_cainfo() const noexcept - { - return _cainfo; - } - void set_useragent(const string_view useragent) { _useragent = useragent;