From 1844f3571a457cb6190c6a1571bed0b27243bac6 Mon Sep 17 00:00:00 2001 From: tastytea Date: Wed, 17 Jan 2018 23:51:59 +0100 Subject: [PATCH] Added percent-encoding --- CMakeLists.txt | 2 +- README.md | 4 ++-- src/api_get.cpp | 37 +++++++++++++++++++------------------ src/http_sync.cpp | 8 +++++--- src/mastodon-cpp.cpp | 30 ++++++++++++++++++++++++++++-- src/mastodon-cpp.hpp | 17 ++++++++++++++++- 6 files changed, 71 insertions(+), 27 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 473cb82..2776847 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required (VERSION 3.7) include(GNUInstallDirs) project (mastodon-cpp - VERSION 0.1.2 + VERSION 0.1.3 LANGUAGES CXX ) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -fPIC") diff --git a/README.md b/README.md index a4469bb..65466e6 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Install with `make install`. # Usage -The HTML reference can be generated with `build_doc.sh`, if doxygen is installed. Or just look in `src/mastodon-cpp.hpp`. There are examples in `src/examples/`. +The HTML reference can be generated with `build_doc.sh`, if doxygen is installed. Or just look in `src/mastodon-cpp.hpp`. There are examples in `src/examples/`. All input is expected to be UTF-8. ## Most basic example @@ -86,6 +86,7 @@ If you use a debug build, you get more verbose error messages. * [x] Network stuff * [x] Comprehensive example * Version 0.2.0 + * [x] Escape user input * [ ] Implement all PATCH calls * [ ] Implement all POST calls * [ ] Implement all DELETE calls @@ -93,7 +94,6 @@ If you use a debug build, you get more verbose error messages. * [ ] Handle HTTP statuses 301 & 302 * [ ] Support registering as an application * Later - * [ ] Escape user input * [ ] Asynchronous I/O ## Status of implementation diff --git a/src/api_get.cpp b/src/api_get.cpp index 66f7e96..9f96418 100644 --- a/src/api_get.cpp +++ b/src/api_get.cpp @@ -100,65 +100,66 @@ const std::uint16_t API::get(const Mastodon::API::v1 &call, { string strcall = ""; bool firstparam = true; + const string argument_encoded = urlencode(argument); switch (call) { case v1::accounts_id: - strcall = "/api/v1/accounts/" + argument; + strcall = "/api/v1/accounts/" + argument_encoded; break; case v1::accounts_id_followers: - strcall = "/api/v1/accounts/" + argument + "/followers"; + strcall = "/api/v1/accounts/" + argument_encoded + "/followers"; break; case v1::accounts_id_following: - strcall = "/api/v1/accounts/" + argument + "/following"; + strcall = "/api/v1/accounts/" + argument_encoded + "/following"; break; case v1::accounts_id_statuses: - strcall = "/api/v1/accounts/" + argument + "/statuses"; + strcall = "/api/v1/accounts/" + argument_encoded + "/statuses"; break; case v1::accounts_relationships: - strcall = "/api/v1/accounts/relationships?id=" + argument; + strcall = "/api/v1/accounts/relationships?id=" + argument_encoded; firstparam = false; break; case v1::accounts_search: - strcall = "/api/v1/accounts/search?q=" + argument; + strcall = "/api/v1/accounts/search?q=" + argument_encoded; firstparam = false; break; case v1::accounts_id_lists: - strcall = "/api/v1/accounts/" + argument + "/lists"; + strcall = "/api/v1/accounts/" + argument_encoded + "/lists"; break; case v1::lists_id_accounts: - strcall = "/api/v1/lists/" + argument + "/accounts"; + strcall = "/api/v1/lists/" + argument_encoded + "/accounts"; break; case v1::lists_id: - strcall = "/api/v1/lists/" + argument; + strcall = "/api/v1/lists/" + argument_encoded; break; case v1::notifications_id: - strcall = "/api/v1/notifications/" + argument; + strcall = "/api/v1/notifications/" + argument_encoded; break; case v1::search: - strcall = "/api/v1/search?q=" + argument; + strcall = "/api/v1/search?q=" + argument_encoded; firstparam = false; break; case v1::statuses_id: - strcall = "/api/v1/statuses/" + argument; + strcall = "/api/v1/statuses/" + argument_encoded; break; case v1::statuses_id_context: - strcall = "/api/v1/statuses/" + argument + "/context"; + strcall = "/api/v1/statuses/" + argument_encoded + "/context"; break; case v1::statuses_id_card: - strcall = "/api/v1/statuses/" + argument + "/card"; + strcall = "/api/v1/statuses/" + argument_encoded + "/card"; break; case v1::statuses_id_reblogged_by: - strcall = "/api/v1/statuses/" + argument + "/reblogged_by"; + strcall = "/api/v1/statuses/" + argument_encoded + "/reblogged_by"; break; case v1::statuses_id_favourited_by: - strcall = "/api/v1/statuses/" + argument + "/favourited_by"; + strcall = "/api/v1/statuses/" + argument_encoded + "/favourited_by"; break; case v1::timelines_tag_hashtag: - strcall = "/api/v1/timelines/tag/" + argument; + strcall = "/api/v1/timelines/tag/" + argument_encoded; break; case v1::timelines_list_list_id: - strcall = "/api/v1/timelines/list/" + argument; + strcall = "/api/v1/timelines/list/" + argument_encoded; break; default: ttdebug << "ERROR: Invalid call.\n"; diff --git a/src/http_sync.cpp b/src/http_sync.cpp index a7fb153..2370378 100644 --- a/src/http_sync.cpp +++ b/src/http_sync.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include "macros.hpp" @@ -59,6 +60,7 @@ const std::uint16_t API::http::request_sync(const method &meth, const string &data, string &answer) { + ttdebug << "Path is: " << path << '\n'; try { tcp::resolver::query query(_instance, "https"); @@ -154,17 +156,17 @@ const std::uint16_t API::http::request_sync(const method &meth, // Read body boost::system::error_code error; answer = ""; - std::ostringstream ss; + std::ostringstream oss; while (boost::asio::read(_socket, response, boost::asio::transfer_at_least(1), error)) { - ss << &response; + oss << &response; } if (error != boost::asio::error::eof) { throw boost::system::system_error(error); } - answer = ss.str(); + answer = oss.str(); } catch (const std::exception &e) { diff --git a/src/mastodon-cpp.cpp b/src/mastodon-cpp.cpp index 8f518ae..9a86d14 100644 --- a/src/mastodon-cpp.cpp +++ b/src/mastodon-cpp.cpp @@ -58,7 +58,7 @@ const string API::maptostr(const parametermap &map, const bool &firstparam) { if (it.second.size() == 1) { - result += (delim + it.first + "=" + it.second.front()); + result += (delim + it.first + "=" + urlencode(it.second.front())); if (delim == '?') { delim = '&'; @@ -68,7 +68,7 @@ const string API::maptostr(const parametermap &map, const bool &firstparam) { for (const string &str : it.second) { - result += (delim + it.first + "[]=" + str); + result += (delim + it.first + "[]=" + urlencode(str)); if (delim == '?') { delim = '&'; @@ -80,3 +80,29 @@ 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 +{ + 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; + } + } + + ttdebug << "Unencoded string: " << str << '\n'; + ttdebug << "Encoded string: " << oss.str() << '\n'; + return oss.str(); +} diff --git a/src/mastodon-cpp.hpp b/src/mastodon-cpp.hpp index dc9e93f..0cef246 100644 --- a/src/mastodon-cpp.hpp +++ b/src/mastodon-cpp.hpp @@ -32,7 +32,7 @@ namespace Mastodon { /*! - * @brief Class for the Mastodon API. + * @brief Class for the Mastodon API. All input is expected to be UTF-8. * @section error Error codes * | Code | Explanation | * | --------: |:------------------------------| @@ -197,6 +197,21 @@ public: */ 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; + private: const std::string _instance; const std::string _access_token;