mirror of https://github.com/PurpleI2P/i2pd.git
I2P: End-to-End encrypted and anonymous Internet
https://i2pd.website/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
264 lines
7.5 KiB
264 lines
7.5 KiB
#include <boost/bind.hpp> |
|
#include <ctime> |
|
#include <fstream> |
|
#include "util/Log.h" |
|
#include "util/util.h" |
|
#include "util/I2PEndian.h" |
|
#include "HTTPServer.h" |
|
|
|
namespace i2p { |
|
namespace util { |
|
|
|
HTTPConnection::HTTPConnection(boost::asio::ip::tcp::socket* socket, |
|
std::shared_ptr<client::i2pcontrol::I2PControlSession> session) |
|
: m_Socket(socket), m_BufferLen(0), m_Request(), m_Reply(), m_Session(session) |
|
{ |
|
|
|
} |
|
|
|
void HTTPConnection::Terminate() |
|
{ |
|
m_Socket->close(); |
|
} |
|
|
|
void HTTPConnection::Receive() |
|
{ |
|
m_Socket->async_read_some( |
|
boost::asio::buffer(m_Buffer, HTTP_CONNECTION_BUFFER_SIZE), std::bind( |
|
&HTTPConnection::HandleReceive, shared_from_this(), |
|
std::placeholders::_1, std::placeholders::_2 |
|
) |
|
); |
|
} |
|
|
|
void HTTPConnection::HandleReceive(const boost::system::error_code& e, std::size_t nb_bytes) |
|
{ |
|
if(!e) { |
|
m_Buffer[nb_bytes] = 0; |
|
m_BufferLen = nb_bytes; |
|
const std::string data = std::string(m_Buffer, m_Buffer + m_BufferLen); |
|
if(!m_Request.hasData()) // New request |
|
m_Request = i2p::util::http::Request(data); |
|
else |
|
m_Request.update(data); |
|
|
|
if(m_Request.isComplete()) { |
|
RunRequest(); |
|
m_Request.clear(); |
|
} else { |
|
Receive(); |
|
} |
|
} else if(e != boost::asio::error::operation_aborted) |
|
Terminate(); |
|
} |
|
|
|
void HTTPConnection::RunRequest() |
|
{ |
|
try { |
|
if(m_Request.getMethod() == "GET") |
|
return HandleRequest(); |
|
if(m_Request.getHeader("Content-Type").find("application/json") != std::string::npos) |
|
return HandleI2PControlRequest(); |
|
} catch(...) { |
|
// Ignore the error for now, probably Content-Type doesn't exist |
|
// Could also be invalid json data |
|
} |
|
// Unsupported method |
|
m_Reply = i2p::util::http::Response(502, ""); |
|
SendReply(); |
|
} |
|
|
|
void HTTPConnection::ExtractParams(const std::string& str, std::map<std::string, std::string>& params) |
|
{ |
|
if(str[0] != '&') return; |
|
size_t pos = 1, end; |
|
do |
|
{ |
|
end = str.find('&', pos); |
|
std::string param = str.substr(pos, end - pos); |
|
LogPrint(param); |
|
size_t e = param.find('='); |
|
if(e != std::string::npos) |
|
params[param.substr(0, e)] = param.substr(e+1); |
|
pos = end + 1; |
|
} |
|
while(end != std::string::npos); |
|
} |
|
|
|
void HTTPConnection::HandleWriteReply(const boost::system::error_code& e) |
|
{ |
|
if(e != boost::asio::error::operation_aborted) { |
|
boost::system::error_code ignored_ec; |
|
m_Socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); |
|
Terminate(); |
|
} |
|
} |
|
|
|
void HTTPConnection::Send404Reply() |
|
{ |
|
try { |
|
const std::string error_page = "404.html"; |
|
m_Reply = i2p::util::http::Response(404, GetFileContents(error_page, true)); |
|
m_Reply.setHeader("Content-Type", i2p::util::http::getMimeType(error_page)); |
|
} catch(const std::runtime_error&) { |
|
// Failed to load 404.html, assume the webui is incorrectly installed |
|
m_Reply = i2p::util::http::Response(404, |
|
"<!DOCTYPE HTML><html>" |
|
"<head><title>Error: 404 - webui not installed</title></head><body>" |
|
"<p>It looks like your webui installation is broken.</p>" |
|
"<p>Run the following command to (re)install it:</p>" |
|
"<pre>./i2pd --install=/path/to/webui</pre>" |
|
"<p>Or from a directory containing a folder named webui:</p>" |
|
"<pre>./i2pd --install</pre>" |
|
"<p>The webui folder should come with the binaries.</p>" |
|
"</body></html>" |
|
); |
|
} |
|
SendReply(); |
|
} |
|
|
|
std::string HTTPConnection::GetFileContents(const std::string& filename, bool preprocess) const |
|
{ |
|
boost::system::error_code e; |
|
|
|
// Use canonical to avoid .. or . in path |
|
const boost::filesystem::path address = boost::filesystem::canonical( |
|
i2p::util::filesystem::GetWebuiDataDir() / filename, e |
|
); |
|
|
|
const std::string address_str = address.string(); |
|
|
|
std::ifstream ifs(address_str, std::ios_base::in | std::ios_base::binary); |
|
if(e || !ifs || !isAllowed(address_str)) |
|
throw std::runtime_error("Cannot load " + address_str + "."); |
|
|
|
std::string str; |
|
ifs.seekg(0, ifs.end); |
|
str.resize(ifs.tellg()); |
|
ifs.seekg(0, ifs.beg); |
|
ifs.read(&str[0], str.size()); |
|
ifs.close(); |
|
|
|
if(preprocess) |
|
return i2p::util::http::preprocessContent(str, address.parent_path().string()); |
|
else |
|
return str; |
|
} |
|
|
|
void HTTPConnection::HandleRequest() |
|
{ |
|
|
|
std::string uri = m_Request.getUri(); |
|
if(uri == "/") |
|
uri = "index.html"; |
|
|
|
try { |
|
m_Reply = i2p::util::http::Response(200, GetFileContents(uri, true)); |
|
m_Reply.setHeader("Content-Type", i2p::util::http::getMimeType(uri) + "; charset=UTF-8"); |
|
SendReply(); |
|
} catch(const std::runtime_error&) { |
|
// Cannot open the file for some reason, send 404 |
|
Send404Reply(); |
|
} |
|
} |
|
|
|
void HTTPConnection::HandleI2PControlRequest() |
|
{ |
|
std::stringstream ss(m_Request.getContent()); |
|
const client::i2pcontrol::I2PControlSession::Response rsp = m_Session->handleRequest(ss); |
|
m_Reply = i2p::util::http::Response(200, rsp.toJsonString()); |
|
m_Reply.setHeader("Content-Type", "application/json"); |
|
SendReply(); |
|
} |
|
|
|
bool HTTPConnection::isAllowed(const std::string& address) const |
|
{ |
|
const std::size_t pos_dot = address.find_last_of('.'); |
|
const std::size_t pos_slash = address.find_last_of('/'); |
|
if(pos_dot == std::string::npos || pos_dot == address.size() - 1) |
|
return false; |
|
if(pos_slash != std::string::npos && pos_dot < pos_slash) |
|
return false; |
|
return true; |
|
} |
|
|
|
void HTTPConnection::SendReply() |
|
{ |
|
// we need the date header to be compliant with HTTP 1.1 |
|
std::time_t time_now = std::time(nullptr); |
|
char time_buff[128]; |
|
if(std::strftime(time_buff, sizeof(time_buff), "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(&time_now)) ) { |
|
m_Reply.setHeader("Date", std::string(time_buff)); |
|
m_Reply.setContentLength(); |
|
} |
|
boost::asio::async_write( |
|
*m_Socket, boost::asio::buffer(m_Reply.toString()), |
|
std::bind(&HTTPConnection::HandleWriteReply, shared_from_this(), std::placeholders::_1) |
|
); |
|
} |
|
|
|
HTTPServer::HTTPServer(const std::string& address, int port): |
|
m_Thread(nullptr), m_Work(m_Service), |
|
m_Acceptor(m_Service, boost::asio::ip::tcp::endpoint( |
|
boost::asio::ip::address::from_string(address), port) |
|
), |
|
m_NewSocket(nullptr), |
|
m_Session(std::make_shared<client::i2pcontrol::I2PControlSession>(m_Service)) |
|
{ |
|
|
|
} |
|
|
|
HTTPServer::~HTTPServer() |
|
{ |
|
Stop(); |
|
} |
|
|
|
void HTTPServer::Start() |
|
{ |
|
m_Thread = new std::thread(std::bind(&HTTPServer::Run, this)); |
|
m_Acceptor.listen(); |
|
m_Session->start(); |
|
Accept(); |
|
} |
|
|
|
void HTTPServer::Stop() |
|
{ |
|
m_Session->stop(); |
|
m_Acceptor.close(); |
|
m_Service.stop(); |
|
if(m_Thread) |
|
{ |
|
m_Thread->join(); |
|
delete m_Thread; |
|
m_Thread = nullptr; |
|
} |
|
} |
|
|
|
void HTTPServer::Run() |
|
{ |
|
m_Service.run(); |
|
} |
|
|
|
void HTTPServer::Accept() |
|
{ |
|
m_NewSocket = new boost::asio::ip::tcp::socket(m_Service); |
|
m_Acceptor.async_accept(*m_NewSocket, boost::bind(&HTTPServer::HandleAccept, this, |
|
boost::asio::placeholders::error)); |
|
} |
|
|
|
void HTTPServer::HandleAccept(const boost::system::error_code& ecode) |
|
{ |
|
if(!ecode) { |
|
CreateConnection(m_NewSocket); |
|
Accept(); |
|
} |
|
} |
|
|
|
void HTTPServer::CreateConnection(boost::asio::ip::tcp::socket* m_NewSocket) |
|
{ |
|
auto conn = std::make_shared<HTTPConnection>(m_NewSocket, m_Session); |
|
conn->Receive(); |
|
} |
|
|
|
} |
|
}
|
|
|