@ -1,7 +1,5 @@
@@ -1,7 +1,5 @@
# include <cstring>
# include <cassert>
# include <boost/lexical_cast.hpp>
# include <boost/regex.hpp>
# include <string>
# include <atomic>
# include <memory>
@ -20,73 +18,59 @@
@@ -20,73 +18,59 @@
# include "I2PEndian.h"
# include "I2PTunnel.h"
# include "Config.h"
# include "HTTP.h"
namespace i2p
{
namespace proxy
{
static const size_t http_buffer_size = 8192 ;
class HTTPProxyHandler : public i2p : : client : : I2PServiceHandler , public std : : enable_shared_from_this < HTTPProxyHandler >
namespace i2p {
namespace proxy {
bool str_rmatch ( std : : string & str , const char * suffix ) {
auto pos = str . rfind ( suffix ) ;
if ( pos = = std : : string : : npos )
return false ; /* not found */
if ( str . length ( ) = = ( pos + std : : strlen ( suffix ) ) )
return true ; /* match */
return false ;
}
class HTTPReqHandler : public i2p : : client : : I2PServiceHandler , public std : : enable_shared_from_this < HTTPReqHandler >
{
private :
enum state
{
GET_METHOD ,
GET_HOSTNAME ,
GET_HTTPV ,
GET_HTTPVNL , //TODO: fallback to finding HOst: header if needed
DONE
} ;
void EnterState ( state nstate ) ;
bool HandleData ( uint8_t * http_buff , std : : size_t len ) ;
bool HandleRequest ( std : : size_t len ) ;
void HandleSockRecv ( const boost : : system : : error_code & ecode , std : : size_t bytes_transfered ) ;
void Terminate ( ) ;
void AsyncSockRead ( ) ;
void HTTPRequestFailed ( const char * message ) ;
void RedirectToJumpService ( ) ;
void ExtractRequest ( ) ;
bool IsI2PAddress ( ) ;
bool ValidateHTTPRequest ( ) ;
void HandleJumpServices ( ) ;
bool CreateHTTPRequest ( uint8_t * http_buff , std : : size_t len ) ;
void RedirectToJumpService ( std : : string & host ) ;
bool ExtractAddressHelper ( i2p : : http : : URL & url , std : : string & b64 ) ;
void SanitizeHTTPRequest ( i2p : : http : : HTTPReq & req ) ;
void SentHTTPFailed ( const boost : : system : : error_code & ecode ) ;
void HandleStreamRequestComplete ( std : : shared_ptr < i2p : : stream : : Stream > stream ) ;
uint8_t m_http_buff [ http_buffer_size ] ;
std : : shared_ptr < boost : : asio : : ip : : tcp : : socket > m_sock ;
std : : string m_request ; //Data left to be sent
std : : string m_Response ;
std : : string m_url ; //URL
std : : string m_method ; //Method
std : : string m_version ; //HTTP version
std : : string m_address ; //Address
std : : string m_path ; //Path
int m_port ; //Port
state m_state ; //Parsing state
std : : vector < unsigned char > m_recv_buf ; /* as "downstream recieve buffer", from client to me */
std : : vector < unsigned char > m_send_buf ; /* as "upstream send buffer", from me to remote host */
public :
HTTPProxyHandler ( HTTPProxyServer * parent , std : : shared_ptr < boost : : asio : : ip : : tcp : : socket > sock ) :
I2PServiceHandler ( parent ) , m_sock ( sock )
{ EnterState ( GET_METHOD ) ; }
~ HTTPProxyHandler ( ) { Terminate ( ) ; }
HTTPReqHandler ( HTTPProxy * parent , std : : shared_ptr < boost : : asio : : ip : : tcp : : socket > sock ) :
I2PServiceHandler ( parent ) , m_sock ( sock ) , m_recv_buf ( 8192 ) , m_send_buf ( 0 ) { } ;
~ HTTPReqHandler ( ) { Terminate ( ) ; }
void Handle ( ) { AsyncSockRead ( ) ; }
} ;
void HTTPProxy Handler : : AsyncSockRead ( )
void HTTPReq Handler : : AsyncSockRead ( )
{
LogPrint ( eLogDebug , " HTTPProxy: async sock read " ) ;
if ( m_sock ) {
m_sock - > async_receive ( boost : : asio : : buffer ( m_http_buff , http_buffer_size ) ,
std : : bind ( & HTTPProxyHandler : : HandleSockRecv , shared_from_this ( ) ,
std : : placeholders : : _1 , std : : placeholders : : _2 ) ) ;
} else {
if ( ! m_sock ) {
LogPrint ( eLogError , " HTTPProxy: no socket for read " ) ;
return ;
}
m_sock - > async_receive ( boost : : asio : : buffer ( m_recv_buf ) ,
std : : bind ( & HTTPReqHandler : : HandleSockRecv , shared_from_this ( ) ,
std : : placeholders : : _1 , std : : placeholders : : _2 ) ) ;
}
void HTTPProxyHandler : : Terminate ( ) {
void HTTPReq Handler : : Terminate ( ) {
if ( Kill ( ) ) return ;
if ( m_sock )
{
@ -97,219 +81,131 @@ namespace proxy
@@ -97,219 +81,131 @@ namespace proxy
Done ( shared_from_this ( ) ) ;
}
/* All hope is lost beyond this point */
//TODO: handle this apropriately
void HTTPProxyHandler : : HTTPRequestFailed ( const char * message )
void HTTPReqHandler : : HTTPRequestFailed ( const char * message )
{
std : : size_t size = std : : strlen ( message ) ;
std : : stringstream ss ;
ss < < " HTTP/1.0 500 Internal Server Error \r \n "
< < " Content-Type: text/plain \r \n " ;
ss < < " Content-Length: " < < std : : to_string ( size + 2 ) < < " \r \n "
< < " \r \n " ; /* end of headers */
ss < < message < < " \r \n " ;
m_Response = ss . str ( ) ;
boost : : asio : : async_write ( * m_sock , boost : : asio : : buffer ( m_Response ) ,
std : : bind ( & HTTPProxyHandler : : SentHTTPFailed , shared_from_this ( ) , std : : placeholders : : _1 ) ) ;
i2p : : http : : HTTPRes res ;
res . code = 500 ;
res . add_header ( " Content-Type " , " text/plain " ) ;
res . add_header ( " Connection " , " close " ) ;
res . body = message ;
res . body + = " \r \n " ;
std : : string response = res . to_string ( ) ;
boost : : asio : : async_write ( * m_sock , boost : : asio : : buffer ( response , response . size ( ) ) ,
std : : bind ( & HTTPReqHandler : : SentHTTPFailed , shared_from_this ( ) , std : : placeholders : : _1 ) ) ;
}
void HTTPProxy Handler : : RedirectToJumpService ( /*HTTPProxyHandler::errTypes error*/ )
void HTTPReq Handler : : RedirectToJumpService ( std : : string & host )
{
std : : stringstream response ;
std : : string httpAddr ; i2p : : config : : GetOption ( " http.address " , httpAddr ) ;
uint16_t httpPort ; i2p : : config : : GetOption ( " http.port " , httpPort ) ;
response < < " HTTP/1.1 302 Found \r \n Location: http:// " < < httpAddr < < " : " < < httpPort < < " /?page=jumpservices&address= " < < m_address < < " \r \n \r \n " ;
m_Response = response . str ( ) ;
boost : : asio : : async_write ( * m_sock , boost : : asio : : buffer ( m_Response ) ,
std : : bind ( & HTTPProxyHandler : : SentHTTPFailed , shared_from_this ( ) , std : : placeholders : : _1 ) ) ;
}
i2p : : http : : HTTPRes res ;
i2p : : http : : URL url ;
void HTTPProxyHandler : : EnterState ( HTTPProxyHandler : : state nstate )
{
m_state = nstate ;
}
i2p : : config : : GetOption ( " http.address " , url . host ) ;
i2p : : config : : GetOption ( " http.port " , url . port ) ;
url . schema = " http " ;
url . path = " / " ;
url . query = " page=jumpservices&address= " ;
url . query + = host ;
void HTTPProxyHandler : : ExtractRequest ( )
{
LogPrint ( eLogDebug , " HTTPProxy: request: " , m_method , " " , m_url ) ;
std : : string server = " " ;
std : : string port = " 80 " ;
boost : : regex rHTTP ( " http://(.*?)(:( \\ d+))?(/.*) " ) ;
boost : : smatch m ;
std : : string path ;
if ( boost : : regex_search ( m_url , m , rHTTP , boost : : match_extra ) )
{
server = m [ 1 ] . str ( ) ;
if ( m [ 2 ] . str ( ) ! = " " ) port = m [ 3 ] . str ( ) ;
path = m [ 4 ] . str ( ) ;
}
LogPrint ( eLogDebug , " HTTPProxy: server: " , server , " , port: " , port , " , path: " , path ) ;
m_address = server ;
m_port = boost : : lexical_cast < int > ( port ) ;
m_path = path ;
res . code = 302 ; /* redirect */
res . add_header ( " Location " , url . to_string ( ) . c_str ( ) ) ;
res . add_header ( " Connection " , " close " ) ;
std : : string response = res . to_string ( ) ;
boost : : asio : : async_write ( * m_sock , boost : : asio : : buffer ( response , response . length ( ) ) ,
std : : bind ( & HTTPReqHandler : : SentHTTPFailed , shared_from_this ( ) , std : : placeholders : : _1 ) ) ;
}
bool HTTPProxyHandler : : ValidateHTTPRequest ( )
bool HTTPReqHandler : : ExtractAddressHelper ( i2p : : http : : URL & url , std : : string & b64 )
{
if ( m_version ! = " HTTP/1.0 " & & m_version ! = " HTTP/1.1 " )
{
LogPrint ( eLogError , " HTTPProxy: unsupported version: " , m_version ) ;
HTTPRequestFailed ( " unsupported HTTP version " ) ;
const char * param = " i2paddresshelper= " ;
std : : size_t pos = url . query . find ( param ) ;
std : : size_t len = std : : strlen ( param ) ;
std : : map < std : : string , std : : string > params ;
if ( pos = = std : : string : : npos )
return false ; /* not found */
if ( ! url . parse_query ( params ) )
return false ;
}
std : : string value = params [ " i2paddresshelper " ] ;
len + = value . length ( ) ;
b64 = i2p : : http : : UrlDecode ( value ) ;
url . query . replace ( pos , len , " " ) ;
return true ;
}
void HTTPProxyHandler : : HandleJumpServices ( )
void HTTPReqHandler : : SanitizeHTTPRequest ( i2p : : http : : HTTPReq & req )
{
static const char * helpermark1 = " ?i2paddresshelper= " ;
static const char * helpermark2 = " &i2paddresshelper= " ;
size_t addressHelperPos1 = m_path . rfind ( helpermark1 ) ;
size_t addressHelperPos2 = m_path . rfind ( helpermark2 ) ;
size_t addressHelperPos ;
if ( addressHelperPos1 = = std : : string : : npos )
{
if ( addressHelperPos2 = = std : : string : : npos )
return ; //Not a jump service
else
addressHelperPos = addressHelperPos2 ;
}
else
{
if ( addressHelperPos2 = = std : : string : : npos )
addressHelperPos = addressHelperPos1 ;
else if ( addressHelperPos1 > addressHelperPos2 )
addressHelperPos = addressHelperPos1 ;
else
addressHelperPos = addressHelperPos2 ;
}
auto base64 = m_path . substr ( addressHelperPos + strlen ( helpermark1 ) ) ;
base64 = i2p : : util : : http : : urlDecode ( base64 ) ; //Some of the symbols may be urlencoded
LogPrint ( eLogInfo , " HTTPProxy: jump service for " , m_address , " , inserting to address book " ) ;
//TODO: this is very dangerous and broken. We should ask the user before doing anything see http://pastethis.i2p/raw/pn5fL4YNJL7OSWj3Sc6N/
//TODO: we could redirect the user again to avoid dirtiness in the browser
i2p : : client : : context . GetAddressBook ( ) . InsertAddress ( m_address , base64 ) ;
m_path . erase ( addressHelperPos ) ;
req . del_header ( " Referer " ) ;
req . add_header ( " Connection " , " close " , true ) ;
req . add_header ( " User-Agent " , " MYOB/6.66 (AN/ON) " , true ) ;
}
bool HTTPProxyHandler : : IsI2PAddress ( )
/**
* @ param len length of data in m_recv_buf
* @ return true on processed request or false if more data needed
*/
bool HTTPReqHandler : : HandleRequest ( std : : size_t len )
{
auto pos = m_address . rfind ( " .i2p " ) ;
if ( pos ! = std : : string : : npos & & ( pos + 4 ) = = m_address . length ( ) )
{
return true ;
i2p : : http : : HTTPReq req ;
i2p : : http : : URL url ;
std : : string b64 ;
int req_len = 0 ;
req_len = req . parse ( ( const char * ) m_recv_buf . data ( ) , len ) ;
if ( req_len = = 0 )
return false ; /* need more data */
if ( req_len < 0 ) {
LogPrint ( eLogError , " HTTPProxy: unable to parse request " ) ;
HTTPRequestFailed ( " invalid request " ) ;
return true ; /* parse error */
}
return false ;
}
bool HTTPProxyHandler : : CreateHTTPRequest ( uint8_t * http_buff , std : : size_t len )
{
ExtractRequest ( ) ; //TODO: parse earlier
if ( ! ValidateHTTPRequest ( ) ) return false ;
HandleJumpServices ( ) ;
/* parsing success, now let's look inside request */
LogPrint ( eLogDebug , " HTTPProxy: requested: " , req . uri ) ;
url . parse ( req . uri ) ;
i2p : : data : : IdentHash identHash ;
if ( IsI2PAddress ( ) )
{
if ( ! i2p : : client : : context . GetAddressBook ( ) . GetIdentHash ( m_address , identHash ) ) {
RedirectToJumpService ( ) ;
return false ;
}
if ( ExtractAddressHelper ( url , b64 ) ) {
i2p : : client : : context . GetAddressBook ( ) . InsertAddress ( url . host , b64 ) ;
std : : string message = " added b64 from addresshelper for " + url . host + " to address book " ;
LogPrint ( eLogInfo , " HTTPProxy: " , message ) ;
message + = " , please reload page " ;
HTTPRequestFailed ( message . c_str ( ) ) ;
return true ; /* request processed */
}
m_request = m_method ;
m_request . push_back ( ' ' ) ;
m_request + = m_path ;
m_request . push_back ( ' ' ) ;
m_request + = m_version ;
m_request . push_back ( ' \r ' ) ;
m_request . push_back ( ' \n ' ) ;
m_request . append ( " Connection: close \r \n " ) ;
// TODO: temporary shortcut. Must be implemented properly
uint8_t * eol = nullptr ;
bool isEndOfHeader = false ;
while ( ! isEndOfHeader & & len & & ( eol = ( uint8_t * ) memchr ( http_buff , ' \r ' , len ) ) )
{
if ( eol )
{
* eol = 0 ; eol + + ;
if ( strncmp ( ( const char * ) http_buff , " Referer " , 7 ) & & strncmp ( ( const char * ) http_buff , " Connection " , 10 ) ) // strip out referer and connection
{
if ( ! strncmp ( ( const char * ) http_buff , " User-Agent " , 10 ) ) // replace UserAgent
m_request . append ( " User-Agent: MYOB/6.66 (AN/ON) " ) ;
else
m_request . append ( ( const char * ) http_buff ) ;
m_request . append ( " \r \n " ) ;
}
isEndOfHeader = ! http_buff [ 0 ] ;
auto l = eol - http_buff ;
http_buff = eol ;
len - = l ;
if ( len > 0 ) // \r
{
http_buff + + ;
len - - ;
}
}
}
m_request . append ( reinterpret_cast < const char * > ( http_buff ) , len ) ;
return true ;
}
bool HTTPProxyHandler : : HandleData ( uint8_t * http_buff , std : : size_t len )
{
while ( len > 0 )
{
//TODO: fallback to finding HOst: header if needed
switch ( m_state )
{
case GET_METHOD :
switch ( * http_buff )
{
case ' ' : EnterState ( GET_HOSTNAME ) ; break ;
default : m_method . push_back ( * http_buff ) ; break ;
}
break ;
case GET_HOSTNAME :
switch ( * http_buff )
{
case ' ' : EnterState ( GET_HTTPV ) ; break ;
default : m_url . push_back ( * http_buff ) ; break ;
}
break ;
case GET_HTTPV :
switch ( * http_buff )
{
case ' \r ' : EnterState ( GET_HTTPVNL ) ; break ;
default : m_version . push_back ( * http_buff ) ; break ;
}
break ;
case GET_HTTPVNL :
switch ( * http_buff )
{
case ' \n ' : EnterState ( DONE ) ; break ;
default :
LogPrint ( eLogError , " HTTPProxy: rejected invalid request ending with: " , ( ( int ) * http_buff ) ) ;
HTTPRequestFailed ( " rejected invalid request " ) ;
return false ;
}
break ;
default :
LogPrint ( eLogError , " HTTPProxy: invalid state: " , m_state ) ;
HTTPRequestFailed ( " invalid parser state " ) ;
return false ;
i2p : : data : : IdentHash identHash ;
if ( str_rmatch ( url . host , " .i2p " ) ) {
if ( ! i2p : : client : : context . GetAddressBook ( ) . GetIdentHash ( url . host , identHash ) ) {
RedirectToJumpService ( url . host ) ;
return true ; /* request processed */
}
http_buff + + ;
len - - ;
if ( m_state = = DONE )
return CreateHTTPRequest ( http_buff , len ) ;
/* TODO: outproxy handler here */
} else {
std : : string message = " Host " + url . host + " not inside i2p network, but outproxy support still missing " ;
HTTPRequestFailed ( message . c_str ( ) ) ;
LogPrint ( eLogWarning , " HTTPProxy: " , message ) ;
return true ;
}
SanitizeHTTPRequest ( req ) ;
/* drop original request from input buffer */
m_recv_buf . erase ( m_recv_buf . begin ( ) , m_recv_buf . begin ( ) + req_len ) ;
/* build new buffer from modified request and data from original request */
std : : string request = req . to_string ( ) ;
m_send_buf . assign ( request . begin ( ) , request . end ( ) ) ;
m_send_buf . insert ( m_send_buf . end ( ) , m_recv_buf . begin ( ) , m_recv_buf . end ( ) ) ;
/* connect to destination */
GetOwner ( ) - > CreateStream ( std : : bind ( & HTTPReqHandler : : HandleStreamRequestComplete ,
shared_from_this ( ) , std : : placeholders : : _1 ) , url . host , url . port ) ;
return true ;
}
void HTTPProxyHandler : : HandleSockRecv ( const boost : : system : : error_code & ecode , std : : size_t len )
void HTTPReqHandler : : HandleSockRecv ( const boost : : system : : error_code & ecode , std : : size_t len )
{
LogPrint ( eLogDebug , " HTTPProxy: sock recv: " , len , " bytes " ) ;
if ( ecode )
@ -319,53 +215,42 @@ namespace proxy
@@ -319,53 +215,42 @@ namespace proxy
return ;
}
if ( HandleData ( m_http_buff , len ) )
{
if ( m_state = = DONE )
{
LogPrint ( eLogDebug , " HTTPProxy: requested: " , m_url ) ;
GetOwner ( ) - > CreateStream ( std : : bind ( & HTTPProxyHandler : : HandleStreamRequestComplete ,
shared_from_this ( ) , std : : placeholders : : _1 ) , m_address , m_port ) ;
}
else
AsyncSockRead ( ) ;
}
if ( HandleRequest ( len ) )
return ; /* request processed */
AsyncSockRead ( ) ;
}
void HTTPProxy Handler : : SentHTTPFailed ( const boost : : system : : error_code & ecode )
void HTTPReqHandler : : SentHTTPFailed ( const boost : : system : : error_code & ecode )
{
if ( ecode )
LogPrint ( eLogError , " HTTPProxy: Closing socket after sending failure because: " , ecode . message ( ) ) ;
Terminate ( ) ;
}
void HTTPProxy Handler : : HandleStreamRequestComplete ( std : : shared_ptr < i2p : : stream : : Stream > stream )
void HTTPReq Handler : : HandleStreamRequestComplete ( std : : shared_ptr < i2p : : stream : : Stream > stream )
{
if ( stream )
{
if ( Kill ( ) ) return ;
LogPrint ( eLogInfo , " HTTPProxy: New I2PTunnel connection " ) ;
auto connection = std : : make_shared < i2p : : client : : I2PTunnelConnection > ( GetOwner ( ) , m_sock , stream ) ;
GetOwner ( ) - > AddHandler ( connection ) ;
connection - > I2PConnect ( reinterpret_cast < const uint8_t * > ( m_request . data ( ) ) , m_request . size ( ) ) ;
Done ( shared_from_this ( ) ) ;
}
else
{
if ( ! stream ) {
LogPrint ( eLogError , " HTTPProxy: error when creating the stream, check the previous warnings for more info " ) ;
HTTPRequestFailed ( " error when creating the stream, check logs " ) ;
return ;
}
if ( Kill ( ) )
return ;
LogPrint ( eLogDebug , " HTTPProxy: New I2PTunnel connection " ) ;
auto connection = std : : make_shared < i2p : : client : : I2PTunnelConnection > ( GetOwner ( ) , m_sock , stream ) ;
GetOwner ( ) - > AddHandler ( connection ) ;
connection - > I2PConnect ( m_send_buf . data ( ) , m_send_buf . size ( ) ) ;
Done ( shared_from_this ( ) ) ;
}
HTTPProxyServer : : HTTPProxyServer ( const std : : string & address , int port , std : : shared_ptr < i2p : : client : : ClientDestination > localDestination ) :
HTTPProxy : : HTTPProxy ( const std : : string & address , int port , std : : shared_ptr < i2p : : client : : ClientDestination > localDestination ) :
TCPIPAcceptor ( address , port , localDestination ? localDestination : i2p : : client : : context . GetSharedLocalDestination ( ) )
{
}
std : : shared_ptr < i2p : : client : : I2PServiceHandler > HTTPProxyServer : : CreateHandler ( std : : shared_ptr < boost : : asio : : ip : : tcp : : socket > socket )
std : : shared_ptr < i2p : : client : : I2PServiceHandler > HTTPProxy : : CreateHandler ( std : : shared_ptr < boost : : asio : : ip : : tcp : : socket > socket )
{
return std : : make_shared < HTTPProxy Handler > ( this , socket ) ;
return std : : make_shared < HTTPReq Handler > ( this , socket ) ;
}
}
}
} // http
} // i2p