2018-01-09 22:12:11 +01:00
|
|
|
/* This file is part of mastodon-cpp.
|
|
|
|
* Copyright © 2018 tastytea <tastytea@tastytea.de>
|
|
|
|
*
|
|
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <string>
|
|
|
|
#include <cstdint>
|
|
|
|
#include <iostream>
|
|
|
|
#include <istream>
|
|
|
|
#include <ostream>
|
2018-01-17 23:51:59 +01:00
|
|
|
#include <sstream>
|
2018-01-09 22:12:11 +01:00
|
|
|
#include <boost/asio.hpp>
|
|
|
|
#include <boost/asio/ssl.hpp>
|
2018-01-13 15:49:46 +01:00
|
|
|
#include "macros.hpp"
|
2018-01-09 22:12:11 +01:00
|
|
|
#include "mastodon-cpp.hpp"
|
|
|
|
|
|
|
|
using namespace Mastodon;
|
|
|
|
using std::string;
|
|
|
|
using std::cerr;
|
|
|
|
|
|
|
|
using boost::asio::ip::tcp;
|
|
|
|
namespace ssl = boost::asio::ssl;
|
|
|
|
typedef ssl::stream<tcp::socket> ssl_socket;
|
|
|
|
|
2018-01-10 18:19:19 +01:00
|
|
|
API::http::http(const API &api, const string &instance,
|
|
|
|
const string &access_token)
|
|
|
|
: parent(api)
|
|
|
|
, _instance(instance)
|
2018-01-09 22:12:11 +01:00
|
|
|
, _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();
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::uint16_t API::http::request_sync(const method &meth,
|
|
|
|
const string &path,
|
|
|
|
string &answer)
|
|
|
|
{
|
|
|
|
return request_sync(meth, path, "", answer);
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::uint16_t API::http::request_sync(const method &meth,
|
|
|
|
const string &path,
|
2018-01-21 23:54:42 +01:00
|
|
|
const string &formdata,
|
2018-01-09 22:12:11 +01:00
|
|
|
string &answer)
|
|
|
|
{
|
2018-01-17 23:51:59 +01:00
|
|
|
ttdebug << "Path is: " << path << '\n';
|
2018-01-09 22:12:11 +01:00
|
|
|
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));
|
2018-01-13 15:49:46 +01:00
|
|
|
}
|
|
|
|
catch (const std::exception &e)
|
|
|
|
{
|
|
|
|
ttdebug << "ERROR: " << e.what() << "\n";
|
|
|
|
return 16;
|
|
|
|
}
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
2018-01-09 22:12:11 +01:00
|
|
|
// 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);
|
2018-01-13 15:49:46 +01:00
|
|
|
}
|
|
|
|
catch (const std::exception &e)
|
|
|
|
{
|
|
|
|
ttdebug << "ERROR: " << e.what() << "\n";
|
|
|
|
return 17;
|
|
|
|
}
|
2018-01-09 22:12:11 +01:00
|
|
|
|
2018-01-13 15:49:46 +01:00
|
|
|
try
|
|
|
|
{
|
2018-01-09 22:12:11 +01:00
|
|
|
boost::asio::streambuf request;
|
|
|
|
std::ostream request_stream(&request);
|
|
|
|
switch (meth)
|
|
|
|
{
|
|
|
|
case http::method::GET:
|
|
|
|
request_stream << "GET";
|
2018-01-24 18:52:24 +01:00
|
|
|
ttdebug << "Method is GET\n";
|
2018-01-09 22:12:11 +01:00
|
|
|
break;
|
2018-01-21 23:54:42 +01:00
|
|
|
case http::method::PATCH:
|
|
|
|
request_stream << "PATCH";
|
2018-01-24 18:52:24 +01:00
|
|
|
ttdebug << "Method is PATCH\n";
|
2018-01-21 23:54:42 +01:00
|
|
|
break;
|
|
|
|
case http::method::POST:
|
|
|
|
request_stream << "POST";
|
2018-01-24 18:52:24 +01:00
|
|
|
ttdebug << "Method is POST\n";
|
|
|
|
break;
|
|
|
|
case http::method::PUT:
|
|
|
|
request_stream << "PUT";
|
|
|
|
ttdebug << "Method is PUT\n";
|
2018-01-21 23:54:42 +01:00
|
|
|
break;
|
|
|
|
case http::method::DELETE:
|
|
|
|
request_stream << "DELETE";
|
2018-01-24 18:52:24 +01:00
|
|
|
ttdebug << "Method is DELETE\n";
|
2018-01-21 23:54:42 +01:00
|
|
|
break;
|
2018-01-09 22:12:11 +01:00
|
|
|
default:
|
2018-01-13 15:49:46 +01:00
|
|
|
ttdebug << "ERROR: Not implemented\n";
|
|
|
|
return 2;
|
2018-01-09 22:12:11 +01:00
|
|
|
}
|
2018-01-21 23:54:42 +01:00
|
|
|
request_stream << " " << path;
|
2018-01-09 22:12:11 +01:00
|
|
|
request_stream << " HTTP/1.0\r\n";
|
|
|
|
request_stream << "Host: " << _instance << "\r\n";
|
|
|
|
request_stream << "Accept: */*\r\n";
|
|
|
|
request_stream << "Connection: close\r\n";
|
2018-01-10 18:19:19 +01:00
|
|
|
request_stream << "User-Agent: " << parent.get_useragent() << "\r\n";
|
2018-01-26 00:24:00 +01:00
|
|
|
if (!_access_token.empty())
|
|
|
|
{
|
|
|
|
request_stream << "Authorization: Bearer "
|
|
|
|
<< _access_token << "\r\n";
|
|
|
|
}
|
2018-01-21 23:54:42 +01:00
|
|
|
switch (meth)
|
|
|
|
{
|
|
|
|
case http::method::GET:
|
|
|
|
request_stream << "\r\n";
|
|
|
|
break;
|
|
|
|
case http::method::PATCH:
|
|
|
|
request_stream << formdata;
|
|
|
|
break;
|
|
|
|
case http::method::POST:
|
2018-01-24 18:52:24 +01:00
|
|
|
case http::method::PUT:
|
|
|
|
case http::method::DELETE:
|
2018-01-21 23:54:42 +01:00
|
|
|
if (formdata.empty())
|
|
|
|
{
|
|
|
|
request_stream << "\r\n";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
request_stream << formdata;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2018-01-09 22:12:11 +01:00
|
|
|
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/")
|
|
|
|
{
|
2018-01-13 15:49:46 +01:00
|
|
|
ttdebug << "ERROR: Invalid response from server\n";
|
|
|
|
ttdebug << "Response was: " << http_version << " " << status_code
|
|
|
|
<< " " << status_message << '\n';
|
|
|
|
return 18;
|
2018-01-09 22:12:11 +01:00
|
|
|
}
|
|
|
|
if (status_code != 200)
|
|
|
|
{
|
2018-01-13 15:49:46 +01:00
|
|
|
ttdebug << "ERROR: Response returned with status code "
|
|
|
|
<< status_code << ": " << status_message << "\n";
|
|
|
|
return status_code;
|
2018-01-09 22:12:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Read headers
|
|
|
|
boost::asio::read_until(_socket, response, "\r\n\r\n");
|
|
|
|
std::string header;
|
2018-01-21 23:54:42 +01:00
|
|
|
// ttdebug << "Header: \n";
|
2018-01-09 22:12:11 +01:00
|
|
|
while (std::getline(response_stream, header) && header != "\r")
|
2018-01-21 23:54:42 +01:00
|
|
|
{
|
|
|
|
// ttdebug << header << '\n';
|
|
|
|
}
|
2018-01-09 22:12:11 +01:00
|
|
|
|
|
|
|
// Read body
|
|
|
|
boost::system::error_code error;
|
|
|
|
answer = "";
|
2018-01-17 23:51:59 +01:00
|
|
|
std::ostringstream oss;
|
2018-01-09 22:12:11 +01:00
|
|
|
while (boost::asio::read(_socket, response,
|
|
|
|
boost::asio::transfer_at_least(1), error))
|
|
|
|
{
|
2018-01-17 23:51:59 +01:00
|
|
|
oss << &response;
|
2018-01-09 22:12:11 +01:00
|
|
|
}
|
|
|
|
if (error != boost::asio::error::eof)
|
|
|
|
{
|
2018-01-22 02:09:44 +01:00
|
|
|
// 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";
|
2018-01-09 22:12:11 +01:00
|
|
|
}
|
2018-01-17 23:51:59 +01:00
|
|
|
answer = oss.str();
|
2018-01-21 23:54:42 +01:00
|
|
|
ttdebug << "Answer from server: " << oss.str() << '\n';
|
2018-01-09 22:12:11 +01:00
|
|
|
}
|
|
|
|
catch (const std::exception &e)
|
|
|
|
{
|
2018-01-13 15:49:46 +01:00
|
|
|
ttdebug << "Exception: " << e.what() << "\n";
|
2018-01-09 22:12:11 +01:00
|
|
|
return 0xffff;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|