/ libi2pd_client / HTTPProxy.cpp
HTTPProxy.cpp
1 /* 2 * Copyright (c) 2013-2026, The PurpleI2P Project 3 * 4 * This file is part of Purple i2pd project and licensed under BSD3 5 * 6 * See full license text in LICENSE file at top of project tree 7 */ 8 9 #include <cstring> 10 #include <cassert> 11 #include <string> 12 #include <string_view> 13 #include <atomic> 14 #include <memory> 15 #include <set> 16 #include <boost/asio.hpp> 17 #include <boost/algorithm/string/predicate.hpp> 18 #include <mutex> 19 20 #include "I2PService.h" 21 #include "Destination.h" 22 #include "HTTPProxy.h" 23 #include "util.h" 24 #include "Identity.h" 25 #include "Streaming.h" 26 #include "Destination.h" 27 #include "ClientContext.h" 28 #include "I2PEndian.h" 29 #include "I2PTunnel.h" 30 #include "Config.h" 31 #include "HTTP.h" 32 #include "I18N.h" 33 #include "Socks5.h" 34 35 namespace i2p 36 { 37 namespace proxy 38 { 39 static constexpr std::array jumporder = 40 { 41 "reg.i2p", 42 "stats.i2p", 43 "identiguy.i2p", 44 "notbob.i2p" 45 }; 46 47 static const std::map<std::string, std::string> jumpservices = { 48 { "reg.i2p", "http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/jump/" }, 49 { "identiguy.i2p", "http://3mzmrus2oron5fxptw7hw2puho3bnqmw2hqy7nw64dsrrjwdilva.b32.i2p/cgi-bin/query?hostname=" }, 50 { "stats.i2p", "http://7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq.b32.i2p/cgi-bin/jump.cgi?a=" }, 51 { "notbob.i2p", "http://nytzrhrjjfsutowojvxi7hphesskpqqr65wpistz6wa7cpajhp7a.b32.i2p/cgi-bin/jump.cgi?q=" } 52 }; 53 54 static constexpr std::string_view pageHead = 55 "<head>\r\n" 56 " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n" 57 " <title>I2Pd HTTP proxy</title>\r\n" 58 " <style type=\"text/css\">\r\n" 59 " body { font: 100%/1.5em sans-serif; margin: 0; padding: 1.5em; background: #FAFAFA; color: #103456; }\r\n" 60 " h1 { font-size: 1.7em; color: #894C84; }\r\n" 61 " @media screen and (max-width: 980px) { h1 { font-size: 1.7em; text-align: center; color: #894C84; }}\r\n" 62 " </style>\r\n" 63 "</head>\r\n" 64 ; 65 66 static bool str_rmatch(std::string_view str, std::string_view suffix) 67 { 68 auto pos = str.rfind (suffix); 69 if (pos == std::string_view::npos) 70 return false; /* not found */ 71 if (str.length() == pos + suffix.length ()) 72 return true; /* match */ 73 return false; 74 } 75 76 class HTTPReqHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this<HTTPReqHandler> 77 { 78 private: 79 80 bool HandleRequest(); 81 void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); 82 void Terminate(); 83 void AsyncSockRead(); 84 static bool ExtractAddressHelper(i2p::http::URL& url, std::string& jump, bool& confirm); 85 static bool VerifyAddressHelper (std::string_view jump); 86 void SanitizeHTTPRequest(i2p::http::HTTPReq& req); 87 void SentHTTPFailed(const boost::system::error_code & ecode); 88 void HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream); 89 /* error helpers */ 90 void GenericProxyError(std::string_view title, std::string_view description); 91 void GenericProxyInfo(std::string_view title, std::string_view description); 92 void HostNotFound(std::string_view host); 93 void SendProxyError(std::string_view content); 94 void SendRedirect(const std::string& address); 95 96 void ForwardToUpstreamProxy(); 97 void HandleUpstreamHTTPProxyConnect(const boost::system::error_code & ec); 98 void HandleUpstreamSocksProxyConnect(const boost::system::error_code & ec); 99 void HTTPConnect(std::string_view host, uint16_t port); 100 void HandleHTTPConnectStreamRequestComplete(std::shared_ptr<i2p::stream::Stream> stream); 101 102 typedef std::function<void(boost::asio::ip::tcp::endpoint)> ProxyResolvedHandler; 103 104 void HandleUpstreamProxyResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::results_type endpoints, ProxyResolvedHandler handler); 105 106 void SocksProxySuccess(); 107 void HandoverToUpstreamProxy(); 108 109 uint8_t m_recv_chunk[8192]; 110 std::string m_recv_buf; // from client 111 std::string m_send_buf; // to upstream 112 std::shared_ptr<boost::asio::ip::tcp::socket> m_sock; 113 std::shared_ptr<boost::asio::ip::tcp::socket> m_proxysock; 114 boost::asio::ip::tcp::resolver m_proxy_resolver; 115 std::string m_OutproxyUrl, m_Response; 116 bool m_Addresshelper, m_SendUserAgent; 117 i2p::http::URL m_ProxyURL; 118 i2p::http::URL m_RequestURL; 119 int m_req_len; 120 i2p::http::URL m_ClientRequestURL; 121 i2p::http::HTTPReq m_ClientRequest; 122 i2p::http::HTTPRes m_ClientResponse; 123 std::stringstream m_ClientRequestBuffer; 124 125 public: 126 127 HTTPReqHandler(HTTPProxy * parent, std::shared_ptr<boost::asio::ip::tcp::socket> sock) : 128 I2PServiceHandler(parent), m_sock(sock), 129 m_proxysock(std::make_shared<boost::asio::ip::tcp::socket>(parent->GetService())), 130 m_proxy_resolver(parent->GetService()), 131 m_OutproxyUrl(parent->GetOutproxyURL()), 132 m_Addresshelper(parent->GetHelperSupport()), 133 m_SendUserAgent (parent->GetSendUserAgent ()) {} 134 ~HTTPReqHandler() { Terminate(); } 135 void Handle () { AsyncSockRead(); } /* overload */ 136 }; 137 138 void HTTPReqHandler::AsyncSockRead() 139 { 140 LogPrint(eLogDebug, "HTTPProxy: Async sock read"); 141 if (!m_sock) { 142 LogPrint(eLogError, "HTTPProxy: No socket for read"); 143 return; 144 } 145 m_sock->async_read_some(boost::asio::buffer(m_recv_chunk, sizeof(m_recv_chunk)), 146 std::bind(&HTTPReqHandler::HandleSockRecv, shared_from_this(), 147 std::placeholders::_1, std::placeholders::_2)); 148 } 149 150 void HTTPReqHandler::Terminate() { 151 if (Kill()) return; 152 if (m_sock) 153 { 154 LogPrint(eLogDebug, "HTTPProxy: Close sock"); 155 m_sock->close(); 156 m_sock = nullptr; 157 } 158 if(m_proxysock) 159 { 160 LogPrint(eLogDebug, "HTTPProxy: Close proxysock"); 161 if(m_proxysock->is_open()) 162 m_proxysock->close(); 163 m_proxysock = nullptr; 164 } 165 Done(shared_from_this()); 166 } 167 168 void HTTPReqHandler::GenericProxyError(std::string_view title, std::string_view description) 169 { 170 std::stringstream ss; 171 ss << "<h1>" << tr("Proxy error") << ": " << title << "</h1>\r\n"; 172 ss << "<p>" << description << "</p>\r\n"; 173 SendProxyError(ss.str ()); 174 } 175 176 void HTTPReqHandler::GenericProxyInfo(std::string_view title, std::string_view description) 177 { 178 std::stringstream ss; 179 ss << "<h1>" << tr("Proxy info") << ": " << title << "</h1>\r\n"; 180 ss << "<p>" << description << "</p>\r\n"; 181 SendProxyError(ss.str ()); 182 } 183 184 void HTTPReqHandler::HostNotFound(std::string_view host) 185 { 186 std::stringstream ss; 187 ss << "<h1>" << tr("Proxy error: Host not found") << "</h1>\r\n" 188 << "<p>" << tr("Remote host not found in router's addressbook") << "</p>\r\n" 189 << "<p>" << tr("You may try to find this host on jump services below") << ":</p>\r\n" 190 << "<ul>\r\n"; 191 for (const auto& jump : jumporder) 192 { 193 auto js = jumpservices.find (jump); 194 if (js != jumpservices.end()) 195 ss << " <li><a href=\"" << js->second << host << "\">" << js->first << "</a></li>\r\n"; 196 } 197 ss << "</ul>\r\n"; 198 SendProxyError(ss.str ()); 199 } 200 201 void HTTPReqHandler::SendProxyError(std::string_view content) 202 { 203 i2p::http::HTTPRes res; 204 res.code = 500; 205 res.add_header("Content-Type", "text/html; charset=UTF-8"); 206 res.add_header("Connection", "close"); 207 std::stringstream ss; 208 ss << "<html>\r\n" << pageHead 209 << "<body>" << content << "</body>\r\n" 210 << "</html>\r\n"; 211 res.body = ss.str(); 212 m_Response = res.to_string(); 213 boost::asio::async_write(*m_sock, boost::asio::buffer(m_Response), boost::asio::transfer_all(), 214 std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); 215 } 216 217 void HTTPReqHandler::SendRedirect(const std::string& address) 218 { 219 i2p::http::HTTPRes res; 220 res.code = 302; 221 res.add_header("Location", address); 222 res.add_header("Connection", "close"); 223 m_Response = res.to_string(); 224 boost::asio::async_write(*m_sock, boost::asio::buffer(m_Response), boost::asio::transfer_all(), 225 std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); 226 } 227 228 bool HTTPReqHandler::ExtractAddressHelper(i2p::http::URL& url, std::string& jump, bool& confirm) 229 { 230 confirm = false; 231 const char *param = "i2paddresshelper="; 232 std::size_t pos = url.query.find(param); 233 std::size_t len = std::strlen(param); 234 std::map<std::string, std::string> params; 235 236 237 if (pos == std::string::npos) 238 return false; /* not found */ 239 if (!url.parse_query(params)) 240 return false; 241 242 std::string value = params["i2paddresshelper"]; 243 len += value.length(); 244 jump = i2p::http::UrlDecode(value); 245 if (!VerifyAddressHelper (jump)) 246 { 247 LogPrint (eLogError, "HTTPProxy: Malformed jump link ", jump); 248 return false; 249 } 250 251 // if we need update exists, request formed with update param 252 if (params["update"] == "true") 253 { 254 len += std::strlen("&update=true"); 255 confirm = true; 256 } 257 258 // if helper is not only one query option and it placed after user's query 259 if (pos != 0 && url.query[pos-1] == '&') 260 { 261 pos--; 262 len++; 263 } 264 // if helper is not only one query option and it placed before user's query 265 else if (pos == 0 && url.query.length () > len && url.query[len] == '&') 266 { 267 // we don't touch the '?' but remove the trailing '&' 268 len++; 269 } 270 else 271 { 272 // there is no more query options, resetting hasquery flag 273 url.hasquery = false; 274 } 275 276 // reset hasquery flag and remove addresshelper from URL 277 url.query.replace(pos, len, ""); 278 return true; 279 } 280 281 bool HTTPReqHandler::VerifyAddressHelper (std::string_view jump) 282 { 283 auto pos = jump.find(".b32.i2p"); 284 if (pos != std::string::npos) 285 { 286 auto b32 = jump.substr (0, pos); 287 for (auto& ch: b32) 288 if (!i2p::data::IsBase32(ch)) return false; 289 return true; 290 } 291 else 292 { 293 bool padding = false; 294 for (auto& ch: jump) 295 { 296 if (ch == '=') 297 padding = true; 298 else 299 { 300 if (padding) return false; // other chars after padding 301 if (!i2p::data::IsBase64(ch)) return false; 302 } 303 } 304 return true; 305 } 306 return false; 307 } 308 309 void HTTPReqHandler::SanitizeHTTPRequest(i2p::http::HTTPReq& req) 310 { 311 /* drop common headers */ 312 req.RemoveHeader("Via"); 313 req.RemoveHeader("From"); 314 req.RemoveHeader("Forwarded"); 315 req.RemoveHeader("DNT"); // Useless DoNotTrack flag 316 req.RemoveHeader("Accept", "Accept-Encoding"); // Accept*, but Accept-Encoding 317 /* drop proxy-disclosing headers */ 318 req.RemoveHeader("X-Forwarded"); 319 req.RemoveHeader("Proxy-"); // Proxy-* 320 /* replace headers */ 321 if (!m_SendUserAgent) 322 req.UpdateHeader("User-Agent", "MYOB/6.66 (AN/ON)"); 323 324 /** 325 * i2pd PR #1816: 326 * Android Webview send this with the value set to the application ID, so we drop it, 327 * but only if it does not belong to an AJAX request (*HttpRequest, like XMLHttpRequest). 328 */ 329 if(req.GetHeader("X-Requested-With") != "") { 330 auto h = req.GetHeader ("X-Requested-With"); 331 auto x = h.find("HttpRequest"); 332 if (x == std::string::npos) // not found 333 req.RemoveHeader("X-Requested-With"); 334 } 335 336 /** 337 * according to i2p ticket #1862: 338 * leave Referer if requested URL with same schema, host and port, 339 * otherwise, drop it. 340 */ 341 if(req.GetHeader("Referer") != "") { 342 i2p::http::URL reqURL; reqURL.parse(req.uri); 343 i2p::http::URL refURL; refURL.parse(req.GetHeader("Referer")); 344 if(!boost::iequals(reqURL.schema, refURL.schema) || !boost::iequals(reqURL.host, refURL.host) || reqURL.port != refURL.port) 345 req.RemoveHeader("Referer"); 346 } 347 348 /* add headers */ 349 /* close connection, if not Connection: (U|u)pgrade (for websocket) */ 350 auto h = req.GetHeader ("Connection"); 351 auto x = h.find("pgrade"); 352 if (!(x != std::string::npos && std::tolower(h[x - 1]) == 'u')) 353 req.UpdateHeader("Connection", "close"); 354 } 355 356 /** 357 * @brief Try to parse request from @a m_recv_buf 358 * If parsing success, rebuild request and store to @a m_send_buf 359 * with remaining data tail 360 * @return true on processed request or false if more data needed 361 */ 362 bool HTTPReqHandler::HandleRequest() 363 { 364 m_req_len = m_ClientRequest.parse(m_recv_buf); 365 366 if (m_req_len == 0) 367 return false; /* need more data */ 368 369 if (m_req_len < 0) { 370 LogPrint(eLogError, "HTTPProxy: Unable to parse request"); 371 GenericProxyError(tr("Invalid request"), tr("Proxy unable to parse your request")); 372 return true; /* parse error */ 373 } 374 375 /* parsing success, now let's look inside request */ 376 LogPrint(eLogDebug, "HTTPProxy: Requested: ", m_ClientRequest.uri); 377 m_RequestURL.parse(m_ClientRequest.uri); 378 bool m_Confirm; 379 380 std::string jump; 381 if (ExtractAddressHelper(m_RequestURL, jump, m_Confirm)) 382 { 383 if (!m_Addresshelper || !i2p::client::context.GetAddressBook ().IsEnabled ()) 384 { 385 LogPrint(eLogWarning, "HTTPProxy: Addresshelper request rejected"); 386 GenericProxyError(tr("Invalid request"), tr("Addresshelper is not supported")); 387 return true; 388 } 389 390 if (i2p::client::context.GetAddressBook ().RecordExists (m_RequestURL.host, jump)) 391 { 392 std::string full_url = m_RequestURL.to_string(); 393 SendRedirect(full_url); 394 return true; 395 } 396 else if (!i2p::client::context.GetAddressBook ().FindAddress (m_RequestURL.host) || m_Confirm) 397 { 398 const std::string referer_raw = m_ClientRequest.GetHeader("Referer"); 399 i2p::http::URL referer_url; 400 if (!referer_raw.empty ()) 401 { 402 referer_url.parse (referer_raw); 403 } 404 if (m_RequestURL.host != referer_url.host) 405 { 406 if (m_Confirm) // Attempt to forced overwriting by link with "&update=true" from harmful URL 407 { 408 LogPrint (eLogWarning, "HTTPProxy: Address update from addresshelper rejected for ", m_RequestURL.host, " (referer is ", m_RequestURL.host.empty() ? "empty" : "harmful", ")"); 409 std::string full_url = m_RequestURL.to_string(); 410 std::stringstream ss; 411 ss << tr("Host %s is <font color=red>already in router's addressbook</font>. <b>Be careful: source of this URL may be harmful!</b> Click here to update record: <a href=\"%s%s%s&update=true\">Continue</a>.", 412 m_RequestURL.host.c_str(), full_url.c_str(), (full_url.find('?') != std::string::npos ? "&i2paddresshelper=" : "?i2paddresshelper="), jump.c_str()); 413 GenericProxyInfo(tr("Addresshelper forced update rejected"), ss.str()); 414 } 415 else // Preventing unauthorized additions to the address book 416 { 417 LogPrint (eLogDebug, "HTTPProxy: Adding address from addresshelper for ", m_RequestURL.host, " (generate refer-base page)"); 418 std::string full_url = m_RequestURL.to_string(); 419 std::stringstream ss; 420 ss << tr("To add host <b>%s</b> in router's addressbook, click here: <a href=\"%s%s%s\">Continue</a>.", 421 m_RequestURL.host.c_str(), full_url.c_str(), (full_url.find('?') != std::string::npos ? "&i2paddresshelper=" : "?i2paddresshelper="), jump.c_str()); 422 GenericProxyInfo(tr("Addresshelper request"), ss.str()); 423 } 424 return true; /* request processed */ 425 } 426 427 i2p::client::context.GetAddressBook ().InsertAddress (m_RequestURL.host, jump); 428 LogPrint (eLogInfo, "HTTPProxy: Added address from addresshelper for ", m_RequestURL.host); 429 std::string full_url = m_RequestURL.to_string(); 430 std::stringstream ss; 431 ss << tr("Host %s added to router's addressbook from helper. Click here to proceed: <a href=\"%s\">Continue</a>.", 432 m_RequestURL.host.c_str(), full_url.c_str()); 433 GenericProxyInfo(tr("Addresshelper adding"), ss.str()); 434 return true; /* request processed */ 435 } 436 else 437 { 438 std::string full_url = m_RequestURL.to_string(); 439 std::stringstream ss; 440 ss << tr("Host %s is <font color=red>already in router's addressbook</font>. Click here to update record: <a href=\"%s%s%s&update=true\">Continue</a>.", 441 m_RequestURL.host.c_str(), full_url.c_str(), (full_url.find('?') != std::string::npos ? "&i2paddresshelper=" : "?i2paddresshelper="), jump.c_str()); 442 GenericProxyInfo(tr("Addresshelper update"), ss.str()); 443 return true; /* request processed */ 444 } 445 } 446 std::string dest_host; 447 uint16_t dest_port; 448 bool useConnect = false; 449 if(m_ClientRequest.method == "CONNECT") 450 { 451 const std::string& uri = m_ClientRequest.uri; 452 auto pos = uri.find(":"); 453 if(pos == std::string::npos || pos == uri.size() - 1) 454 { 455 GenericProxyError(tr("Invalid request"), tr("Invalid request URI")); 456 return true; 457 } 458 else 459 { 460 useConnect = true; 461 dest_port = std::stoi(uri.substr(pos+1)); 462 dest_host = uri.substr(0, pos); 463 } 464 } 465 else 466 { 467 SanitizeHTTPRequest(m_ClientRequest); 468 469 dest_host = m_RequestURL.host; 470 dest_port = m_RequestURL.port; 471 /* always set port, even if missing in request */ 472 if (!dest_port) 473 dest_port = (m_RequestURL.schema == "https") ? 443 : 80; 474 /* detect dest_host, set proper 'Host' header in upstream request */ 475 if (dest_host != "") 476 { 477 /* absolute url, replace 'Host' header */ 478 std::string h (dest_host); 479 if (dest_port != 0 && dest_port != 80) 480 h += ":" + std::to_string(dest_port); 481 m_ClientRequest.UpdateHeader("Host", h); 482 } 483 else 484 { 485 auto h = m_ClientRequest.GetHeader ("Host"); 486 if (h.length () > 0) 487 { 488 /* relative url and 'Host' header provided. transparent proxy mode? */ 489 i2p::http::URL u; 490 std::string t = "http://" + h; 491 u.parse(t); 492 dest_host = u.host; 493 dest_port = u.port; 494 } 495 else 496 { 497 /* relative url and missing 'Host' header */ 498 GenericProxyError(tr("Invalid request"), tr("Can't detect destination host from request")); 499 return true; 500 } 501 } 502 } 503 /* check dest_host really exists and inside I2P network */ 504 if (str_rmatch(dest_host, ".i2p")) { 505 if (!i2p::client::context.GetAddressBook ().GetAddress (dest_host)) { 506 HostNotFound(dest_host); 507 return true; /* request processed */ 508 } 509 } else { 510 if(m_OutproxyUrl.size()) { 511 LogPrint (eLogDebug, "HTTPProxy: Using outproxy ", m_OutproxyUrl); 512 if(m_ProxyURL.parse(m_OutproxyUrl)) 513 ForwardToUpstreamProxy(); 514 else 515 GenericProxyError(tr("Outproxy failure"), tr("Bad outproxy settings")); 516 } else { 517 LogPrint (eLogWarning, "HTTPProxy: Outproxy failure for ", dest_host, ": no outproxy enabled"); 518 std::stringstream ss; ss << tr("Host %s is not inside I2P network, but outproxy is not enabled", dest_host.c_str ()); 519 GenericProxyError(tr("Outproxy failure"), ss.str()); 520 } 521 return true; 522 } 523 if(useConnect) 524 { 525 HTTPConnect(dest_host, dest_port); 526 return true; 527 } 528 529 /* make relative url */ 530 m_RequestURL.schema = ""; 531 m_RequestURL.host = ""; 532 m_ClientRequest.uri = m_RequestURL.to_string(); 533 534 /* drop original request from recv buffer */ 535 m_recv_buf.erase(0, m_req_len); 536 /* build new buffer from modified request and data from original request */ 537 m_send_buf = m_ClientRequest.to_string(); 538 m_send_buf.append(m_recv_buf); 539 /* connect to destination */ 540 LogPrint(eLogDebug, "HTTPProxy: Connecting to host ", dest_host, ":", dest_port); 541 GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleStreamRequestComplete, 542 shared_from_this(), std::placeholders::_1), dest_host, dest_port); 543 return true; 544 } 545 546 void HTTPReqHandler::ForwardToUpstreamProxy() 547 { 548 LogPrint(eLogDebug, "HTTPProxy: Forwarded to upstream"); 549 550 /* build http request */ 551 m_ClientRequestURL = m_RequestURL; 552 LogPrint(eLogDebug, "HTTPProxy: ", m_ClientRequestURL.host); 553 m_ClientRequestURL.schema = ""; 554 m_ClientRequestURL.host = ""; 555 std::string origURI = m_ClientRequest.uri; // TODO: what do we need to change uri for? 556 m_ClientRequest.uri = m_ClientRequestURL.to_string(); 557 558 /* update User-Agent to ESR version of Firefox, same as Tor Browser below version 13, for non-HTTPS connections */ 559 if(m_ClientRequest.method != "CONNECT" && !m_SendUserAgent) 560 m_ClientRequest.UpdateHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; rv:109.0) Gecko/20100101 Firefox/115.0"); 561 562 m_ClientRequest.write(m_ClientRequestBuffer); 563 m_ClientRequestBuffer << m_recv_buf.substr(m_req_len); 564 565 /* assume http if empty schema */ 566 if (m_ProxyURL.schema == "" || m_ProxyURL.schema == "http") 567 { 568 /* handle upstream http proxy */ 569 if (!m_ProxyURL.port) m_ProxyURL.port = 80; 570 if (m_ProxyURL.is_i2p()) 571 { 572 m_ClientRequest.uri = origURI; 573 auto auth = i2p::http::CreateBasicAuthorizationString (m_ProxyURL.user, m_ProxyURL.pass); 574 if (!auth.empty ()) 575 { 576 /* remove existing authorization if any */ 577 m_ClientRequest.RemoveHeader("Proxy-"); 578 /* add own http proxy authorization */ 579 m_ClientRequest.AddHeader("Proxy-Authorization", auth); 580 } 581 m_send_buf = m_ClientRequest.to_string(); 582 m_recv_buf.erase(0, m_req_len); 583 m_send_buf.append(m_recv_buf); 584 GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleStreamRequestComplete, 585 shared_from_this(), std::placeholders::_1), m_ProxyURL.host, m_ProxyURL.port); 586 } 587 else 588 { 589 m_proxy_resolver.async_resolve(m_ProxyURL.host, std::to_string(m_ProxyURL.port), std::bind(&HTTPReqHandler::HandleUpstreamProxyResolved, this, 590 std::placeholders::_1, std::placeholders::_2, [&](boost::asio::ip::tcp::endpoint ep) 591 { 592 m_proxysock->async_connect(ep, std::bind(&HTTPReqHandler::HandleUpstreamHTTPProxyConnect, this, std::placeholders::_1)); 593 })); 594 } 595 } 596 else if (m_ProxyURL.schema == "socks") 597 { 598 /* handle upstream socks proxy */ 599 if (!m_ProxyURL.port) m_ProxyURL.port = 9050; // default to tor default if not specified 600 m_proxy_resolver.async_resolve(m_ProxyURL.host, std::to_string(m_ProxyURL.port), std::bind(&HTTPReqHandler::HandleUpstreamProxyResolved, this, 601 std::placeholders::_1, std::placeholders::_2, [&](boost::asio::ip::tcp::endpoint ep) 602 { 603 m_proxysock->async_connect(ep, std::bind(&HTTPReqHandler::HandleUpstreamSocksProxyConnect, this, std::placeholders::_1)); 604 })); 605 } 606 else 607 { 608 /* unknown type, complain */ 609 GenericProxyError(tr("Unknown outproxy URL"), m_ProxyURL.to_string()); 610 } 611 } 612 613 void HTTPReqHandler::HandleUpstreamProxyResolved(const boost::system::error_code & ec, boost::asio::ip::tcp::resolver::results_type endpoints, ProxyResolvedHandler handler) 614 { 615 if(ec) GenericProxyError(tr("Cannot resolve upstream proxy"), ec.message()); 616 else handler(*endpoints.begin ()); 617 } 618 619 void HTTPReqHandler::HandleUpstreamSocksProxyConnect(const boost::system::error_code & ec) 620 { 621 if(!ec) 622 { 623 if(m_RequestURL.host.size() > 255) 624 { 625 GenericProxyError(tr("Hostname is too long"), m_RequestURL.host); 626 return; 627 } 628 uint16_t port = m_RequestURL.port; 629 if(!port) port = 80; 630 LogPrint(eLogDebug, "HTTPProxy: Connected to SOCKS upstream"); 631 std::string host = m_RequestURL.host; 632 auto s = shared_from_this (); 633 i2p::transport::Socks5Handshake (*m_proxysock, std::make_pair(host, port), 634 [s](const boost::system::error_code& ec) 635 { 636 if (!ec) 637 s->SocksProxySuccess(); 638 else 639 s->GenericProxyError(tr("SOCKS proxy error"), ec.message ()); 640 }); 641 642 } 643 else 644 GenericProxyError(tr("Cannot connect to upstream SOCKS proxy"), ec.message()); 645 } 646 647 void HTTPReqHandler::HandoverToUpstreamProxy() 648 { 649 LogPrint(eLogDebug, "HTTPProxy: Handover to SOCKS proxy"); 650 auto connection = CreateSocketsPipe (GetOwner(), m_proxysock, m_sock); 651 m_sock = nullptr; 652 m_proxysock = nullptr; 653 GetOwner()->AddHandler(connection); 654 connection->Start(); 655 Terminate(); 656 } 657 658 void HTTPReqHandler::HTTPConnect(std::string_view host, uint16_t port) 659 { 660 LogPrint(eLogDebug, "HTTPProxy: CONNECT ",host, ":", port); 661 if(str_rmatch(host, ".i2p")) 662 GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleHTTPConnectStreamRequestComplete, 663 shared_from_this(), std::placeholders::_1), host, port); 664 else 665 ForwardToUpstreamProxy(); 666 } 667 668 void HTTPReqHandler::HandleHTTPConnectStreamRequestComplete(std::shared_ptr<i2p::stream::Stream> stream) 669 { 670 if(stream) 671 { 672 m_ClientResponse.code = 200; 673 m_ClientResponse.status = "OK"; 674 m_send_buf = m_ClientResponse.to_string(); 675 m_sock->send(boost::asio::buffer(m_send_buf)); 676 auto connection = std::make_shared<i2p::client::I2PTunnelConnection>(GetOwner(), m_sock, stream); 677 GetOwner()->AddHandler(connection); 678 connection->I2PConnect(); 679 m_sock = nullptr; 680 Terminate(); 681 } 682 else 683 { 684 GenericProxyError(tr("CONNECT error"), tr("Failed to connect")); 685 } 686 } 687 688 void HTTPReqHandler::SocksProxySuccess() 689 { 690 if(m_ClientRequest.method == "CONNECT") { 691 m_ClientResponse.code = 200; 692 m_send_buf = m_ClientResponse.to_string(); 693 boost::asio::async_write(*m_sock, boost::asio::buffer(m_send_buf), boost::asio::transfer_all(), [&] (const boost::system::error_code & ec, std::size_t transferred) 694 { 695 if(ec) GenericProxyError(tr("SOCKS proxy error"), ec.message()); 696 else HandoverToUpstreamProxy(); 697 }); 698 } else { 699 m_send_buf = m_ClientRequestBuffer.str(); 700 LogPrint(eLogDebug, "HTTPProxy: Send ", m_send_buf.size(), " bytes"); 701 boost::asio::async_write(*m_proxysock, boost::asio::buffer(m_send_buf), boost::asio::transfer_all(), [&](const boost::system::error_code & ec, std::size_t transferred) 702 { 703 if(ec) GenericProxyError(tr("Failed to send request to upstream"), ec.message()); 704 else HandoverToUpstreamProxy(); 705 }); 706 } 707 } 708 709 void HTTPReqHandler::HandleUpstreamHTTPProxyConnect(const boost::system::error_code & ec) 710 { 711 if(!ec) { 712 LogPrint(eLogDebug, "HTTPProxy: Connected to http upstream"); 713 GenericProxyError(tr("Cannot connect"), tr("HTTP out proxy not implemented")); 714 } else GenericProxyError(tr("Cannot connect to upstream HTTP proxy"), ec.message()); 715 } 716 717 /* will be called after some data received from client */ 718 void HTTPReqHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len) 719 { 720 LogPrint(eLogDebug, "HTTPProxy: Sock recv: ", len, " bytes, recv buf: ", m_recv_buf.length(), ", send buf: ", m_send_buf.length()); 721 if(ecode) 722 { 723 LogPrint(eLogWarning, "HTTPProxy: Sock recv got error: ", ecode); 724 Terminate(); 725 return; 726 } 727 728 m_recv_buf.append(reinterpret_cast<const char *>(m_recv_chunk), len); 729 if (HandleRequest()) { 730 m_recv_buf.clear(); 731 return; 732 } 733 AsyncSockRead(); 734 } 735 736 void HTTPReqHandler::SentHTTPFailed(const boost::system::error_code & ecode) 737 { 738 if (ecode) 739 LogPrint (eLogError, "HTTPProxy: Closing socket after sending failure because: ", ecode.message ()); 740 Terminate(); 741 } 742 743 void HTTPReqHandler::HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream) 744 { 745 if (!stream) { 746 LogPrint (eLogError, "HTTPProxy: Error when creating the stream, check the previous warnings for more info"); 747 GenericProxyError(tr("Host is down"), tr("Can't create connection to requested host, it may be down. Please try again later.")); 748 return; 749 } 750 if (Kill()) 751 return; 752 LogPrint (eLogDebug, "HTTPProxy: Created new I2PTunnel stream, sSID=", stream->GetSendStreamID(), ", rSID=", stream->GetRecvStreamID()); 753 auto connection = std::make_shared<i2p::client::I2PClientTunnelConnectionHTTP>(GetOwner(), m_sock, stream); 754 GetOwner()->AddHandler (connection); 755 connection->I2PConnect (reinterpret_cast<const uint8_t*>(m_send_buf.data()), m_send_buf.length()); 756 Done (shared_from_this()); 757 } 758 759 HTTPProxy::HTTPProxy(const std::string& name, const std::string& address, uint16_t port, 760 const std::string & outproxy, bool addresshelper, bool senduseragent, std::shared_ptr<i2p::client::ClientDestination> localDestination): 761 TCPIPAcceptor (address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()), 762 m_Name (name), m_OutproxyUrl (outproxy), m_Addresshelper (addresshelper), m_SendUserAgent (senduseragent) 763 { 764 } 765 766 std::shared_ptr<i2p::client::I2PServiceHandler> HTTPProxy::CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket) 767 { 768 return std::make_shared<HTTPReqHandler> (this, socket); 769 } 770 } // http 771 } // i2p