From ac680f1ef5fa6cadfd54ab04c51b2978bd9fce62 Mon Sep 17 00:00:00 2001 From: tastytea Date: Sun, 21 Jan 2018 23:54:42 +0100 Subject: [PATCH] Added PATC, doesn't work (needs research) --- README.md | 1 + src/api_patch.cpp | 46 ++++++ src/examples/example4_update_credentials.cpp | 46 ++++++ src/http_sync.cpp | 50 ++++-- src/mastodon-cpp.cpp | 66 +++++--- src/mastodon-cpp.hpp | 162 ++++++++++++++++--- 6 files changed, 314 insertions(+), 57 deletions(-) create mode 100644 src/api_patch.cpp create mode 100644 src/examples/example4_update_credentials.cpp diff --git a/README.md b/README.md index 65466e6..246a789 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,7 @@ If you use a debug build, you get more verbose error messages. * [ ] Support registering as an application * Later * [ ] Asynchronous I/O + * [ ] Handle X-RateLimit header ## Status of implementation diff --git a/src/api_patch.cpp b/src/api_patch.cpp new file mode 100644 index 0000000..3836f79 --- /dev/null +++ b/src/api_patch.cpp @@ -0,0 +1,46 @@ +/* This file is part of mastodon-cpp. + * Copyright © 2018 tastytea + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include "macros.hpp" +#include "mastodon-cpp.hpp" + +using namespace Mastodon; +using std::string; +using std::cerr; +const std::uint16_t API::patch(const Mastodon::API::v1 &call, + const parametermap ¶meters, + string &answer) +{ + std::cerr << "NOT IMPLEMENTED\n"; + return 2; + string strcall = ""; + switch (call) + { + case v1::accounts_update_credentials: + strcall = "/api/v1/accounts/update_credentials"; + break; + default: + ttdebug << "ERROR: Invalid call.\n"; + return 1; + break; + } + + return _http.request_sync(API::http::method::PATCH, + strcall, maptoformdata(parameters), answer); +} diff --git a/src/examples/example4_update_credentials.cpp b/src/examples/example4_update_credentials.cpp new file mode 100644 index 0000000..d762b58 --- /dev/null +++ b/src/examples/example4_update_credentials.cpp @@ -0,0 +1,46 @@ +/* This file is part of mastodon-cpp. + */ + +#include +#include +#include +#include +#include "../mastodon-cpp.hpp" + +using Mastodon::API; + +int main(int argc, char *argv[]) +{ + std::cerr << "Doesn't work yet. :-(\n"; + return 2; + + if (argc < 3) + { + std::cerr << "usage: " << argv[0] << " \n"; + return 1; + } + + Mastodon::API masto(argv[1], argv[2]); + masto.set_useragent("mastodon-cpp-example/1.3.3.7"); + std::string answer; + std::uint16_t ret; + + API::parametermap params = + { + { "display_name", { "Botty McBotface" } }, + // { "note", { "Beep Bop." } } + }; + + ret = masto.patch(API::v1::accounts_update_credentials, params, answer); + if (ret == 0) + { + std::cout << answer << '\n'; + } + else + { + std::cerr << "Error code: " << ret << '\n'; + return ret; + } + + return 0; +} diff --git a/src/http_sync.cpp b/src/http_sync.cpp index 2370378..0cfa644 100644 --- a/src/http_sync.cpp +++ b/src/http_sync.cpp @@ -57,7 +57,7 @@ const std::uint16_t API::http::request_sync(const method &meth, const std::uint16_t API::http::request_sync(const method &meth, const string &path, - const string &data, + const string &formdata, string &answer) { ttdebug << "Path is: " << path << '\n'; @@ -98,28 +98,48 @@ const std::uint16_t API::http::request_sync(const method &meth, { case http::method::GET: request_stream << "GET"; - request_stream << " " << path; break; - // case http::method::PATCH: - // request_stream << "PATCH"; - // break; - // case http::method::POST: - // request_stream << "POST"; - // break; - // case http::method::DELETE: - // request_stream << "DELETE"; - // break; + case http::method::PATCH: + request_stream << "PATCH"; + break; + case http::method::POST: + request_stream << "POST"; + break; + case http::method::DELETE: + request_stream << "DELETE"; + break; default: ttdebug << "ERROR: Not implemented\n"; return 2; } + request_stream << " " << path; request_stream << " HTTP/1.0\r\n"; request_stream << "Host: " << _instance << "\r\n"; request_stream << "Accept: */*\r\n"; request_stream << "Connection: close\r\n"; request_stream << "User-Agent: " << parent.get_useragent() << "\r\n"; request_stream << "Authorization: Bearer " - << _access_token << "\r\n\r\n"; + << _access_token << "\r\n"; + switch (meth) + { + case http::method::GET: + request_stream << "\r\n"; + break; + case http::method::PATCH: + request_stream << formdata; + break; + case http::method::POST: + if (formdata.empty()) + { + request_stream << "\r\n"; + } + else + { + request_stream << formdata; + } + default: + break; + } boost::asio::write(_socket, request); boost::asio::streambuf response; @@ -150,8 +170,11 @@ const std::uint16_t API::http::request_sync(const method &meth, // Read headers boost::asio::read_until(_socket, response, "\r\n\r\n"); std::string header; + // ttdebug << "Header: \n"; while (std::getline(response_stream, header) && header != "\r") - {} + { + // ttdebug << header << '\n'; + } // Read body boost::system::error_code error; @@ -167,6 +190,7 @@ const std::uint16_t API::http::request_sync(const method &meth, throw boost::system::system_error(error); } answer = oss.str(); + ttdebug << "Answer from server: " << oss.str() << '\n'; } catch (const std::exception &e) { diff --git a/src/mastodon-cpp.cpp b/src/mastodon-cpp.cpp index 9a86d14..5c83262 100644 --- a/src/mastodon-cpp.cpp +++ b/src/mastodon-cpp.cpp @@ -45,6 +45,30 @@ const string API::get_useragent() const return _useragent; } +const std::string API::urlencode(const string &str) const +{ + std::ostringstream oss; + + for (const std::uint8_t &b: str) + { + // Check for unreserved characters (RFC 3986 section 2.3) + if ((b >= 0x30 && b <= 0x39) || // 0-9 + (b >= 0x41 && b <= 0x5A) || // A-Z + (b >= 0x61 && b <= 0x7A) || // a-z + b == 0x2D || b == 0x2E || // -, . + b == 0x5F || b == 0x7E) // _, ~ + { + oss << b; + } + else + { + oss << '%' << std::hex << std::uppercase << (int)(unsigned char)b; + } + } + + return oss.str(); +} + const string API::maptostr(const parametermap &map, const bool &firstparam) { string result = ""; @@ -80,29 +104,33 @@ const string API::maptostr(const parametermap &map, const bool &firstparam) ttdebug << "Constructed parameter string: " << result << '\n'; return result; } - -const std::string API::urlencode(const string &str) const +const string API::maptoformdata(const parametermap &map) { - std::ostringstream oss; - - for (const std::uint8_t &b: str) + if (map.size() == 0) { - // Check for unreserved characters (RFC 3986 section 2.3) - if ((b >= 0x30 && b <= 0x39) || // 0-9 - (b >= 0x41 && b <= 0x5A) || // A-Z - (b >= 0x61 && b <= 0x7A) || // a-z - b == 0x2D || b == 0x2E || // -, . - b == 0x5F || b == 0x7E) // _, ~ + return ""; + } + + const string boundary = "MEEP"; + string header; + string body; + + header = "Content-type: multipart/form-data, boundary=" + boundary + "\r\n"; + header += "Content-Length: "; + body = "--" + boundary + "\r\n"; + + for (const auto &it : map) + { + if (it.second.size() == 1) { - oss << b; - } - else - { - oss << '%' << std::hex << std::uppercase << (int)(unsigned char)b; + body += ("content-disposition: form-data; name=\"" + + it.first + "\"\r\n\r\n"); + body += (it.second.front() + "\r\n--" + boundary + "\r\n"); } } - ttdebug << "Unencoded string: " << str << '\n'; - ttdebug << "Encoded string: " << oss.str() << '\n'; - return oss.str(); + header += (std::to_string(body.length() - 2) + "\r\n\r\n"); + + ttdebug << "Form data: \n" << header << body; + return header + body; } diff --git a/src/mastodon-cpp.hpp b/src/mastodon-cpp.hpp index 0cef246..fe4c739 100644 --- a/src/mastodon-cpp.hpp +++ b/src/mastodon-cpp.hpp @@ -60,7 +60,7 @@ public: * } * @endcode */ - typedef std::multimap> parametermap; + typedef std::map> parametermap; /*! * @brief A list of all API calls. * @@ -99,7 +99,32 @@ public: timelines_home, timelines_public, timelines_tag_hashtag, - timelines_list_list_id + timelines_list_list_id, + // PATCH + accounts_update_credentials, + // POST + accounts_id_follow, + accounts_id_unfollow, + accounts_id_block, + accounts_id_unblock, + accounts_id_mute, + accounts_id_unmute, + apps, + follow_requests_id_authorize, + follow_requests_id_reject, + follows, + media, + notifications_clear, + notifications_dismiss, + statuses, + statuses_id_reblog, + statuses_id_unreblog, + statuses_id_favourite, + statuses_id_unfavourite, + statuses_id_pin, + statuses_id_unpin, + statuses_id_mute, + statuses_id_unmute }; /*! @@ -112,6 +137,35 @@ public: explicit API(const std::string &instance, const std::string &access_token); + /*! + * @brief Sets the useragent. Default is mastodon-cpp/version. + * + * @param useragent The useragent + */ + const void set_useragent(const std::string &useragent); + + /*! + * @brief Gets the useragent. + * + * @return The useragent. + */ + const std::string get_useragent() const; + + /*! + * @brief Percent-encodes a string. This is done automatically, unless you + * make a custom request. + * + * The only time you should use this, is if you use + * get(const std::string &call, std::string &answer). + * + * See RFC 3986 section 2.1 for more info. + * + * @param str The string + * + * @return The percent-encoded string + */ + const std::string urlencode(const std::string &str) const; + /*! * @brief Make a GET request which doesn't require an argument. * @@ -184,33 +238,92 @@ public: std::string &answer); /*! - * @brief Sets the useragent. Default is mastodon-cpp/version. + * @brief Make a PATCH request. * - * @param useragent The useragent + * Couldn't make it work yet. + * + * @param call A call defined in Mastodon::API::v1 + * @param parameters A Mastodon::API::parametermap containing optional + * parameters. + * @param answer The answer from the server. Usually JSON. On error + * an empty string. + * + * @return @ref error "Error code". */ - const void set_useragent(const std::string &useragent); + const std::uint16_t patch(const Mastodon::API::v1 &call, + const parametermap ¶meters, + std::string &answer); /*! - * @brief Gets the useragent. + * @brief Make a POST request which doesn't require an argument. * - * @return The useragent. + * @param call A call defined in Mastodon::API::v1 + * @param answer The answer from the server. Usually JSON. On error an + * empty string. + * + * @return @ref error "Error code". */ - const std::string get_useragent() const; + const std::uint16_t post(const Mastodon::API::v1 &call, std::string &answer); /*! - * @brief Percent-encodes a string. This is done automatically, unless you - * make a custom request. + * @brief Make a POST request which requires an argument * - * The only time you should use this, is if you use - * get(const std::string &call, std::string &answer). - * - * See RFC 3986 section 2.1 for more info. + * @param call A call defined in Mastodon::API::v1 + * @param argument The non-optional argument + * @param answer The answer from the server. Usually JSON. On error an + * empty string. * - * @param str The string - * - * @return The percent-encoded string + * @return @ref error "Error code". */ - const std::string urlencode(const std::string &str) const; + const std::uint16_t post(const Mastodon::API::v1 &call, + const std::string &argument, + std::string &answer); + + /*! + * @brief Make a POST request which doesn't require an argument, pass + * optional parameters. + * + * @param call A call defined in Mastodon::API::v1 + * @param parameters A Mastodon::API::parametermap containing optional + * parameters. + * @param answer The answer from the server. Usually JSON. On error + * an empty string. + * + * @return @ref error "Error code". + */ + const std::uint16_t post(const Mastodon::API::v1 &call, + const parametermap ¶meters, + std::string &answer); + + /*! + * @brief Make a POST request which requires an argument, pass optional + * parameters. + * + * @param call A call defined in Mastodon::API::v1 + * @param argument The non-optional argument + * @param parameters A Mastodon::API::parametermap containing optional + * parameters. + * @param answer The answer from the server. Usually JSON. On error + * an empty string. + * + * @return @ref error "Error code". + */ + const std::uint16_t post(const Mastodon::API::v1 &call, + const std::string &argument, + const parametermap ¶meters, + std::string &answer); + + /*! + * @brief Make a custom POST request. + * + * @param call String in the form `/api/v1/example` + * @param answer The answer from the server. Usually JSON. On error an + * empty string. + * + * @return @ref error "Error code". + */ + const std::uint16_t post(const std::string &call, + std::string &answer); private: const std::string _instance; @@ -227,6 +340,7 @@ private: */ const std::string maptostr(const parametermap &map, const bool &firstparam = true); + const std::string maptoformdata(const parametermap &map); class http { @@ -248,18 +362,16 @@ private: /*! * @brief Blocking request. * - * @param meth The method defined in http::method - * @param path The api call as string - * @param data The form data for PATCH and POST request. Not - * implemented at the moment. This will likely change - * into a std::vector. - * @param answer The answer from the server + * @param meth The method defined in http::method + * @param path The api call as string + * @param formdata The form data for PATCH and POST request. + * @param answer The answer from the server * * @return Error code. See README.md for details. */ const std::uint16_t request_sync(const method &meth, const std::string &path, - const std::string &data, + const std::string &formdata, std::string &answer); private: