diff --git a/CMakeLists.txt b/CMakeLists.txt index f4e132d..d430ccc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required (VERSION 3.7) include(GNUInstallDirs) project (mastodon-cpp - VERSION 0.2.1 + VERSION 0.2.10 LANGUAGES CXX ) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") @@ -24,6 +24,9 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug") add_definitions(-DDEBUG=1) endif() +include(FindCURL) +find_package(CURL REQUIRED) + # Library file(GLOB sources src/*.cpp src/*.hpp) add_library(mastodon-cpp SHARED ${sources}) @@ -31,7 +34,7 @@ set_target_properties(mastodon-cpp PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${mastodon-cpp_VERSION_MAJOR} ) -target_link_libraries(mastodon-cpp boost_system ssl crypto) +target_link_libraries(mastodon-cpp ${CURL_LIBRARIES} ssl crypto curlpp) install(TARGETS mastodon-cpp LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) install(FILES ${PROJECT_SOURCE_DIR}/src/mastodon-cpp.hpp DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/src/http_sync.cpp b/src/http_sync.cpp index 57c1d75..cde4e23 100644 --- a/src/http_sync.cpp +++ b/src/http_sync.cpp @@ -17,201 +17,87 @@ #include #include #include -#include -#include #include -#include -#include +#include +#include +#include +#include #include "macros.hpp" #include "mastodon-cpp.hpp" using namespace Mastodon; +namespace curlopts = curlpp::options; using std::string; using std::cerr; -using boost::asio::ip::tcp; -namespace ssl = boost::asio::ssl; -typedef ssl::stream ssl_socket; - API::http::http(const API &api, const string &instance, const string &access_token) : parent(api) , _instance(instance) , _access_token(access_token) -, _ctx(ssl::context::tlsv12) -, _resolver(_io_service) -, _socket(_io_service, _ctx) { - _ctx.set_options(ssl::context::tlsv12 | ssl::context::tlsv11 | - ssl::context::no_sslv3 | ssl::context::no_sslv2 | - ssl::context::no_tlsv1); - _ctx.set_default_verify_paths(); + curlpp::initialize(); } const std::uint16_t API::http::request_sync(const method &meth, const string &path, string &answer) { - return request_sync(meth, path, "", answer); + return request_sync(meth, path, curlpp::Forms(), answer); } const std::uint16_t API::http::request_sync(const method &meth, const string &path, - const string &formdata, + const curlpp::Forms &formdata, string &answer) { ttdebug << "Path is: " << path << '\n'; + try { - tcp::resolver::query query(_instance, "https"); - tcp::resolver::iterator endpoint_iterator = _resolver.resolve(query); - boost::asio::connect(_socket.lowest_layer(), endpoint_iterator); - _socket.lowest_layer().set_option(tcp::no_delay(true)); - } - catch (const std::exception &e) - { - ttdebug << "ERROR: " << e.what() << "\n"; - return 16; - } + std::ostringstream oss; + curlpp::Easy request; + request.setOpt("https://" + _instance + path); + request.setOpt(parent.get_useragent()); + request.setOpt( + { + "Connection: close", + "Authorization: Bearer " + _access_token + }); + if (!formdata.empty()) + { + request.setOpt(formdata); + } - try - { - // Server Name Indication (SNI) - SSL_set_tlsext_host_name(_socket.native_handle(), _instance.c_str()); - - _socket.set_verify_mode(ssl::verify_peer); - _socket.set_verify_callback(ssl::rfc2818_verification(_instance)); - - _socket.handshake(ssl_socket::client); - } - catch (const std::exception &e) - { - ttdebug << "ERROR: " << e.what() << "\n"; - return 17; - } - - try - { - boost::asio::streambuf request; - std::ostream request_stream(&request); switch (meth) { case http::method::GET: - request_stream << "GET"; - ttdebug << "Method is GET\n"; break; case http::method::PATCH: - request_stream << "PATCH"; - ttdebug << "Method is PATCH\n"; + request.setOpt("PATCH"); break; case http::method::POST: - request_stream << "POST"; - ttdebug << "Method is POST\n"; + request.setOpt("POST"); break; case http::method::PUT: - request_stream << "PUT"; - ttdebug << "Method is PUT\n"; - break; + request.setOpt("PUT"); case http::method::DELETE: - request_stream << "DELETE"; - ttdebug << "Method is DELETE\n"; - 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"; - if (!_access_token.empty()) - { - request_stream << "Authorization: Bearer " - << _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: - case http::method::PUT: - case http::method::DELETE: - if (formdata.empty()) - { - request_stream << "\r\n"; - } - else - { - request_stream << formdata; - } + request.setOpt("DELETE"); default: break; } - boost::asio::write(_socket, request); - - boost::asio::streambuf response; - boost::asio::read_until(_socket, response, "\r\n"); - - // Check that response is OK. - std::istream response_stream(&response); - std::string http_version; - std::uint16_t status_code; - std::string status_message; - response_stream >> http_version; - response_stream >> status_code; - std::getline(response_stream, status_message); - if (!response_stream || http_version.substr(0, 5) != "HTTP/") - { - ttdebug << "ERROR: Invalid response from server\n"; - ttdebug << "Response was: " << http_version << " " << status_code - << " " << status_message << '\n'; - return 18; - } - if (status_code != 200) - { - ttdebug << "ERROR: Response returned with status code " - << status_code << ": " << status_message << "\n"; - return status_code; - } - - // 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; - answer = ""; - std::ostringstream oss; - while (boost::asio::read(_socket, response, - boost::asio::transfer_at_least(1), error)) - { - oss << &response; - } - if (error != boost::asio::error::eof) - { - // TODO: Find out why the "short read" error occurs - // with PATCH and POST - //throw boost::system::system_error(error); - ttdebug << "ERROR: " << error.message() << '\n'; - ttdebug << "The preceding error is ignored.\n"; - } + + oss << request; answer = oss.str(); - ttdebug << "Answer from server: " << oss.str() << '\n'; } - catch (const std::exception &e) + catch (curlpp::RuntimeError &e) { - ttdebug << "Exception: " << e.what() << "\n"; + cerr << "RUNTIME ERROR: " << e.what() << std::endl; + return 0xffff; + } + catch (curlpp::LogicError &e) + { + cerr << "LOGIC ERROR: " << e.what() << std::endl; return 0xffff; } diff --git a/src/mastodon-cpp.cpp b/src/mastodon-cpp.cpp index 09b0006..b6d19fb 100644 --- a/src/mastodon-cpp.cpp +++ b/src/mastodon-cpp.cpp @@ -17,6 +17,9 @@ #include #include #include +#include +#include +#include #include "version.hpp" #include "macros.hpp" #include "mastodon-cpp.hpp" @@ -104,58 +107,34 @@ const string API::maptostr(const parametermap &map, const bool &firstparam) ttdebug << "Constructed parameter string: " << result << '\n'; return result; } -const string API::maptoformdata(const parametermap &map) + +const curlpp::Forms API::maptoformdata(const parametermap &map) { + curlpp::Forms formdata; + if (map.size() == 0) { - return ""; + return formdata; } - - const string boundary = "MEEP"; - string header; - string body; - - header = "Content-type: multipart/form-data, boundary=" + boundary + "\r\n"; - header += "Content-Length: "; - body = "--" + boundary; for (const auto &it : map) { - // This is directly after the last boundary - body += "\r\n"; if (it.second.size() == 1) { - if (it.first == "avatar" || - it.first == "header" || - it.first == "file") - { - body += "Content-Transfer-Encoding: base64\r\n"; - } - else - { - body += "Content-Transfer-Encoding: 8bit\r\n"; - } - body += ("Content-Disposition: form-data; name=\"" + - it.first + "\"\r\n\r\n"); - body += (it.second.front() + "\r\n--" + boundary); + formdata.push_back(new curlpp::FormParts::Content(it.first, + it.second.front())); } else { for (const string &str : it.second) { - body += ("Content-Disposition: form-data; name=\"" + - it.first + "[]\"\r\n\r\n"); - body += (str + "\r\n--" + boundary); + formdata.push_back(new curlpp::FormParts::Content(it.first + "[]", + str)); } } } - // The last segment has to have "--" after the boundary - body += "--\r\n"; - header += (std::to_string(body.length()) + "\r\n\r\n"); - - ttdebug << "Form data: \n" << header << body; - return header + body; + return formdata; } // const string API::register_app(const std::string &instance, diff --git a/src/mastodon-cpp.hpp b/src/mastodon-cpp.hpp index c87a934..3d1cbd8 100644 --- a/src/mastodon-cpp.hpp +++ b/src/mastodon-cpp.hpp @@ -21,8 +21,8 @@ #include #include #include -#include -#include +#include +#include /*! * @example example1_dump_json.cpp @@ -436,7 +436,7 @@ private: */ const std::string maptostr(const parametermap &map, const bool &firstparam = true); - const std::string maptoformdata(const parametermap &map); + const curlpp::Forms maptoformdata(const parametermap &map); class http { @@ -468,17 +468,13 @@ private: */ const std::uint16_t request_sync(const method &meth, const std::string &path, - const std::string &formdata, + const curlpp::Forms &formdata, std::string &answer); private: const API &parent; const std::string _instance; const std::string _access_token; - boost::asio::ssl::context _ctx; - boost::asio::io_service _io_service; - boost::asio::ip::tcp::resolver _resolver; - boost::asio::ssl::stream _socket; } _http; }; }