This repository has been archived on 2020-05-10. You can view files and clone it, but cannot push or open issues or pull requests.
mastodon-cpp/src/http.cpp

365 lines
10 KiB
C++
Raw Normal View History

2018-01-09 22:12:11 +01:00
/* This file is part of mastodon-cpp.
* Copyright © 2018, 2019 tastytea <tastytea@tastytea.de>
2019-03-30 22:14:58 +01:00
*
2018-01-09 22:12:11 +01:00
* This program is free software: you can redistribute it and/or modify
2019-08-15 22:53:38 +02:00
* it under the terms of the GNU Affero General Public License as published by
2018-01-09 22:12:11 +01:00
* 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
2019-08-15 22:53:38 +02:00
* GNU Affero General Public License for more details.
2018-01-09 22:12:11 +01:00
*
2019-08-15 22:53:38 +02:00
* You should have received a copy of the GNU Affero General Public License
2018-01-09 22:12:11 +01:00
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <iostream>
2018-02-26 07:57:30 +01:00
#include <functional> // std::bind
2018-04-10 10:17:30 +02:00
#include <exception>
2019-04-10 02:25:55 +02:00
#include <thread>
#include <regex>
#include <Poco/Net/HTTPSClientSession.h>
#include <Poco/Net/HTTPRequest.h>
#include <Poco/Net/HTTPResponse.h>
#include <Poco/StreamCopier.h>
#include <Poco/URI.h>
#include <Poco/Environment.h>
#include <Poco/Exception.h>
#include <Poco/Net/NetException.h>
#include <Poco/Net/SSLException.h>
2019-02-22 11:35:06 +01:00
#include "debug.hpp"
2018-01-09 22:12:11 +01:00
#include "mastodon-cpp.hpp"
using namespace Mastodon;
using std::cerr;
using std::istream;
using std::make_unique;
using std::move;
using std::regex;
using std::regex_search;
using std::smatch;
using Poco::Net::HTTPSClientSession;
using Poco::Net::HTTPRequest;
using Poco::Net::HTTPResponse;
using Poco::Net::HTTPMessage;
using Poco::StreamCopier;
using Poco::Environment;
2018-01-09 22:12:11 +01:00
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)
, _cancel_stream(false)
2018-01-09 22:12:11 +01:00
{
Poco::Net::initializeSSL();
try
{
string env_proxy = Environment::get("http_proxy");
size_t pos;
// Only keep text between // and /.
if ((pos = env_proxy.find("//")) != string::npos)
{
env_proxy = env_proxy.substr(pos + 2);
}
if ((pos = env_proxy.find('/')) != string::npos)
{
env_proxy = env_proxy.substr(0, pos);
}
if ((pos = env_proxy.find('@')) != string::npos)
{
string hostport = env_proxy.substr(pos + 1);
string userpw = env_proxy.substr(0, pos);
set_proxy(hostport, userpw);
}
else
{
set_proxy(env_proxy);
}
}
catch (const std::exception &)
{
// No proxy found, no problem.
}
2018-01-09 22:12:11 +01:00
}
2018-02-26 07:57:30 +01:00
API::http::~http()
{
Poco::Net::uninitializeSSL();
2018-02-26 07:57:30 +01:00
}
void API::http::set_proxy(const string &hostport, const string &userpw)
{
try
{
HTTPSClientSession::ProxyConfig proxyconfig;
size_t pos = hostport.find(':');
proxyconfig.host = hostport.substr(0, pos);
if (pos != string::npos)
{
proxyconfig.port = std::stoi(hostport.substr(pos + 1));
}
if (!userpw.empty())
{
string username;
pos = userpw.find(':');
Poco::URI::decode(userpw.substr(0, pos), username);
proxyconfig.username = username;
if (pos != string::npos)
{
string password;
Poco::URI::decode(userpw.substr(pos + 1), password);
proxyconfig.password = password;
}
}
HTTPSClientSession::setGlobalProxyConfig(proxyconfig);
ttdebug << "Set proxy to " << hostport << ".\n";
}
catch (const std::exception &e)
{
if (parent.exceptions())
{
std::rethrow_exception(std::current_exception());
}
}
}
return_call API::http::request(const http_method &meth, const string &path)
2018-01-09 22:12:11 +01:00
{
HTMLForm form;
return request(meth, path, form);
2018-01-09 22:12:11 +01:00
}
2019-04-10 02:25:55 +02:00
return_call API::http::request(const http_method &meth, const string &path,
HTMLForm &formdata)
2019-04-10 02:25:55 +02:00
{
string answer;
return request_common(meth, path, formdata, answer);
2019-04-10 02:25:55 +02:00
}
void API::http::request_stream(const string &path, string &stream)
2019-04-10 02:25:55 +02:00
{
static return_call ret;
_streamthread = std::thread(
[&, path] // path is captured by value because it may be
{ // deleted before we access it.
HTMLForm form;
2019-04-10 02:25:55 +02:00
ret = request_common(http_method::GET_STREAM, path,
form, stream);
ttdebug << "Remaining content of the stream: " << stream << '\n';
if (!ret)
{
// Embed the HTTP status code in stream on error.
stream += "event: ERROR\ndata: {\"error_code\":"
+ std::to_string(ret.error_code) + ",\"http_error\":"
+ std::to_string(ret.http_error_code) + "}\n";
}
2019-04-10 02:25:55 +02:00
});
}
return_call API::http::request_common(const http_method &meth,
const string &path,
HTMLForm &formdata,
2019-04-10 02:25:55 +02:00
string &answer)
2018-01-09 22:12:11 +01:00
{
2018-01-17 23:51:59 +01:00
ttdebug << "Path is: " << path << '\n';
2019-03-30 22:14:58 +01:00
2018-01-09 22:12:11 +01:00
try
{
string method;
2018-02-09 16:01:24 +01:00
switch (meth)
{
case http_method::GET:
case http_method::GET_STREAM:
{
method = HTTPRequest::HTTP_GET;
break;
}
case http_method::PUT:
{
method = HTTPRequest::HTTP_PUT;
break;
}
case http_method::POST:
{
method = HTTPRequest::HTTP_POST;
break;
}
case http_method::PATCH:
{
2019-09-20 03:54:47 +02:00
// POCO in CentOS 7 (1.6.1) doesn't have HTTPRequest::HTTP_PATCH.
method = "PATCH";
break;
}
case http_method::DELETE:
{
method = HTTPRequest::HTTP_DELETE;
break;
}
default:
{
break;
}
}
2019-03-30 22:14:58 +01:00
HTTPSClientSession session(_instance);
HTTPRequest request(method, path, HTTPMessage::HTTP_1_1);
request.set("User-Agent", parent.get_useragent());
if (!_access_token.empty())
{
request.set("Authorization", " Bearer " + _access_token);
}
if (!formdata.empty())
{
ttdebug << "Size of HTMLForm is " << formdata.size() << '\n';
formdata.prepareSubmit(request);
formdata.write(session.sendRequest(request));
2019-08-21 03:36:25 +02:00
}
else
{
session.sendRequest(request);
}
HTTPResponse response;
2019-10-06 14:35:06 +02:00
istream &body_stream = session.receiveResponse(response);
const uint16_t http_code = response.getStatus();
2019-02-22 12:03:28 +01:00
ttdebug << "Response code: " << http_code << '\n';
answer.clear();
2019-10-06 14:35:06 +02:00
StreamCopier::copyToString(body_stream, answer);
std::ostringstream headers_stream;
response.write(headers_stream);
_headers = headers_stream.str();
switch (http_code)
{
case HTTPResponse::HTTP_OK:
{
return { error::OK, "", http_code, answer };
2018-02-10 12:01:55 +01:00
}
// Not using the constants because some are too new for Debian stretch.
case 301: // HTTPResponse::HTTP_MOVED_PERMANENTLY
case 308: // HTTPResponse::HTTP_PERMANENT_REDIRECT
case 302: // HTTPResponse::HTTP_FOUND
case 303: // HTTPResponse::HTTP_SEE_OTHER
case 307: // HTTPResponse::HTTP_TEMPORARY_REDIRECT
2018-10-08 01:24:12 +02:00
{
ttdebug << "HTTP redirect.\n";
string location = response.get("Location");
if (location.substr(0, 4) == "http")
{ // Remove protocol and instance from path.
size_t pos1 = location.find("//") + 2;
size_t pos2 = location.find('/', pos1);
if (location.substr(pos1, pos2 - pos1) != _instance)
{ // Return new location if the domain changed.
ttdebug << "New location is on another domain.\n";
return { error::URL_CHANGED, "Remote address changed",
http_code, location };
}
location = location.substr(pos2);
}
if (http_code == 301 || http_code == 308)
{ // Return new location for permanent redirects.
return { error::URL_CHANGED, "Remote address changed",
http_code, location };
}
else
{
ttdebug << "Following temporary redirect: " << location << '\n';
return request_common(meth, location, formdata, answer);
}
2018-10-08 01:24:12 +02:00
}
default:
2018-02-10 12:01:55 +01:00
{
return { error::CONNECTION_REFUSED, "Connection refused",
http_code, answer };
2018-02-10 12:01:55 +01:00
}
}
2018-01-09 22:12:11 +01:00
}
catch (const Poco::Net::DNSException &e)
2018-02-09 16:01:24 +01:00
{
if (parent.exceptions())
2018-10-08 01:24:12 +02:00
{
e.rethrow();
2018-10-08 01:24:12 +02:00
}
ttdebug << e.displayText() << "\n";
return { error::DNS, e.displayText(), 0, "" };
}
catch (const Poco::Net::ConnectionRefusedException &e)
{
if (parent.exceptions())
{
e.rethrow();
}
2018-04-10 10:44:46 +02:00
ttdebug << e.displayText() << "\n";
return { error::CONNECTION_REFUSED, e.displayText(), 0, "" };
}
catch (const Poco::Net::SSLException &e)
{
2018-04-10 10:44:46 +02:00
if (parent.exceptions())
2018-04-09 17:55:11 +02:00
{
e.rethrow();
2018-04-09 17:55:11 +02:00
}
ttdebug << e.displayText() << "\n";
return { error::ENCRYPTION, e.displayText(), 0, "" };
}
catch (const Poco::Net::NetException &e)
{
if (parent.exceptions())
2018-02-26 07:57:30 +01:00
{
e.rethrow();
2018-02-26 07:57:30 +01:00
}
ttdebug << "Unknown network error: " << e.displayText() << std::endl;
return { error::UNKNOWN, e.displayText(), 0, "" };
2018-02-09 16:01:24 +01:00
}
catch (const std::exception &e)
2018-01-09 22:12:11 +01:00
{
2018-04-10 10:17:30 +02:00
if (parent.exceptions())
{
std::rethrow_exception(std::current_exception());
}
ttdebug << "Unknown error: " << e.what() << std::endl;
return { error::UNKNOWN, e.what(), 0, "" };
2018-01-09 22:12:11 +01:00
}
}
2018-02-25 23:20:02 +01:00
2018-12-04 11:26:28 +01:00
void API::http::get_headers(string &headers) const
2018-02-25 23:20:02 +01:00
{
headers = _headers;
}
2018-02-26 07:57:30 +01:00
2018-12-04 11:26:28 +01:00
void API::http::cancel_stream()
{
_cancel_stream = true;
2019-04-10 02:25:55 +02:00
_streamthread.join();
}
2018-05-17 17:59:44 +02:00
std::mutex &API::http::get_mutex()
{
return _mutex;
}