Added percent-encoding

This commit is contained in:
tastytea 2018-01-17 23:51:59 +01:00
parent 5bba324385
commit 1844f3571a
Signed by: tastytea
GPG Key ID: 59346E0EA35C67E5
6 changed files with 71 additions and 27 deletions

View File

@ -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")

View File

@ -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

View File

@ -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";

View File

@ -19,6 +19,7 @@
#include <iostream>
#include <istream>
#include <ostream>
#include <sstream>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#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)
{

View File

@ -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();
}

View File

@ -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;