/ daemon / HTTPServer.cpp
HTTPServer.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 <time.h>
  10  #include <iomanip>
  11  #include <sstream>
  12  #include <thread>
  13  #include <memory>
  14  #include <regex>
  15  
  16  #include <boost/asio.hpp>
  17  #include <boost/algorithm/string.hpp>
  18  
  19  #include "Base.h"
  20  #include "FS.h"
  21  #include "Log.h"
  22  #include "Config.h"
  23  #include "Tunnel.h"
  24  #include "Transports.h"
  25  #include "NetDb.hpp"
  26  #include "HTTP.h"
  27  #include "LeaseSet.h"
  28  #include "Destination.h"
  29  #include "RouterContext.h"
  30  #include "ClientContext.h"
  31  #include "HTTPServer.h"
  32  #include "Daemon.h"
  33  #include "util.h"
  34  #include "ECIESX25519AEADRatchetSession.h"
  35  #include "I18N.h"
  36  
  37  #ifdef WIN32_APP
  38  #include "Win32App.h"
  39  #endif
  40  
  41  // For image, style and info
  42  #include "version.h"
  43  #include "HTTPServerResources.h"
  44  
  45  namespace i2p {
  46  namespace http {
  47  
  48  	static void LoadExtCSS (std::string fileName = "style")
  49  	{
  50  		std::stringstream s;
  51  		std::string styleFile = i2p::fs::DataDirPath ("webconsole/"+fileName+".css");
  52  		if (i2p::fs::Exists(styleFile)) {
  53  			std::ifstream f(styleFile, std::ifstream::binary);
  54  			s << f.rdbuf();
  55  			externalCSS = s.str();
  56  		} else if (externalCSS.length() != 0) { // clean up external style if file was removed
  57  			externalCSS = "";
  58  		}
  59  	}
  60  
  61  	static void GetStyles (std::stringstream& s)
  62  	{
  63  		if (externalCSS.length() != 0)
  64  			s << "<style>\r\n" << externalCSS << "</style>\r\n";
  65  		else
  66  			s << internalCSS;
  67  	}
  68  
  69  	const char HTTP_PAGE_TUNNELS[] = "tunnels";
  70  	const char HTTP_PAGE_TRANSIT_TUNNELS[] = "transit_tunnels";
  71  	const char HTTP_PAGE_TRANSPORTS[] = "transports";
  72  	const char HTTP_PAGE_LOCAL_DESTINATIONS[] = "local_destinations";
  73  	const char HTTP_PAGE_LOCAL_DESTINATION[] = "local_destination";
  74  	const char HTTP_PAGE_I2CP_LOCAL_DESTINATION[] = "i2cp_local_destination";
  75  	const char HTTP_PAGE_SAM_SESSIONS[] = "sam_sessions";
  76  	const char HTTP_PAGE_SAM_SESSION[] = "sam_session";
  77  	const char HTTP_PAGE_I2P_TUNNELS[] = "i2p_tunnels";
  78  	const char HTTP_PAGE_COMMANDS[] = "commands";
  79  	const char HTTP_PAGE_LEASESETS[] = "leasesets";
  80  	const char HTTP_COMMAND_ENABLE_TRANSIT[] = "enable_transit";
  81  	const char HTTP_COMMAND_DISABLE_TRANSIT[] = "disable_transit";
  82  	const char HTTP_COMMAND_SHUTDOWN_START[] = "shutdown_start";
  83  	const char HTTP_COMMAND_SHUTDOWN_CANCEL[] = "shutdown_cancel";
  84  	const char HTTP_COMMAND_SHUTDOWN_NOW[] = "terminate";
  85  	const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test";
  86  	const char HTTP_COMMAND_RELOAD_TUNNELS_CONFIG[] = "reload_tunnels_config";
  87  	const char HTTP_COMMAND_LOGLEVEL[] = "set_loglevel";
  88  	const char HTTP_COMMAND_KILLSTREAM[] = "closestream";
  89  	const char HTTP_COMMAND_LIMITTRANSIT[] = "limittransit";
  90  	const char HTTP_COMMAND_GET_REG_STRING[] = "get_reg_string";
  91  	const char HTTP_COMMAND_SETLANGUAGE[] = "setlanguage";
  92  	const char HTTP_COMMAND_RELOAD_CSS[] = "reload_css";
  93  	const char HTTP_COMMAND_EXPIRELEASE[] = "expirelease";
  94  
  95  	static std::string ConvertTime (uint64_t time)
  96  	{
  97  		struct tm caltime;
  98  		lldiv_t divTime = lldiv(time, 1000);
  99  		time_t t = divTime.quot;
 100  #ifdef _WIN32
 101  		localtime_s(&caltime, &t);
 102  #else
 103  		localtime_r(&t, &caltime);
 104  #endif
 105  		char date[128];
 106  		snprintf(date, sizeof(date), "%02d/%02d/%d %02d:%02d:%02d.%03lld", caltime.tm_mday, caltime.tm_mon + 1, caltime.tm_year + 1900, caltime.tm_hour, caltime.tm_min, caltime.tm_sec, divTime.rem);
 107  		return date;
 108  	}
 109  
 110  	static void ShowUptime (std::stringstream& s, int seconds)
 111  	{
 112  		int num;
 113  
 114  		if ((num = seconds / 86400) > 0) {
 115  			s << ntr("%d day", "%d days", num, num) << ", ";
 116  			seconds -= num * 86400;
 117  		}
 118  		if ((num = seconds / 3600) > 0) {
 119  			s << ntr("%d hour", "%d hours", num, num) << ", ";
 120  			seconds -= num * 3600;
 121  		}
 122  		if ((num = seconds / 60) > 0) {
 123  			s << ntr("%d minute", "%d minutes", num, num) << ", ";
 124  			seconds -= num * 60;
 125  		}
 126  		s << ntr("%d second", "%d seconds", seconds, seconds);
 127  	}
 128  
 129  	static void ShowTraffic (std::stringstream& s, uint64_t bytes)
 130  	{
 131  		s << std::fixed << std::setprecision(2);
 132  		auto numKBytes = (double) bytes / 1024;
 133  		if (numKBytes < 1024)
 134  			/* Kibibyte */
 135  			s << numKBytes << " KiB";
 136  		else if (numKBytes < 1024 * 1024)
 137  			/* Mebibyte */
 138  			s << (numKBytes / 1024) << " MiB" ;
 139  		else
 140  			/* Gibibyte */
 141  			s << (numKBytes / 1024 / 1024) << " GiB";
 142  	}
 143  
 144  	static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, int bytes)
 145  	{
 146  		std::string state;
 147  		std::string_view stateText;
 148  		switch (eState)
 149  		{
 150  			case i2p::tunnel::eTunnelStateBuildReplyReceived :
 151  			case i2p::tunnel::eTunnelStatePending     : state = "building";    break;
 152  			case i2p::tunnel::eTunnelStateBuildFailed : state = "failed"; stateText = "declined"; break;
 153  			case i2p::tunnel::eTunnelStateTestFailed  : state = "failed"; stateText = "test failed";  break;
 154  			case i2p::tunnel::eTunnelStateFailed      : state = "failed";      break;
 155  			case i2p::tunnel::eTunnelStateExpiring    : state = "expiring";    break;
 156  			case i2p::tunnel::eTunnelStateEstablished : state = "established"; break;
 157  			default: state = "unknown"; break;
 158  		}
 159  		if (stateText.empty ()) stateText = tr(state);
 160  
 161  		s << "<span class=\"tunnel " << state << "\"> " << stateText << ((explr) ? " (" + std::string(tr("exploratory")) + ")" : "") << "</span>, "; // TODO:
 162  		ShowTraffic(s, bytes);
 163  		s << "\r\n";
 164  	}
 165  
 166  	static void SetLogLevel (const std::string& level)
 167  	{
 168  		if (level == "none" || level == "critical" || level == "error" || level == "warn" || level == "info" || level == "debug")
 169  			i2p::log::Logger().SetLogLevel(level);
 170  		else {
 171  			LogPrint(eLogError, "HTTPServer: Unknown loglevel set attempted");
 172  			return;
 173  		}
 174  		i2p::log::Logger().Reopen ();
 175  	}
 176  
 177  	static void ShowPageHead (std::stringstream& s)
 178  	{
 179  		std::string webroot; i2p::config::GetOption("http.webroot", webroot);
 180  		std::string theme; i2p::config::GetOption("http.theme", theme);
 181  
 182  		const auto isThemeRegex = std::regex("^(white|black|light)");
 183  		if (!std::regex_search(theme, isThemeRegex))
 184  		{
 185  			LoadExtCSS(theme);
 186  		}
 187  		else
 188  		{
 189  			LoadExtCSS();
 190  		}
 191  
 192  		// Page language
 193  		std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage(); // get current used language
 194  		auto it = i2p::i18n::languages.find(currLang);
 195  		// fallback to default English, if language is not supported
 196  		if (it == i2p::i18n::languages.end()) {
 197  			it = i2p::i18n::languages.find("english");
 198  		}
 199  		std::string langCode = it->second.ShortCode;
 200  
 201  		// Right to Left language option
 202  		bool rtl = i2p::client::context.GetLanguage ()->GetRTL();
 203  
 204  		s <<
 205  			"<!DOCTYPE html>\r\n"
 206  			"<html lang=\"" << langCode << "\"" << (rtl ? " dir=\"rtl\"" : "") << ">\r\n"
 207  			"<head>\r\n" /* TODO: Find something to parse html/template system. This is horrible. */
 208  			"  <meta charset=\"UTF-8\">\r\n"
 209  			"  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n"
 210  			"  <link rel=\"shortcut icon\" href=\"" << itoopieFavicon << "\">\r\n"
 211  			"  <title>" << tr(/* tr: Webconsole page title */ "Purple I2P Webconsole") << "</title>\r\n";
 212  		GetStyles(s);
 213  		if (theme == "black")
 214  		{
 215  		s <<
 216  		"<style>:root {\r\n"
 217           	"--main-bg-color: #242424;\r\n"
 218                  "--main-text-color: #17ab5c;\r\n"
 219                  "--main-link-color: #bf64b7;\r\n"
 220                  "--main-link-hover-color: #000000;\r\n"
 221          	"}\r\n</style>";
 222  
 223  		}
 224  		s <<
 225  			"</head>\r\n"
 226  			"<body>\r\n"
 227  			"<div class=\"header\">" << tr("<b>i2pd</b> webconsole") << "</div>\r\n"
 228  			"<div class=\"wrapper\">\r\n"
 229  			"<div class=\"menu\">\r\n"
 230  			"  <a href=\"" << webroot << "\">" << tr("Main page") << "</a><br><br>\r\n"
 231  			"  <a href=\"" << webroot << "?page=" << HTTP_PAGE_COMMANDS << "\">" << tr("Router commands") << "</a><br>\r\n"
 232  			"  <a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATIONS << "\">" << tr("Local Destinations") << "</a><br>\r\n";
 233  		if (i2p::context.IsFloodfill ())
 234  			s << "  <a href=\"" << webroot << "?page=" << HTTP_PAGE_LEASESETS << "\">" << tr("LeaseSets") << "</a><br>\r\n";
 235  		s <<
 236  			"  <a href=\"" << webroot << "?page=" << HTTP_PAGE_TUNNELS << "\">" << tr("Tunnels") << "</a><br>\r\n";
 237  		if (i2p::context.AcceptsTunnels () || i2p::tunnel::tunnels.CountTransitTunnels())
 238  			s << "  <a href=\"" << webroot << "?page=" << HTTP_PAGE_TRANSIT_TUNNELS << "\">" << tr("Transit Tunnels") << "</a><br>\r\n";
 239  		s <<
 240  			"  <a href=\"" << webroot << "?page=" << HTTP_PAGE_TRANSPORTS << "\">" << tr("Transports") << "</a><br>\r\n"
 241  			"  <a href=\"" << webroot << "?page=" << HTTP_PAGE_I2P_TUNNELS << "\">" << tr("I2P tunnels") << "</a><br>\r\n";
 242  		if (i2p::client::context.GetSAMBridge ())
 243  			s << "  <a href=\"" << webroot << "?page=" << HTTP_PAGE_SAM_SESSIONS << "\">" << tr("SAM sessions") << "</a><br>\r\n";
 244  		s <<
 245  			"</div>\r\n\r\n"
 246  			"<div class=\"content\">\r\n\r\n";
 247  	}
 248  
 249  	static void ShowPageTail (std::stringstream& s)
 250  	{
 251  		s <<
 252  			"</div>\r\n</div>\r\n"
 253  			"</body>\r\n"
 254  			"</html>\r\n";
 255  	}
 256  
 257  	static void ShowError(std::stringstream& s, std::string_view string)
 258  	{
 259  		s << "<b>" << tr("ERROR") << ":</b>&nbsp;" << string << "<br>\r\n";
 260  	}
 261  
 262  	static void ShowNetworkStatus (std::stringstream& s, RouterStatus status, bool testing, RouterError error)
 263  	{
 264  		switch (status)
 265  		{
 266  			case eRouterStatusOK: s << tr("OK"); break;
 267  			case eRouterStatusFirewalled: s << tr("Firewalled"); break;
 268  			case eRouterStatusUnknown: s << tr("Unknown"); break;
 269  			case eRouterStatusProxy: s << tr("Proxy"); break;
 270  			case eRouterStatusMesh: s << tr("Mesh"); break;
 271  			case eRouterStatusStan: s << tr("Stan"); break;
 272  			default: s << tr("Unknown");
 273  		}
 274  		if (testing)
 275  			s << " (" << tr("Testing") << ")";
 276  		if (error != eRouterErrorNone)
 277  		{
 278  			switch (error)
 279  			{
 280  				case eRouterErrorClockSkew:
 281  					s << " - " << tr("Clock skew");
 282  				break;
 283  				case eRouterErrorOffline:
 284  					s << " - " << tr("Offline");
 285  				break;
 286  				case eRouterErrorSymmetricNAT:
 287  					s << " - " << tr("Symmetric NAT");
 288  				break;
 289  				case eRouterErrorFullConeNAT:
 290  					s << " - " << tr("Full cone NAT");
 291  				break;
 292  				case eRouterErrorNoDescriptors:
 293  					s << " - " << tr("No Descriptors");
 294  				break;
 295  				default: ;
 296  			}
 297  		}
 298  	}
 299  
 300  	void ShowStatus (std::stringstream& s, bool includeHiddenContent, i2p::http::OutputFormatEnum outputFormat)
 301  	{
 302  		s << "<b>" << tr("Uptime") << ":</b> ";
 303  		ShowUptime(s, i2p::context.GetUptime ());
 304  		s << "<br>\r\n";
 305  		if (i2p::context.SupportsV4 () || i2p::context.GetStatus () != eRouterStatusUnknown) // don't show Unknown for ipv6-only
 306  		{
 307  			s << "<b>" << tr("Network status") << ":</b> ";
 308  			ShowNetworkStatus (s, i2p::context.GetStatus (), i2p::context.GetTesting(), i2p::context.GetError ());
 309  			s << "<br>\r\n";
 310  		}
 311  		if (i2p::context.SupportsV6 ())
 312  		{
 313  			s << "<b>" << tr("Network status v6") << ":</b> ";
 314  			ShowNetworkStatus (s, i2p::context.GetStatusV6 (), i2p::context.GetTestingV6(), i2p::context.GetErrorV6 ());
 315  			s << "<br>\r\n";
 316  		}
 317  		auto remains = Daemon.GetGracefulShutdownInterval ();
 318  		if (remains > 0)
 319  		{
 320  			s << "<b>" << tr("Stopping in") << ":</b> ";
 321  			ShowUptime(s, remains);
 322  			s << "<br>\r\n";
 323  		}
 324  		auto family = i2p::context.GetFamily ();
 325  		if (family.length () > 0)
 326  			s << "<b>"<< tr("Family") << ":</b> " << family << "<br>\r\n";
 327  		s << "<b>" << tr("Tunnel creation success rate") << ":</b> " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%<br>\r\n";
 328  		bool isTotalTCSR;
 329  		i2p::config::GetOption("http.showTotalTCSR", isTotalTCSR);
 330  		if (isTotalTCSR) {
 331  			s << "<b>" << tr("Total tunnel creation success rate") << ":</b> " << i2p::tunnel::tunnels.GetTotalTunnelCreationSuccessRate() << "%<br/>\r\n";
 332  		}
 333  		s << std::fixed << std::setprecision(2);
 334  		/* Kibibyte/s */
 335  		s << "<b>" << tr("Received") << ":</b> ";
 336  		ShowTraffic (s, i2p::transport::transports.GetTotalReceivedBytes ());
 337  		s << " (" << ((double) i2p::transport::transports.GetInBandwidth15s () / 1024) << " KiB/s)" << "<br>\r\n";
 338  		s << "<b>" << tr("Sent") << ":</b> ";
 339  		ShowTraffic (s, i2p::transport::transports.GetTotalSentBytes ());
 340  		s << " (" << ((double) i2p::transport::transports.GetOutBandwidth15s () / 1024) << " KiB/s)" << "<br>\r\n";
 341  		s << "<b>" << tr("Transit") << ":</b> ";
 342  		ShowTraffic (s, i2p::transport::transports.GetTotalTransitTransmittedBytes ());
 343  		s << " (" << ((double) i2p::transport::transports.GetTransitBandwidth15s () / 1024) << " KiB/s)" << "<br>\r\n";
 344  		s << "<b>" << tr("Data path") << ":</b> " << i2p::fs::GetUTF8DataDir() << "<br>\r\n";
 345  		s << "<div class='slide'>";
 346  		if ((outputFormat == OutputFormatEnum::forWebConsole) || !includeHiddenContent) {
 347  			s << "<label for=\"slide-info\">" << tr("Hidden content. Press on text to see.") << "</label>\r\n";
 348  			s << "<input type=\"checkbox\" id=\"slide-info\" />\r\n";
 349  			s << "<div class=\"slidecontent\">\r\n";
 350  		}
 351  		if (includeHiddenContent)
 352  		{
 353  			s << "<b>" << tr("Router Ident") << ":</b> " << i2p::context.GetRouterInfo().GetIdentHashBase64() << "<br>\r\n";
 354  			if (!i2p::context.GetRouterInfo().GetProperty("family").empty())
 355  				s << "<b>" << tr("Router Family") << ":</b> " << i2p::context.GetRouterInfo().GetProperty("family") << "<br>\r\n";
 356  			s << "<b>" << tr("Router Caps") << ":</b> " << i2p::context.GetRouterInfo().GetProperty("caps") << "<br>\r\n";
 357  			s << "<b>" << tr("Version") << ":</b> " VERSION "<br>\r\n";
 358  			s << "<b>"<< tr("Our external address") << ":</b>" << "<br>\r\n";
 359  			s << "<table class=\"extaddr\">\r\n<tbody>\r\n";
 360  			auto addresses = i2p::context.GetRouterInfo().GetAddresses ();
 361  			if (addresses)
 362  			{
 363  				for (const auto& address : *addresses)
 364  				{
 365  					if (!address) continue;
 366  					s << "<tr>\r\n<td>";
 367  					switch (address->transportStyle)
 368  					{
 369  						case i2p::data::RouterInfo::eTransportNTCP2:
 370  							s << "NTCP2";
 371  						break;
 372  						case i2p::data::RouterInfo::eTransportSSU2:
 373  							s << "SSU2";
 374  						break;
 375  						default:
 376  							s << tr("Unknown");
 377  					}
 378  					bool v6 = address->IsV6 ();
 379  					if (v6)
 380  					{
 381  						if (address->IsV4 ()) s << "v4";
 382  						s << "v6";
 383  					}
 384  					s << "</td>\r\n";
 385  					if (address->published) {
 386  						s << "<td style=\"padding-left: 0.5em;\">";
 387  						s << (v6 ? "[" : "") << address->host.to_string() << (v6 ? "]:" : ":");
 388  						s << address->port << "</td>\r\n";
 389  					}
 390  					else
 391  					{
 392  						/* tr: Shown when router doesn't publish itself and have "Firewalled" state */
 393  						s << "<td style=\"padding-left: 0.5em;\">" << tr("supported");
 394  						if (address->port)
 395  							s << " :" << address->port;
 396  						s << "</td>\r\n";
 397  					}
 398  					s << "</tr>\r\n";
 399  				}
 400  			}
 401  			s << "</tbody>\r\n</table>\r\n";
 402  		}
 403  		if ((outputFormat == OutputFormatEnum::forWebConsole) || !includeHiddenContent) {
 404  			s << "</div>\r\n"; // class slidecontent
 405  		}
 406  		s << "</div>\r\n"; // class slide
 407  		if (outputFormat == OutputFormatEnum::forQtUi) {
 408  			s << "<br>";
 409  		}
 410  		s << "<b>" << tr("Routers") << ":</b> " << i2p::data::netdb.GetNumRouters () << "&nbsp;&nbsp;&nbsp;";
 411  		s << "<b>" << tr("Floodfills") << ":</b> " << i2p::data::netdb.GetNumFloodfills () << "&nbsp;&nbsp;&nbsp;";
 412  		s << "<b>" << tr("LeaseSets") << ":</b> " << i2p::data::netdb.GetNumLeaseSets () << "<br>\r\n";
 413  
 414  		size_t clientTunnelCount = i2p::tunnel::tunnels.CountOutboundTunnels();
 415  		clientTunnelCount += i2p::tunnel::tunnels.CountInboundTunnels();
 416  		size_t transitTunnelCount = i2p::tunnel::tunnels.CountTransitTunnels();
 417  
 418  		s << "<b>" << tr("Client Tunnels") << ":</b> " << std::to_string(clientTunnelCount) << "&nbsp;&nbsp;&nbsp;";
 419  		s << "<b>" << tr("Transit Tunnels") << ":</b> " << std::to_string(transitTunnelCount) << "<br>\r\n<br>\r\n";
 420  
 421  		if (outputFormat==OutputFormatEnum::forWebConsole) {
 422  			bool httpproxy  = i2p::client::context.GetHttpProxy ()         ? true : false;
 423  			bool socksproxy = i2p::client::context.GetSocksProxy ()        ? true : false;
 424  			bool bob        = i2p::client::context.GetBOBCommandChannel () ? true : false;
 425  			bool sam        = i2p::client::context.GetSAMBridge ()         ? true : false;
 426  			bool i2cp       = i2p::client::context.GetI2CPServer ()        ? true : false;
 427  			bool i2pcontrol;  i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol);
 428  			s << "<table class=\"services\">\r\n<caption>" << tr("Services") << "</caption>\r\n<tbody>\r\n";
 429  			s << "<tr><td>" << "HTTP " << tr("Proxy")  << "</td><td class='" << (httpproxy  ? "enabled" : "disabled") << "'>" << (httpproxy  ? tr("Enabled") : tr("Disabled")) << "</td></tr>\r\n";
 430  			s << "<tr><td>" << "SOCKS " << tr("Proxy") << "</td><td class='" << (socksproxy ? "enabled" : "disabled") << "'>" << (socksproxy ? tr("Enabled") : tr("Disabled")) << "</td></tr>\r\n";
 431  			s << "<tr><td>" << "BOB"                   << "</td><td class='" << (bob        ? "enabled" : "disabled") << "'>" << (bob        ? tr("Enabled") : tr("Disabled")) << "</td></tr>\r\n";
 432  			s << "<tr><td>" << "SAM"                   << "</td><td class='" << (sam        ? "enabled" : "disabled") << "'>" << (sam        ? tr("Enabled") : tr("Disabled")) << "</td></tr>\r\n";
 433  			s << "<tr><td>" << "I2CP"                  << "</td><td class='" << (i2cp       ? "enabled" : "disabled") << "'>" << (i2cp       ? tr("Enabled") : tr("Disabled")) << "</td></tr>\r\n";
 434  			s << "<tr><td>" << "I2PControl"            << "</td><td class='" << (i2pcontrol ? "enabled" : "disabled") << "'>" << (i2pcontrol ? tr("Enabled") : tr("Disabled")) << "</td></tr>\r\n";
 435  			s << "</tbody>\r\n</table>\r\n<br>\r\n";
 436  		}
 437  	}
 438  
 439  	void ShowLocalDestinations (std::stringstream& s)
 440  	{
 441  		std::string webroot; i2p::config::GetOption("http.webroot", webroot);
 442  		s << "<b>" << tr("Local Destinations") << ":</b><br>\r\n<div class=\"list\">\r\n";
 443  		for (auto& it: i2p::client::context.GetDestinations ())
 444  		{
 445  			auto ident = it.second->GetIdentHash ();
 446  			s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
 447  			s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "</a></div>\r\n" << std::endl;
 448  		}
 449  		s << "</div>\r\n<br>\r\n";
 450  
 451  		auto i2cpServer = i2p::client::context.GetI2CPServer ();
 452  		if (i2cpServer && !(i2cpServer->GetSessions ().empty ()))
 453  		{
 454  			s << "<br><b>I2CP "<< tr("Local Destinations") << ":</b><br>\r\n<div class=\"list\">\r\n";
 455  			for (auto& it: i2cpServer->GetSessions ())
 456  			{
 457  				auto dest = it.second->GetDestination ();
 458  				if (dest)
 459  				{
 460  					auto ident = dest->GetIdentHash ();
 461  					auto& name = dest->GetNickname ();
 462  					s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_I2CP_LOCAL_DESTINATION << "&i2cp_id=" << it.first << "\">[ ";
 463  					s << name << " ]</a> &#8660; " << i2p::client::context.GetAddressBook ().ToAddress(ident) <<"</div>\r\n" << std::endl;
 464  				}
 465  			}
 466  			s << "</div>\r\n<br>\r\n";
 467  		}
 468  	}
 469  
 470  	static void ShowHop(std::stringstream& s, const i2p::data::IdentityEx& ident)
 471  	{
 472  		auto identHash = ident.GetIdentHash();
 473  		auto router = i2p::data::netdb.FindRouter(identHash);
 474  		s << i2p::data::GetIdentHashAbbreviation(identHash);
 475  		if (router)
 476  			s << "<small style=\"color:gray\"> " << router->GetBandwidthCap() << "</small>";
 477  	}
 478  
 479  	static void ShowLeaseSetDestination (std::stringstream& s, std::shared_ptr<const i2p::client::LeaseSetDestination> dest, uint32_t token)
 480  	{
 481  		std::string webroot; i2p::config::GetOption("http.webroot", webroot);
 482  		s << "<b>Base32:</b><br>\r\n<textarea readonly cols=\"80\" rows=\"1\">";
 483  		s << dest->GetIdentHash ().ToBase32 () << "</textarea><br>\r\n<br>\r\n";
 484  
 485  		s << "<b>Base64:</b><br>\r\n<textarea readonly cols=\"80\" rows=\"8\">";
 486  		s << dest->GetIdentity ()->ToBase64 () << "</textarea><br>\r\n<br>\r\n";
 487  
 488  		if (dest->IsEncryptedLeaseSet ())
 489  		{
 490  			i2p::data::BlindedPublicKey blinded (dest->GetIdentity (), dest->IsPerClientAuth ());
 491  			s << "<div class='slide'><label for='slide-b33'><b>" << tr("Encrypted B33 address") << ":</b></label>\r\n";
 492  			s << "<input type=\"checkbox\" id=\"slide-b33\" />\r\n";
 493  			s << "<div class=\"slidecontent\">\r\n";
 494  			s << blinded.ToB33 () << ".b32.i2p<br>\r\n";
 495  			s << "</div>\r\n</div>\r\n";
 496  		}
 497  
 498  		if (dest->IsPublic() && token && !dest->IsEncryptedLeaseSet ())
 499  		{
 500  			std::string webroot; i2p::config::GetOption("http.webroot", webroot);
 501  			s << "<div class='slide'><label for='slide-regaddr'><b>" << tr("Address registration line") << "</b></label>\r\n"
 502  			     "<input type=\"checkbox\" id=\"slide-regaddr\" />\r\n"
 503  			     "<div class=\"slidecontent\">\r\n"
 504  			     "<form method=\"get\" action=\"" << webroot << "\">\r\n"
 505  			     "  <input type=\"hidden\" name=\"cmd\" value=\"" << HTTP_COMMAND_GET_REG_STRING << "\">\r\n"
 506  			     "  <input type=\"hidden\" name=\"token\" value=\"" << token << "\">\r\n"
 507  			     "  <input type=\"hidden\" name=\"b32\" value=\"" << dest->GetIdentHash ().ToBase32 () << "\">\r\n"
 508  			     "  <b>" << tr("Domain") << ":</b>\r\n<input type=\"text\" maxlength=\"67\" name=\"name\" placeholder=\"domain.i2p\" required>\r\n"
 509  			     "  <button type=\"submit\">" << tr("Generate") << "</button>\r\n"
 510  			     "</form>\r\n"
 511  			     "<small>" << tr("<b>Note:</b> result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.") << "</small>\r\n"
 512  			     "</div>\r\n</div>\r\n<br>\r\n";
 513  		}
 514  
 515  		if (dest->GetNumRemoteLeaseSets())
 516  		{
 517  			s << "<div class='slide'><label for='slide-lease'><b>" << tr("LeaseSets") << ":</b> <i>" << dest->GetNumRemoteLeaseSets ()
 518  			  << "</i></label>\r\n<input type=\"checkbox\" id=\"slide-lease\" />\r\n"
 519  			  << "<div class=\"slidecontent\">\r\n"
 520  			  << "<table>\r\n<thead><tr>"
 521  			  << "<th>" << tr("Address") << "</th>"
 522  			  << "<th style=\"width:5px;\">&nbsp;</th>" // LeaseSet expiration button column
 523  			  << "<th>" << tr("Type") << "</th>"
 524  			  << "<th>" << tr("EncType") << "</th>"
 525  			  << "</tr></thead>\r\n<tbody class=\"tableitem\">";
 526  			for(auto& it: dest->GetLeaseSets ())
 527  			{
 528  				s << "<tr>"
 529  				  << "<td>" << it.first.ToBase32 () << "</td>"
 530  				  << "<td><a class=\"button\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_EXPIRELEASE<< "&b32=" << dest->GetIdentHash ().ToBase32 ()
 531  				  << "&lease=" << it.first.ToBase32 () << "&token=" << token << "\" title=\"" << tr("Expire LeaseSet") << "\"> &#10008; </a></td>"
 532  				  << "<td>" << (int)it.second->GetStoreType () << "</td>"
 533  				  << "<td>" << (int)it.second->GetEncryptionType () <<"</td>"
 534  				  << "</tr>\r\n";
 535  			}
 536  			s << "</tbody>\r\n</table>\r\n";
 537  			s << "</div>\r\n</div>\r\n<br>\r\n";
 538  		} else
 539  			s << "<b>" << tr("LeaseSets") << ":</b> <i>0</i><br>\r\n<br>\r\n";
 540  
 541  		auto pool = dest->GetTunnelPool ();
 542  		if (pool)
 543  		{
 544  			s << "<b>" << tr("Inbound tunnels") << ":</b><br>\r\n<div class=\"list\">\r\n";
 545  			for (auto & it : pool->GetInboundTunnels ()) {
 546  				s << "<div class=\"listitem\">";
 547  				// for each tunnel hop if not zero-hop
 548  				if (it->GetNumHops ())
 549  				{
 550  					it->VisitTunnelHops(
 551  						[&s](std::shared_ptr<const i2p::data::IdentityEx> hopIdent)
 552  						{
 553  							s << "&#8658; ";
 554  							ShowHop(s, *hopIdent);
 555  							s << " ";
 556  						}
 557  					);
 558  				}
 559  				s << "&#8658; " << it->GetTunnelID () << ":me";
 560  				if (it->LatencyIsKnown())
 561  					s << " ( " << tr(/* tr: Milliseconds */ "%dms", it->GetMeanLatency()) << " )";
 562  				ShowTunnelDetails(s, it->GetState (), false, it->GetNumReceivedBytes ());
 563  				s << "</div>\r\n";
 564  			}
 565  			s << "</div>\r\n<br>\r\n";
 566  			s << "<b>" << tr("Outbound tunnels") << ":</b><br>\r\n<div class=\"list\">\r\n";
 567  			for (auto & it : pool->GetOutboundTunnels ()) {
 568  				s << "<div class=\"listitem\">";
 569  				s << it->GetTunnelID () << ":me &#8658;";
 570  				// for each tunnel hop if not zero-hop
 571  				if (it->GetNumHops ())
 572  				{
 573  					it->VisitTunnelHops(
 574  						[&s](std::shared_ptr<const i2p::data::IdentityEx> hopIdent)
 575  						{
 576  							s << " ";
 577  							ShowHop(s, *hopIdent);
 578  							s << " &#8658;";
 579  						}
 580  					);
 581  				}
 582  				if (it->LatencyIsKnown())
 583  					s << " ( " << tr("%dms", it->GetMeanLatency()) << " )";
 584  				ShowTunnelDetails(s, it->GetState (), false, it->GetNumSentBytes ());
 585  				s << "</div>\r\n";
 586  			}
 587  			s << "</div>\r\n<br>\r\n";
 588  		}
 589  		s << "<br>\r\n";
 590  
 591  		s << "<b>" << tr("Tags") << "</b><br>\r\n"
 592  		  << tr("Incoming") << ": <i>" << dest->GetNumIncomingTags () << "</i><br>\r\n";
 593  		if (!dest->GetSessions ().empty ()) {
 594  			std::stringstream tmp_s; uint32_t out_tags = 0;
 595  			for (const auto& it: dest->GetSessions ()) {
 596  				tmp_s << "<tr><td>" << i2p::client::context.GetAddressBook ().ToAddress(it.first) << "</td><td>" << it.second->GetNumOutgoingTags () << "</td></tr>\r\n";
 597  				out_tags += it.second->GetNumOutgoingTags ();
 598  			}
 599  			s << "<div class='slide'><label for='slide-tags'>" << tr("Outgoing") << ": <i>" << out_tags << "</i></label>\r\n"
 600  			  << "<input type=\"checkbox\" id=\"slide-tags\" />\r\n"
 601  			  << "<div class=\"slidecontent\">\r\n"
 602  			  << "<table>\r\n<thead><tr><th>" << tr("Destination") << "</th><th>" << tr("Amount") << "</th></tr></thead>\r\n"
 603  			  << "<tbody class=\"tableitem\">\r\n" << tmp_s.str () << "</tbody>\r\n</table>\r\n"
 604  			  << "</div>\r\n</div>\r\n";
 605  		} else
 606  			s << tr("Outgoing") << ": <i>0</i><br>\r\n";
 607  		s << "<br>\r\n";
 608  
 609  		auto numECIESx25519Tags = dest->GetNumIncomingECIESx25519Tags ();
 610  		if (numECIESx25519Tags > 0) {
 611  			s << "<b>ECIESx25519</b><br>\r\n" << tr("Incoming Tags") << ": <i>" << numECIESx25519Tags << "</i><br>\r\n";
 612  			if (!dest->GetECIESx25519Sessions ().empty ())
 613  			{
 614  				std::stringstream tmp_s; uint32_t ecies_sessions = 0;
 615  				for (const auto& it: dest->GetECIESx25519Sessions ()) {
 616  					tmp_s << "<tr><td>" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetDestination ()) << "</td><td>" << it.second->GetState () << "</td></tr>\r\n";
 617  					ecies_sessions++;
 618  				}
 619  				s << "<div class='slide'><label for='slide-ecies-sessions'>" << tr("Tags sessions") << ": <i>" << ecies_sessions << "</i></label>\r\n"
 620  				  << "<input type=\"checkbox\" id=\"slide-ecies-sessions\" />\r\n"
 621  				  << "<div class=\"slidecontent\">\r\n"
 622  				  << "<table>\r\n<thead><tr><th>" << tr("Destination") << "</th><th>" << tr("Status") << "</th></tr></thead>\r\n"
 623  				  << "<tbody class=\"tableitem\">\r\n" << tmp_s.str () << "</tbody>\r\n</table>\r\n"
 624  				  << "</div>\r\n</div>\r\n";
 625  			} else
 626  				s << tr("Tags sessions") << ": <i>0</i><br>\r\n";
 627  			s << "<br>\r\n";
 628  		}
 629  	}
 630  
 631  	void ShowLocalDestination (std::stringstream& s, const std::string& b32, uint32_t token)
 632  	{
 633  		std::string webroot; i2p::config::GetOption("http.webroot", webroot);
 634  		s << "<b>" << tr("Local Destination") << ":</b><br>\r\n<br>\r\n";
 635  		i2p::data::IdentHash ident;
 636  		ident.FromBase32 (b32);
 637  		auto dest = i2p::client::context.FindLocalDestination (ident);
 638  
 639  		if (dest)
 640  		{
 641  			ShowLeaseSetDestination (s, dest, token);
 642  
 643  			// Print table with streams information
 644  			s << "<table>\r\n<caption>" << tr("Streams") << "</caption>\r\n"
 645  			  << "<thead><tr>"
 646  			  << "<th style=\"width:25px;\">StreamID</th>"
 647  			  << "<th style=\"width:5px;\">&nbsp;</th>" // Stream closing button column
 648  			  << "<th class=\"streamdest\">Destination</th>"
 649  			  << "<th>Sent</th>"
 650  			  << "<th>Received</th>"
 651  			  << "<th>Out</th>"
 652  			  << "<th>In</th>"
 653  			  << "<th>Buf</th>"
 654  			  << "<th>RTT</th>"
 655  			  << "<th>Window</th>"
 656  			  << "<th>Status</th>"
 657  			  << "</tr></thead>\r\n"
 658  			  << "<tbody class=\"tableitem\">\r\n";
 659  
 660  			for (const auto& it: dest->GetAllStreams ())
 661  			{
 662  				auto streamDest = i2p::client::context.GetAddressBook ().ToAddress(it->GetRemoteIdentity ());
 663  				std::string streamDestShort = streamDest.substr(0,12) + "&hellip;.b32.i2p";
 664  				s << "<tr>";
 665  				s << "<td>" << it->GetRecvStreamID () << "</td>";
 666  				if (it->GetRecvStreamID ()) {
 667  					s << "<td><a class=\"button\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_KILLSTREAM << "&b32=" << b32 << "&streamID="
 668  					  << it->GetRecvStreamID () << "&token=" << token << "\" title=\"" << tr("Close stream") << "\"> &#10008; </a></td>";
 669  				}
 670  				else {
 671  					s << "<td> !! FIXME !! </td>"; // TODO: FIXME: Undefined HTML code
 672  				}
 673  				s << "<td class=\"streamdest\" title=\"" << streamDest << "\">" << streamDestShort << "</td>";
 674  				s << "<td>" << it->GetNumSentBytes () << "</td>";
 675  				s << "<td>" << it->GetNumReceivedBytes () << "</td>";
 676  				s << "<td>" << it->GetSendQueueSize () << "</td>";
 677  				s << "<td>" << it->GetReceiveQueueSize () << "</td>";
 678  				s << "<td>" << it->GetSendBufferSize () << "</td>";
 679  				s << "<td>" << it->GetRTT () << "</td>";
 680  				s << "<td>" << it->GetWindowSize () << "</td>";
 681  				s << "<td>" << (int)it->GetStatus () << "</td>";
 682  				s << "</tr>\r\n";
 683  			}
 684  			s << "</tbody>\r\n</table>\r\n<br>\r\n";
 685  		}
 686  		else
 687  			ShowError(s, tr("Such destination is not found"));
 688  	}
 689  
 690  	void ShowI2CPLocalDestination (std::stringstream& s, const std::string& id)
 691  	{
 692  		auto i2cpServer = i2p::client::context.GetI2CPServer ();
 693  		if (i2cpServer)
 694  		{
 695  			s << "<b>I2CP " << tr("Local Destination") << ":</b><br>\r\n<br>\r\n";
 696  			auto it = i2cpServer->GetSessions ().find (std::stoi (id));
 697  			if (it != i2cpServer->GetSessions ().end ())
 698  				ShowLeaseSetDestination (s, it->second->GetDestination (), 0);
 699  			else
 700  				ShowError(s, tr("I2CP session not found"));
 701  		}
 702  		else
 703  			ShowError(s, tr("I2CP is not enabled"));
 704  	}
 705  
 706  	void ShowLeasesSets(std::stringstream& s)
 707  	{
 708  		if (i2p::data::netdb.GetNumLeaseSets ())
 709  		{
 710  			s << "<b>" << tr("LeaseSets") << ":</b><br>\r\n<div class=\"list\">\r\n";
 711  			int counter = 1;
 712  			// for each lease set
 713  			i2p::data::netdb.VisitLeaseSets(
 714  				[&s, &counter](const i2p::data::IdentHash dest, std::shared_ptr<i2p::data::LeaseSet> leaseSet)
 715  				{
 716  					// create copy of lease set so we extract leases
 717  					auto storeType = leaseSet->GetStoreType ();
 718  					std::unique_ptr<i2p::data::LeaseSet> ls;
 719  					if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET)
 720  						ls.reset (new i2p::data::LeaseSet (leaseSet->GetBuffer(), leaseSet->GetBufferLen()));
 721  					else
 722  					{
 723  						ls.reset (new i2p::data::LeaseSet2 (storeType));
 724  						ls->Update (leaseSet->GetBuffer(), leaseSet->GetBufferLen(), nullptr, false);
 725  					}
 726  					if (!ls) return;
 727  					s << "<div class=\"leaseset listitem";
 728  					if (ls->IsExpired())
 729  						s << " expired"; // additional css class for expired
 730  					s << "\">\r\n";
 731  					if (!ls->IsValid())
 732  						s << "<div class=\"invalid\">!! " << tr("Invalid") << " !! </div>\r\n";
 733  					s << "<div class=\"slide\"><label for=\"slide" << counter << "\">" << dest.ToBase32() << "</label>\r\n";
 734  					s << "<input type=\"checkbox\" id=\"slide" << (counter++) << "\" />\r\n";
 735  					s << "<div class=\"slidecontent\">\r\n";
 736  					s << "<b>" << tr("Store type") << ":</b> " << (int)storeType << "<br>\r\n";
 737  					s << "<b>" << tr("Expires") << ":</b> " << ConvertTime(ls->GetExpirationTime()) << "<br>\r\n";
 738  					if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET || storeType == i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2)
 739  					{
 740  						// leases information is available
 741  						auto leases = ls->GetNonExpiredLeases();
 742  						s << "<b>" << tr("Non Expired Leases") << ": " << leases.size() << "</b><br>\r\n";
 743  						for ( auto & l : leases )
 744  						{
 745  							s << "<b>" << tr("Gateway") << ":</b> " << l->tunnelGateway.ToBase64() << "<br>\r\n";
 746  							s << "<b>" << tr("TunnelID") << ":</b> " << l->tunnelID << "<br>\r\n";
 747  							s << "<b>" << tr("EndDate") << ":</b> " << ConvertTime(l->endDate) << "<br>\r\n";
 748  						}
 749  					}
 750  					s << "</div>\r\n</div>\r\n"; // class slide class slidecontent
 751  					s << "</div>\r\n"; // class leaseset listitem
 752  				}
 753  			);
 754  			// end for each lease set
 755  			s << "</div>\r\n<br>\r\n";
 756  		}
 757  		else if (!i2p::context.IsFloodfill ())
 758  		{
 759  			s << "<b>" << tr("LeaseSets") << ":</b> " << tr(/* Message on LeaseSets page */ "floodfill mode is disabled") << ".<br>\r\n";
 760  		}
 761  		else
 762  		{
 763  			s << "<b>" << tr("LeaseSets") << ":</b> 0<br>\r\n";
 764  		}
 765  	}
 766  
 767  	void ShowTunnels (std::stringstream& s)
 768  	{
 769  		s << "<b>" << tr("Tunnels") << ":</b><br>\r\n";
 770  		s << "<b>" << tr("Queue size") << ":</b> " << i2p::tunnel::tunnels.GetQueueSize () << "<br>\r\n<br>\r\n";
 771  		s << "<b>" << tr("TBM Queue size") << ":</b> " << i2p::tunnel::tunnels.GetTBMQueueSize () << "<br>\r\n<br>\r\n";
 772  
 773  		auto ExplPool = i2p::tunnel::tunnels.GetExploratoryPool ();
 774  
 775  		s << "<b>" << tr("Inbound tunnels") << ":</b><br>\r\n<div class=\"list\">\r\n";
 776  		for (auto & it : i2p::tunnel::tunnels.GetInboundTunnels ()) {
 777  			s << "<div class=\"listitem\">";
 778  			if (it->GetNumHops ())
 779  			{
 780  				it->VisitTunnelHops(
 781  					[&s](std::shared_ptr<const i2p::data::IdentityEx> hopIdent)
 782  					{
 783  						s << "&#8658; ";
 784  						ShowHop(s, *hopIdent);
 785  						s << " ";
 786  					}
 787  				);
 788  			}
 789  			s << "&#8658; " << it->GetTunnelID () << ":me";
 790  			if (it->LatencyIsKnown())
 791  			s << " ( " << tr("%dms", it->GetMeanLatency()) << " )";
 792  			ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumReceivedBytes ());
 793  			s << "</div>\r\n";
 794  		}
 795  		s << "</div>\r\n<br>\r\n";
 796  		s << "<b>" << tr("Outbound tunnels") << ":</b><br>\r\n<div class=\"list\">\r\n";
 797  		for (auto & it : i2p::tunnel::tunnels.GetOutboundTunnels ()) {
 798  			s << "<div class=\"listitem\">";
 799  			s << it->GetTunnelID () << ":me &#8658;";
 800  			// for each tunnel hop if not zero-hop
 801  			if (it->GetNumHops ())
 802  			{
 803  				it->VisitTunnelHops(
 804  					[&s](std::shared_ptr<const i2p::data::IdentityEx> hopIdent)
 805  					{
 806  						s << " ";
 807  						ShowHop(s, *hopIdent);
 808  						s << " &#8658;";
 809  					}
 810  				);
 811  			}
 812  			if (it->LatencyIsKnown())
 813  			s << " ( " << tr("%dms", it->GetMeanLatency()) << " )";
 814  			ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumSentBytes ());
 815  			s << "</div>\r\n";
 816  		}
 817  		s << "</div>\r\n<br>\r\n";
 818  	}
 819  
 820  	static void ShowCommands (std::stringstream& s, uint32_t token)
 821  	{
 822  		std::string webroot; i2p::config::GetOption("http.webroot", webroot);
 823  
 824  		s << "<b>" << tr("Router commands") << "</b><br>\r\n<br>\r\n<div class=\"commands\">\r\n";
 825  		s << "  <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_RUN_PEER_TEST << "&token=" << token << "\">" << tr("Run peer test") << "</a><br>\r\n";
 826  		s << "  <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_RELOAD_TUNNELS_CONFIG << "&token=" << token << "\">" << tr("Reload tunnels configuration") << "</a><br>\r\n";
 827  
 828  		if (i2p::context.AcceptsTunnels ())
 829  			s << "  <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_DISABLE_TRANSIT << "&token=" << token << "\">" << tr("Decline transit tunnels") << "</a><br>\r\n";
 830  		else
 831  			s << "  <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_ENABLE_TRANSIT << "&token=" << token << "\">" << tr("Accept transit tunnels") << "</a><br>\r\n";
 832  
 833  #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY))
 834  		if (Daemon.gracefulShutdownInterval)
 835  			s << "  <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_SHUTDOWN_CANCEL << "&token=" << token << "\">" << tr("Cancel graceful shutdown") << "</a><br>\r\n";
 836  		else
 837  			s << "  <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_SHUTDOWN_START << "&token=" << token << "\">" << tr("Start graceful shutdown") << "</a><br>\r\n";
 838  #elif defined(WIN32_APP)
 839  		if (i2p::util::DaemonWin32::Instance().isGraceful)
 840  			s << "  <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_SHUTDOWN_CANCEL << "&token=" << token << "\">" << tr("Cancel graceful shutdown") << "</a><br>\r\n";
 841  		else
 842  			s << "  <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_SHUTDOWN_START << "&token=" << token << "\">" << tr("Start graceful shutdown") << "</a><br>\r\n";
 843  #endif
 844  
 845  		s << "  <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_SHUTDOWN_NOW << "&token=" << token << "\">" << tr("Force shutdown") << "</a><br><br>\r\n";
 846  		s << "  <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_RELOAD_CSS << "&token=" << token << "\">" << tr("Reload external CSS styles") << "</a>\r\n";
 847  		s << "</div>";
 848  
 849  		s << "<br>\r\n<small>" << tr("<b>Note:</b> any action done here are not persistent and not changes your config files.") << "</small>\r\n<br>\r\n";
 850  
 851  		auto loglevel = i2p::log::Logger().GetLogLevel();
 852  		s << "<b>" << tr("Logging level") << "</b><br>\r\n";
 853  		s << "  <a class=\"button" << (loglevel == eLogNone     ? " selected" : "") << "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=none&token=" << token << "\"> none </a> \r\n";
 854  		s << "  <a class=\"button" << (loglevel == eLogCritical ? " selected" : "") << "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=critical&token=" << token << "\"> critical </a> \r\n";
 855  		s << "  <a class=\"button" << (loglevel == eLogError    ? " selected" : "") << "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=error&token=" << token << "\"> error </a> \r\n";
 856  		s << "  <a class=\"button" << (loglevel == eLogWarning  ? " selected" : "") << "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=warn&token=" << token << "\"> warn </a> \r\n";
 857  		s << "  <a class=\"button" << (loglevel == eLogInfo     ? " selected" : "") << "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=info&token=" << token << "\"> info </a> \r\n";
 858  		s << "  <a class=\"button" << (loglevel == eLogDebug    ? " selected" : "") << "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=debug&token=" << token << "\"> debug </a><br>\r\n<br>\r\n";
 859  
 860  		uint32_t maxTunnels = i2p::tunnel::tunnels.GetMaxNumTransitTunnels ();
 861  		s << "<b>" << tr("Transit tunnels limit") << "</b><br>\r\n";
 862  		s << "<form method=\"get\" action=\"" << webroot << "\">\r\n";
 863  		s << "  <input type=\"hidden\" name=\"cmd\" value=\"" << HTTP_COMMAND_LIMITTRANSIT << "\">\r\n";
 864  		s << "  <input type=\"hidden\" name=\"token\" value=\"" << token << "\">\r\n";
 865  		s << "  <input type=\"number\" min=\"2\" max=\"" << TRANSIT_TUNNELS_LIMIT <<"\" name=\"limit\" value=\"" << maxTunnels << "\">\r\n";
 866  		s << "  <button type=\"submit\">" << tr("Change") << "</button>\r\n";
 867  		s << "</form>\r\n<br>\r\n";
 868  
 869  		// get current used language
 870  		std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage();
 871  
 872  		s << "<b>"
 873  		  << tr("Change language")
 874  		  << "</b><br>\r\n"
 875  		  << "<form method=\"get\" action=\"" << webroot << "\">\r\n"
 876  		  << "  <input type=\"hidden\" name=\"cmd\" value=\"" << HTTP_COMMAND_SETLANGUAGE << "\">\r\n"
 877  		  << "  <input type=\"hidden\" name=\"token\" value=\"" << token << "\">\r\n"
 878  		  << "  <select name=\"lang\" id=\"lang\">\r\n";
 879  
 880  		for (const auto& it: i2p::i18n::languages)
 881  			s << "    <option value=\"" << it.first << "\"" << ((it.first.compare(currLang) == 0) ? " selected" : "") << ">" << it.second.LocaleName << "</option>\r\n";
 882  
 883  		s << "  </select>\r\n"
 884  		  << "  <button type=\"submit\">"
 885  		  << tr("Change")
 886  		  << "</button>\r\n"
 887  		  << "</form>\r\n<br>\r\n";
 888  
 889  	}
 890  
 891  	void ShowTransitTunnels (std::stringstream& s)
 892  	{
 893  		if (i2p::tunnel::tunnels.CountTransitTunnels())
 894  		{
 895  			s << "<b>" << tr("Transit Tunnels") << ":</b><br>\r\n";
 896  			s << "<table>\r\n";
 897  			s << "<thead><tr><th>&#8658;</th><th>ID</th><th>&#8658;</th><th>" << tr("Amount") << "</th><th>" << tr("Next") << "</th></tr></thead>\r\n";
 898  			s << "<tbody class=\"tableitem\">\r\n";
 899  			for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ())
 900  			{
 901  				s << "<tr class=\"tcell_center\">";
 902  				if (std::dynamic_pointer_cast<i2p::tunnel::TransitTunnelGateway>(it))
 903  					s << "<td> &nbsp;</td><td class=\"tcell_right\">" << std::setw(10) << it->GetTunnelID () << "</td><td>&#8658;</td>";
 904  				else if (std::dynamic_pointer_cast<i2p::tunnel::TransitTunnelEndpoint>(it))
 905  					s << "<td>&#8658;</td><td class=\"tcell_right\">" << std::setw(10) << it->GetTunnelID () << "</td><td> &nbsp;</td>";
 906  				else
 907  					s << "<td>&#8658;</td><td class=\"tcell_right\">" << std::setw(10) << it->GetTunnelID () << "</td><td>&#8658;</td>";
 908  				s << "<td class=\"tcell_right\">";
 909  				ShowTraffic(s, it->GetNumTransmittedBytes ());
 910  				s << "</td>";
 911  				s << "<td class=\"tcell_left\">&nbsp;" << it->GetNextPeerName () << "</td>";
 912  				s << "</tr>\r\n";
 913  			}
 914  			s << "</tbody>\r\n</table>\r\n<br>\r\n";
 915  		}
 916  		else
 917  		{
 918  			s << "<b>" << tr("Transit Tunnels") << ":</b> ";
 919  			/* Message on transit tunnels page */
 920  			s << tr("no transit tunnels currently built") << ".<br>\r\n";
 921  		}
 922  	}
 923  
 924  	template<typename Sessions>
 925  	static void ShowTransportSessions (std::stringstream& s, const Sessions& sessions, const std::string name)
 926  	{
 927  		auto comp = [](typename Sessions::mapped_type a, typename Sessions::mapped_type b)
 928  			{ return a->GetRemoteEndpoint() < b->GetRemoteEndpoint(); };
 929  		std::set<typename Sessions::mapped_type, decltype(comp)> sortedSessions(comp);
 930  		for (const auto& it : sessions)
 931  		{
 932  			auto ret = sortedSessions.insert(it.second);
 933  			if (!ret.second)
 934  				LogPrint(eLogError, "HTTPServer: Duplicate remote endpoint detected: ", (*ret.first)->GetRemoteEndpoint());
 935  		}
 936  		std::stringstream tmp_s, tmp_s6; uint16_t cnt = 0, cnt6 = 0;
 937  		for (const auto& it: sortedSessions)
 938  		{
 939  			auto endpoint = it->GetRemoteEndpoint ();
 940  			if (it && it->IsEstablished () && endpoint.address ().is_v4 ())
 941  			{
 942  				tmp_s << "<div class=\"listitem\">\r\n";
 943  				if (it->IsOutgoing ()) tmp_s << " &#8658; ";
 944  				else tmp_s << " &nbsp; ";
 945  				tmp_s << i2p::data::GetIdentHashAbbreviation (it->GetRemoteIdentity ()->GetIdentHash ()) << ": "
 946  					<< endpoint.address ().to_string () << ":" << endpoint.port ();
 947  				if (!it->IsOutgoing ()) tmp_s << " &#8658; ";
 948  				else tmp_s << " &nbsp; ";
 949  				tmp_s << " [" << it->GetNumSentBytes () << ":" << it->GetNumReceivedBytes () << "]";
 950  				if (it->GetRelayTag ())
 951  					tmp_s << " [itag:" << it->GetRelayTag () << "]";
 952  				if (it->GetSendQueueSize () > 0)
 953  					tmp_s << " [queue:" << it->GetSendQueueSize () << "]";
 954  				if (it->IsSlow ()) tmp_s << " [slow]";
 955  				tmp_s << "</div>\r\n" << std::endl;
 956  				cnt++;
 957  			}
 958  			if (it && it->IsEstablished () && endpoint.address ().is_v6 ())
 959  			{
 960  				tmp_s6 << "<div class=\"listitem\">\r\n";
 961  				if (it->IsOutgoing ()) tmp_s6 << " &#8658; ";
 962  				else tmp_s6 << " &nbsp; ";
 963  				tmp_s6 << i2p::data::GetIdentHashAbbreviation (it->GetRemoteIdentity ()->GetIdentHash ()) << ": "
 964  					<< "[" << endpoint.address ().to_string () << "]:" << endpoint.port ();
 965  				if (!it->IsOutgoing ()) tmp_s6 << " &#8658; ";
 966  				else tmp_s6 << " &nbsp; ";
 967  				tmp_s6 << " [" << it->GetNumSentBytes () << ":" << it->GetNumReceivedBytes () << "]";
 968  				if (it->GetRelayTag ())
 969  					tmp_s6 << " [itag:" << it->GetRelayTag () << "]";
 970  				if (it->GetSendQueueSize () > 0)
 971  					tmp_s6 << " [queue:" << it->GetSendQueueSize () << "]";
 972  				tmp_s6 << "</div>\r\n" << std::endl;
 973  				cnt6++;
 974  			}
 975  		}
 976  		if (!tmp_s.str ().empty ())
 977  		{
 978  			s << "<div class='slide'><label for='slide_" << boost::algorithm::to_lower_copy(name) << "'><b>" << name
 979  			  << "</b> ( " << cnt << " )</label>\r\n"
 980  			  << "<input type=\"checkbox\" id=\"slide_" << boost::algorithm::to_lower_copy(name) << "\" />\r\n"
 981  			  << "<div class=\"slidecontent list\">\r\n\r\n"
 982  			  << tmp_s.str ()
 983  			  << "<div class=\"listitem\">\r\n"
 984  			  << "&nbsp; </div>\r\n" << std::endl
 985  			  << "</div>\r\n</div>\r\n\r\n";
 986  		}
 987  		if (!tmp_s6.str ().empty ())
 988  		{
 989  			s << "<div class='slide'><label for='slide_" << boost::algorithm::to_lower_copy(name) << "v6'><b>" << name
 990  			  << "v6</b> ( " << cnt6 << " )</label>\r\n"
 991  			  << "<input type=\"checkbox\" id=\"slide_" << boost::algorithm::to_lower_copy(name) << "v6\" />\r\n"
 992  			  << "<div class=\"slidecontent list\">\r\n\r\n"
 993  			  << tmp_s6.str ()
 994  			  << "<div class=\"listitem\">\r\n"
 995  			  << "&nbsp; </div>\r\n" << std::endl
 996  			  << "</div>\r\n</div>\r\n\r\n";
 997  		}
 998  	}
 999  
1000  	void ShowTransports (std::stringstream& s)
1001  	{
1002  		s << "<b>" << tr("Transports") << ":</b><br>\r\n";
1003  		auto ntcp2Server = i2p::transport::transports.GetNTCP2Server ();
1004  		if (ntcp2Server)
1005  		{
1006  			auto sessions = ntcp2Server->GetNTCP2Sessions ();
1007  			if (!sessions.empty ())
1008  				ShowTransportSessions (s, sessions, "NTCP2");
1009  		}
1010  		auto ssu2Server = i2p::transport::transports.GetSSU2Server ();
1011  		if (ssu2Server)
1012  		{
1013  			auto sessions = ssu2Server->GetSSU2Sessions ();
1014  			if (!sessions.empty ())
1015  				ShowTransportSessions (s, sessions, "SSU2");
1016  		}
1017  	}
1018  
1019  	void ShowSAMSessions (std::stringstream& s)
1020  	{
1021  		std::string webroot; i2p::config::GetOption("http.webroot", webroot);
1022  		auto sam = i2p::client::context.GetSAMBridge ();
1023  		if (!sam)
1024  		{
1025  			ShowError(s, tr("SAM disabled"));
1026  			return;
1027  		}
1028  
1029  		if (sam->GetSessions ().size ())
1030  		{
1031  			s << "<b>" << tr("SAM sessions") << ":</b><br>\r\n<div class=\"list\">\r\n";
1032  			for (auto& it: sam->GetSessions ())
1033  			{
1034  				auto& name = it.second->GetLocalDestination ()->GetNickname ();
1035  				auto sam_id = i2p::data::ByteStreamToBase64 ((const uint8_t *)it.first.data (), it.first.length ()); // base64, becuase session name might be UTF-8
1036  				s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_SAM_SESSION << "&sam_id=" << sam_id << "\">";
1037  				s << name << " (" << it.first << ")</a></div>\r\n" << std::endl;
1038  			}
1039  			s << "</div>\r\n<br>\r\n";
1040  		}
1041  		else
1042  		{
1043  			s << "<b>" << tr("SAM sessions") << ":</b> ";
1044  			/* Message on SAM sessions page */
1045  			s << tr("no sessions currently running") << ".<br>\r\n";
1046  		}
1047  	}
1048  
1049  	void ShowSAMSession (std::stringstream& s, const std::string& id)
1050  	{
1051  		auto sam = i2p::client::context.GetSAMBridge ();
1052  		if (!sam)
1053  		{
1054  			ShowError(s, tr("SAM disabled"));
1055  			return;
1056  		}
1057  		if (id.empty ())
1058  		{
1059  			ShowError(s, tr("No sam_id"));
1060  			return;
1061  		}
1062  		std::vector<uint8_t> sam_id(id.length ()); // id is in base64
1063  		size_t l = i2p::data::Base64ToByteStream (id, sam_id.data (), sam_id.size ());
1064  		if (!l)
1065  		{
1066  			ShowError(s, tr("Invalid sam_id"));
1067  			return;
1068  		}
1069  		auto session = sam->FindSession ( { (const char *)sam_id.data (), l });
1070  		if (!session)
1071  		{
1072  			ShowError(s, tr("SAM session not found"));
1073  			return;
1074  		}
1075  
1076  		std::string webroot; i2p::config::GetOption("http.webroot", webroot);
1077  		s << "<b>" << tr("SAM Session") << ":</b><br>\r\n<div class=\"list\">\r\n";
1078  		auto& ident = session->GetLocalDestination ()->GetIdentHash();
1079  		s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
1080  		s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "</a></div>\r\n";
1081  		s << "</div>\r\n<br>\r\n";
1082  		s << "<b>" << tr("Streams") << ":</b><br>\r\n<div class=\"list\">\r\n";
1083  		for (const auto& it: sam->ListSockets({ (const char *)sam_id.data (), l }))
1084  		{
1085  			s << "<div class=\"listitem\">";
1086  			switch (it->GetSocketType ())
1087  			{
1088  				case i2p::client::SAMSocketType::eSAMSocketTypeSession  : s << "session";  break;
1089  				case i2p::client::SAMSocketType::eSAMSocketTypeStream   : s << "stream";   break;
1090  				case i2p::client::SAMSocketType::eSAMSocketTypeAcceptor : s << "acceptor"; break;
1091  				case i2p::client::SAMSocketType::eSAMSocketTypeForward  : s << "forward";  break;
1092  				default: s << "unknown"; break;
1093  			}
1094  			if (it->GetSocketType () != i2p::client::SAMSocketType::eSAMSocketTypeTerminated && it->GetSocket ().is_open ())
1095  				s << " [" << it->GetSocket ().remote_endpoint() << "]";
1096  			s << "</div>\r\n";
1097  		}
1098  		s << "</div>\r\n";
1099  	}
1100  
1101  	void ShowI2PTunnels (std::stringstream& s)
1102  	{
1103  		std::string webroot; i2p::config::GetOption("http.webroot", webroot);
1104  
1105  		auto& clientTunnels = i2p::client::context.GetClientTunnels ();
1106  		auto httpProxy = i2p::client::context.GetHttpProxy ();
1107  		auto socksProxy = i2p::client::context.GetSocksProxy ();
1108  		if (!clientTunnels.empty () || httpProxy || socksProxy)
1109  		{
1110  			s << "<b>" << tr("Client Tunnels") << ":</b><br>\r\n<div class=\"list\">\r\n";
1111  			if (!clientTunnels.empty ())
1112  			{
1113  				for (auto& it: clientTunnels)
1114  				{
1115  					auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
1116  					s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
1117  					s << it.second->GetName () << "</a> &#8656; ";
1118  					s << i2p::client::context.GetAddressBook ().ToAddress(ident);
1119  					s << "</div>\r\n"<< std::endl;
1120  				}
1121  			}
1122  			if (httpProxy)
1123  			{
1124  				auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash();
1125  				s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
1126  				s << "HTTP " << tr("Proxy") << "</a> &#8656; ";
1127  				s << i2p::client::context.GetAddressBook ().ToAddress(ident);
1128  				s << "</div>\r\n"<< std::endl;
1129  			}
1130  			if (socksProxy)
1131  			{
1132  				auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash();
1133  				s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
1134  				s << "SOCKS " << tr("Proxy") << "</a> &#8656; ";
1135  				s << i2p::client::context.GetAddressBook ().ToAddress(ident);
1136  				s << "</div>\r\n"<< std::endl;
1137  			}
1138  			s << "</div>\r\n<br>\r\n";
1139  		}
1140  
1141  		auto& serverTunnels = i2p::client::context.GetServerTunnels ();
1142  		if (!serverTunnels.empty ()) {
1143  			s << "<br>\r\n<b>" << tr("Server Tunnels") << ":</b><br>\r\n<div class=\"list\">\r\n";
1144  			for (auto& it: serverTunnels)
1145  			{
1146  				auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
1147  				s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
1148  				s << it.second->GetName () << "</a> &#8658; ";
1149  				s << i2p::client::context.GetAddressBook ().ToAddress(ident);
1150  				s << ":" << it.second->GetLocalPort ();
1151  				s << "</a></div>\r\n"<< std::endl;
1152  			}
1153  			s << "</div>\r\n<br>\r\n";
1154  		}
1155  
1156  		auto& clientForwards = i2p::client::context.GetClientForwards ();
1157  		if (!clientForwards.empty ())
1158  		{
1159  			s << "<br>\r\n<b>" << tr("Client Forwards") << ":</b><br>\r\n<div class=\"list\">\r\n";
1160  			for (auto& it: clientForwards)
1161  			{
1162  				auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
1163  				s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
1164  				s << it.second->GetName () << "</a> &#8656; ";
1165  				s << i2p::client::context.GetAddressBook ().ToAddress(ident);
1166  				s << "</div>\r\n"<< std::endl;
1167  			}
1168  			s << "</div>\r\n<br>\r\n";
1169  		}
1170  		auto& serverForwards = i2p::client::context.GetServerForwards ();
1171  		if (!serverForwards.empty ())
1172  		{
1173  			s << "<br>\r\n<b>" << tr("Server Forwards") << ":</b><br>\r\n<div class=\"list\">\r\n";
1174  			for (auto& it: serverForwards)
1175  			{
1176  				auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
1177  				s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
1178  				s << it.second->GetName () << "</a> &#8656; ";
1179  				s << i2p::client::context.GetAddressBook ().ToAddress(ident);
1180  				s << "</div>\r\n"<< std::endl;
1181  			}
1182  			s << "</div>\r\n<br>\r\n";
1183  		}
1184  	}
1185  
1186  	HTTPConnection::HTTPConnection (std::string hostname, std::shared_ptr<boost::asio::ip::tcp::socket> socket):
1187  		m_Socket (socket), m_BufferLen (0), expected_host(hostname)
1188  	{
1189  		/* cache options */
1190  		i2p::config::GetOption("http.auth", needAuth);
1191  		i2p::config::GetOption("http.user", user);
1192  		i2p::config::GetOption("http.pass", pass);
1193  	}
1194  
1195  	void HTTPConnection::Receive ()
1196  	{
1197  		m_Socket->async_read_some (boost::asio::buffer (m_Buffer, HTTP_CONNECTION_BUFFER_SIZE),
1198  			std::bind(&HTTPConnection::HandleReceive, shared_from_this (),
1199  				std::placeholders::_1, std::placeholders::_2));
1200  	}
1201  
1202  	void HTTPConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred)
1203  	{
1204  		if (ecode) {
1205  			if (ecode != boost::asio::error::operation_aborted)
1206  				Terminate (ecode);
1207  			return;
1208  		}
1209  		if (bytes_transferred <= HTTP_CONNECTION_BUFFER_SIZE) {
1210  			m_Buffer[bytes_transferred] = '\0';
1211  			m_BufferLen = bytes_transferred;
1212  		}
1213  		else {
1214  			m_Buffer[HTTP_CONNECTION_BUFFER_SIZE] = '\0';
1215  			m_BufferLen = HTTP_CONNECTION_BUFFER_SIZE;
1216  		}
1217  		RunRequest();
1218  		Receive ();
1219  	}
1220  
1221  	void HTTPConnection::RunRequest ()
1222  	{
1223  		HTTPReq request;
1224  		int ret = request.parse(m_Buffer);
1225  		if (ret < 0) {
1226  			m_Buffer[0] = '\0';
1227  			m_BufferLen = 0;
1228  			return; /* error */
1229  		}
1230  		if (ret == 0)
1231  			return; /* need more data */
1232  
1233  		HandleRequest (request);
1234  	}
1235  
1236  	void HTTPConnection::Terminate (const boost::system::error_code& ecode)
1237  	{
1238  		if (ecode == boost::asio::error::operation_aborted)
1239  			return;
1240  		boost::system::error_code ignored_ec;
1241  		m_Socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec);
1242  		m_Socket->close ();
1243  	}
1244  
1245  	bool HTTPConnection::CheckAuth (const HTTPReq & req)
1246  	{
1247  		/* method #1: http://user:pass@127.0.0.1:7070/ */
1248  		if (req.uri.find('@') != std::string::npos) {
1249  			URL url;
1250  			if (url.parse(req.uri) && url.user == user && url.pass == pass)
1251  				return true;
1252  		}
1253  		/* method #2: 'Authorization' header sent */
1254  		auto provided = req.GetHeader ("Authorization");
1255  		if (provided.length () > 0)
1256  		{
1257  			std::string expected = "Basic " + i2p::data::ToBase64Standard (user + ":" + pass);
1258  			if (expected == provided) return true;
1259  		}
1260  
1261  		LogPrint(eLogWarning, "HTTPServer: Auth failure from ", m_Socket->remote_endpoint().address ());
1262  		return false;
1263  	}
1264  
1265  	void HTTPConnection::HandleRequest (const HTTPReq & req)
1266  	{
1267  		std::stringstream s;
1268  		std::string content;
1269  		HTTPRes res;
1270  
1271  		LogPrint(eLogDebug, "HTTPServer: Request: ", req.uri);
1272  
1273  		if (needAuth && !CheckAuth(req)) {
1274  			res.code = 401;
1275  			res.add_header("WWW-Authenticate", "Basic realm=\"WebAdmin\"");
1276  			SendReply(res, content);
1277  			return;
1278  		}
1279  
1280  		bool strictheaders;
1281  		i2p::config::GetOption("http.strictheaders", strictheaders);
1282  		if (strictheaders)
1283  		{
1284  			std::string http_hostname;
1285  			i2p::config::GetOption("http.hostname", http_hostname);
1286  			std::string host = req.GetHeader("Host");
1287  			auto idx = host.find(':');
1288  			/* strip out port so it's just host */
1289  			if (idx != std::string::npos && idx > 0)
1290  			{
1291  				host = host.substr(0, idx);
1292  			}
1293  			if (!(host == expected_host || host == http_hostname))
1294  			{
1295  				/* deny request as it's from a non whitelisted hostname */
1296  				res.code = 403;
1297  				content = "host mismatch";
1298  				SendReply(res, content);
1299  				return;
1300  			}
1301  		}
1302  
1303  		// HTML head start
1304  		ShowPageHead (s);
1305  		if (req.uri.find("page=") != std::string::npos) {
1306  			HandlePage (req, res, s);
1307  		} else if (req.uri.find("cmd=") != std::string::npos) {
1308  			HandleCommand (req, res, s);
1309  		} else {
1310  			ShowStatus (s, true, i2p::http::OutputFormatEnum::forWebConsole);
1311  			res.add_header("Refresh", "10");
1312  		}
1313  		ShowPageTail (s);
1314  
1315  		res.code = 200;
1316  		content = s.str ();
1317  		SendReply (res, content);
1318  	}
1319  
1320  	std::map<uint32_t, uint32_t> HTTPConnection::m_Tokens;
1321  
1322  	uint32_t HTTPConnection::CreateToken ()
1323  	{
1324  		uint32_t token;
1325  		RAND_bytes ((uint8_t *)&token, 4);
1326  		token &= 0x7FFFFFFF; // clear first bit
1327  		auto ts = i2p::util::GetSecondsSinceEpoch ();
1328  		for (auto it = m_Tokens.begin (); it != m_Tokens.end (); )
1329  		{
1330  			if (ts > it->second + TOKEN_EXPIRATION_TIMEOUT)
1331  				it = m_Tokens.erase (it);
1332  			else
1333  				++it;
1334  		}
1335  		m_Tokens[token] = ts;
1336  		return token;
1337  	}
1338  
1339  	void HTTPConnection::HandlePage (const HTTPReq& req, HTTPRes& res, std::stringstream& s)
1340  	{
1341  		std::map<std::string, std::string> params;
1342  		std::string page("");
1343  		URL url;
1344  
1345  		url.parse(req.uri);
1346  		url.parse_query(params);
1347  		page = params["page"];
1348  
1349  		if (page == HTTP_PAGE_TRANSPORTS)
1350  			ShowTransports (s);
1351  		else if (page == HTTP_PAGE_TUNNELS)
1352  			ShowTunnels (s);
1353  		else if (page == HTTP_PAGE_COMMANDS)
1354  		{
1355  			uint32_t token = CreateToken ();
1356  			ShowCommands (s, token);
1357  		}
1358  		else if (page == HTTP_PAGE_TRANSIT_TUNNELS)
1359  			ShowTransitTunnels (s);
1360  		else if (page == HTTP_PAGE_LOCAL_DESTINATIONS)
1361  			ShowLocalDestinations (s);
1362  		else if (page == HTTP_PAGE_LOCAL_DESTINATION)
1363  		{
1364  			uint32_t token = CreateToken ();
1365  			ShowLocalDestination (s, params["b32"], token);
1366  		}
1367  		else if (page == HTTP_PAGE_I2CP_LOCAL_DESTINATION)
1368  			ShowI2CPLocalDestination (s, params["i2cp_id"]);
1369  		else if (page == HTTP_PAGE_SAM_SESSIONS)
1370  			ShowSAMSessions (s);
1371  		else if (page == HTTP_PAGE_SAM_SESSION)
1372  			ShowSAMSession (s, params["sam_id"]);
1373  		else if (page == HTTP_PAGE_I2P_TUNNELS)
1374  			ShowI2PTunnels (s);
1375  		else if (page == HTTP_PAGE_LEASESETS)
1376  			ShowLeasesSets(s);
1377  		else {
1378  			res.code = 400;
1379  			ShowError(s, std::string (tr("Unknown page")) + ": " + page); // TODO
1380  			return;
1381  		}
1382  	}
1383  
1384  	void HTTPConnection::HandleCommand (const HTTPReq& req, HTTPRes& res, std::stringstream& s)
1385  	{
1386  		std::map<std::string, std::string> params;
1387  		URL url;
1388  
1389  		url.parse(req.uri);
1390  		url.parse_query(params);
1391  
1392  		std::string webroot; i2p::config::GetOption("http.webroot", webroot);
1393  		std::string redirect = std::to_string(COMMAND_REDIRECT_TIMEOUT) + "; url=" + webroot + "?page=commands";
1394  		std::string token = params["token"];
1395  
1396  		if (token.empty () || m_Tokens.find (std::stoi (token)) == m_Tokens.end ())
1397  		{
1398  			ShowError(s, tr("Invalid token"));
1399  			return;
1400  		}
1401  
1402  		std::string cmd = params["cmd"];
1403  		if (cmd == HTTP_COMMAND_RUN_PEER_TEST)
1404  			i2p::transport::transports.PeerTest ();
1405  		else if (cmd == HTTP_COMMAND_RELOAD_TUNNELS_CONFIG)
1406  			i2p::client::context.ReloadConfig ();
1407  		else if (cmd == HTTP_COMMAND_ENABLE_TRANSIT)
1408  			i2p::context.SetAcceptsTunnels (true);
1409  		else if (cmd == HTTP_COMMAND_DISABLE_TRANSIT)
1410  			i2p::context.SetAcceptsTunnels (false);
1411  		else if (cmd == HTTP_COMMAND_SHUTDOWN_START)
1412  		{
1413  			i2p::context.SetAcceptsTunnels (false);
1414  #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY))
1415  			Daemon.gracefulShutdownInterval = 10*60;
1416  #elif defined(WIN32_APP)
1417  			i2p::win32::GracefulShutdown ();
1418  #endif
1419  		}
1420  		else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL)
1421  		{
1422  			i2p::context.SetAcceptsTunnels (true);
1423  #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY))
1424  			Daemon.gracefulShutdownInterval = 0;
1425  #elif defined(WIN32_APP)
1426  			i2p::win32::StopGracefulShutdown ();
1427  #endif
1428  		}
1429  		else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW)
1430  		{
1431  #ifndef WIN32_APP
1432  			Daemon.running = false;
1433  #else
1434  			i2p::win32::StopWin32App ();
1435  #endif
1436  		}
1437  		else if (cmd == HTTP_COMMAND_LOGLEVEL)
1438  		{
1439  			std::string level = params["level"];
1440  			SetLogLevel (level);
1441  		}
1442  		else if (cmd == HTTP_COMMAND_KILLSTREAM)
1443  		{
1444  			std::string b32 = params["b32"];
1445  			uint32_t streamID = std::stoul(params["streamID"], nullptr);
1446  
1447  			i2p::data::IdentHash ident;
1448  			ident.FromBase32 (b32);
1449  			auto dest = i2p::client::context.FindLocalDestination (ident);
1450  
1451  			if (streamID)
1452  			{
1453  				if (dest)
1454  				{
1455  					if (dest->DeleteStream (streamID))
1456  						s << "<b>" << tr("SUCCESS") << "</b>:&nbsp;" << tr("Stream closed") << "<br>\r\n<br>\r\n";
1457  					else
1458  						s << "<b>" << tr("ERROR") << "</b>:&nbsp;" << tr("Stream not found or already was closed") << "<br>\r\n<br>\r\n";
1459  				}
1460  				else
1461  					s << "<b>" << tr("ERROR") << "</b>:&nbsp;" << tr("Destination not found") << "<br>\r\n<br>\r\n";
1462  			}
1463  			else
1464  				s << "<b>" << tr("ERROR") << "</b>:&nbsp;" << tr("StreamID can't be null") << "<br>\r\n<br>\r\n";
1465  
1466  			s << "<a href=\"" << webroot << "?page=local_destination&b32=" << b32 << "\">" << tr("Return to destination page") << "</a><br>\r\n";
1467  			s << "<p>" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << "</b>";
1468  			redirect = std::to_string(COMMAND_REDIRECT_TIMEOUT) + "; url=" + webroot + "?page=local_destination&b32=" + b32;
1469  			res.add_header("Refresh", redirect.c_str());
1470  			return;
1471  		}
1472  		else if (cmd == HTTP_COMMAND_EXPIRELEASE)
1473  		{
1474  			std::string b32 = params["b32"];
1475  			std::string lease = params["lease"];
1476  
1477  			i2p::data::IdentHash ident, leaseident;
1478  			ident.FromBase32 (b32);
1479  			leaseident.FromBase32 (lease);
1480  			auto dest = i2p::client::context.FindLocalDestination (ident);
1481  
1482  			if (dest)
1483  			{
1484  				auto leaseset = dest->FindLeaseSet (leaseident);
1485  				if (leaseset)
1486  				{
1487  					leaseset->ExpireLease ();
1488  					s << "<b>" << tr("SUCCESS") << "</b>:&nbsp;" << tr("LeaseSet expiration time updated") << "<br>\r\n<br>\r\n";
1489  				}
1490  				else
1491  					s << "<b>" << tr("ERROR") << "</b>:&nbsp;" << tr("LeaseSet is not found or already expired") << "<br>\r\n<br>\r\n";
1492  			}
1493  			else
1494  				s << "<b>" << tr("ERROR") << "</b>:&nbsp;" << tr("Destination not found") << "<br>\r\n<br>\r\n";
1495  
1496  			s << "<a href=\"" << webroot << "?page=local_destination&b32=" << b32 << "\">" << tr("Return to destination page") << "</a><br>\r\n";
1497  			s << "<p>" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << "</b>";
1498  			redirect = std::to_string(COMMAND_REDIRECT_TIMEOUT) + "; url=" + webroot + "?page=local_destination&b32=" + b32;
1499  			res.add_header("Refresh", redirect.c_str());
1500  			return;
1501  		}
1502  		else if (cmd == HTTP_COMMAND_LIMITTRANSIT)
1503  		{
1504  			uint32_t limit = std::stoul(params["limit"], nullptr);
1505  			if (limit >= 2 && limit <= TRANSIT_TUNNELS_LIMIT)
1506  				i2p::tunnel::tunnels.SetMaxNumTransitTunnels (limit);
1507  			else {
1508  				s << "<b>" << tr("ERROR") << "</b>:&nbsp;";
1509  				s << tr("Transit tunnels count must be at least 2 and not exceed %d", TRANSIT_TUNNELS_LIMIT) << "\r\n<br><br>\r\n";
1510  				s << "<a href=\"" << webroot << "?page=commands\">" << tr("Back to commands list") << "</a>\r\n<br>\r\n";
1511  				s << "<p>" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << "</b>";
1512  				res.add_header("Refresh", redirect.c_str());
1513  				return;
1514  			}
1515  		}
1516  		else if (cmd == HTTP_COMMAND_GET_REG_STRING)
1517  		{
1518  			std::string b32 = params["b32"];
1519  			std::string name = i2p::http::UrlDecode(params["name"]);
1520  
1521  			i2p::data::IdentHash ident;
1522  			ident.FromBase32 (b32);
1523  			auto dest = i2p::client::context.FindLocalDestination (ident);
1524  
1525  			if (dest)
1526  			{
1527  				std::size_t pos;
1528  				pos = name.find (".i2p");
1529  				if (pos == (name.length () - 4))
1530  				{
1531  					pos = name.find (".b32.i2p");
1532  					if (pos == std::string::npos)
1533  					{
1534  						auto signatureLen = dest->GetIdentity ()->GetSignatureLen ();
1535  						uint8_t * signature = new uint8_t[signatureLen];
1536  						std::stringstream out;
1537  
1538  						out << name << "=" << dest->GetIdentity ()->ToBase64 ();
1539  						dest->Sign ((uint8_t *)out.str ().c_str (), out.str ().length (), signature);
1540  						auto sig = i2p::data::ByteStreamToBase64 (signature, signatureLen);
1541  						out << "#!sig=" << sig;
1542  						s << "<b>" << tr("SUCCESS") << "</b>:<br>\r\n<form action=\"http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/add\" method=\"post\" rel=\"noreferrer\" target=\"_blank\">\r\n"
1543  						     "<textarea readonly name=\"record\" cols=\"80\" rows=\"10\">" << out.str () << "</textarea>\r\n<br>\r\n<br>\r\n"
1544  						     "<b>" << tr("Register at reg.i2p") << ":</b>\r\n<br>\r\n"
1545  						     "<b>" << tr("Description") << ":</b>\r\n<input type=\"text\" maxlength=\"64\" name=\"desc\" placeholder=\"" << tr("A bit information about service on domain") << "\">\r\n"
1546  						     "<input type=\"submit\" value=\"" << tr("Submit") << "\">\r\n"
1547  						     "</form>\r\n<br>\r\n";
1548  						delete[] signature;
1549  					}
1550  					else
1551  						s << "<b>" << tr("ERROR") << "</b>:&nbsp;" << tr("Domain can't end with .b32.i2p") << "\r\n<br>\r\n<br>\r\n";
1552  				}
1553  				else
1554  					s << "<b>" << tr("ERROR") << "</b>:&nbsp;" << tr("Domain must end with .i2p") << "\r\n<br>\r\n<br>\r\n";
1555  			}
1556  			else
1557  				s << "<b>" << tr("ERROR") << "</b>:&nbsp;" << tr("Such destination is not found") << "\r\n<br>\r\n<br>\r\n";
1558  
1559  			s << "<a href=\"" << webroot << "?page=local_destination&b32=" << b32 << "\">" << tr("Return to destination page") << "</a>\r\n";
1560  			return;
1561  		}
1562  		else if (cmd == HTTP_COMMAND_SETLANGUAGE)
1563  		{
1564  			std::string lang = params["lang"];
1565  			std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage();
1566  
1567  			if (currLang.compare(lang) != 0)
1568  				i2p::i18n::SetLanguage(lang);
1569  		}
1570  		else if (cmd == HTTP_COMMAND_RELOAD_CSS)
1571  		{
1572  			std::string theme; i2p::config::GetOption("http.theme", theme);
1573  
1574  			if (theme != "light" && theme != "black" && theme !="white") LoadExtCSS(theme);
1575  			else LoadExtCSS();
1576  		}
1577  		else
1578  		{
1579  			res.code = 400;
1580  			ShowError(s, std::string (tr("Unknown command")) + ": " + cmd); // TODO
1581  			return;
1582  		}
1583  
1584  		s << "<b>" << tr("SUCCESS") << "</b>:&nbsp;" << tr("Command accepted") << "<br><br>\r\n";
1585  		s << "<a href=\"" << webroot << "?page=commands\">" << tr("Back to commands list") << "</a><br>\r\n";
1586  		s << "<p>" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << "</b>";
1587  		res.add_header("Refresh", redirect.c_str());
1588  	}
1589  
1590  	void HTTPConnection::SendReply (HTTPRes& reply, std::string& content)
1591  	{
1592  		reply.add_header("X-Frame-Options", "SAMEORIGIN");
1593  		reply.add_header("X-Content-Type-Options", "nosniff");
1594  		reply.add_header("X-XSS-Protection", "1; mode=block");
1595  		reply.add_header("Content-Type", "text/html");
1596  		reply.body = content;
1597  
1598  		m_SendBuffer = reply.to_string();
1599  		boost::asio::async_write (*m_Socket, boost::asio::buffer(m_SendBuffer), boost::asio::transfer_all (),
1600  			std::bind (&HTTPConnection::Terminate, shared_from_this (), std::placeholders::_1));
1601  	}
1602  
1603  	HTTPServer::HTTPServer (const std::string& address, int port):
1604  		m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service.get_executor ()),
1605  		m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::make_address(address), port)),
1606  		m_Hostname(address)
1607  	{
1608  	}
1609  
1610  	HTTPServer::~HTTPServer ()
1611  	{
1612  		Stop ();
1613  	}
1614  
1615  	void HTTPServer::Start ()
1616  	{
1617  		bool needAuth;    i2p::config::GetOption("http.auth", needAuth);
1618  		std::string user; i2p::config::GetOption("http.user", user);
1619  		std::string pass; i2p::config::GetOption("http.pass", pass);
1620  		/* generate pass if needed */
1621  		if (needAuth && pass == "") {
1622  			uint8_t random[16];
1623  			char alnum[] = "0123456789"
1624  				"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
1625  				"abcdefghijklmnopqrstuvwxyz";
1626  			pass.resize(sizeof(random));
1627  			RAND_bytes(random, sizeof(random));
1628  			for (size_t i = 0; i < sizeof(random); i++) {
1629  				pass[i] = alnum[random[i] % (sizeof(alnum) - 1)];
1630  			}
1631  			i2p::config::SetOption("http.pass", pass);
1632  			LogPrint(eLogInfo, "HTTPServer: Password set to ", pass);
1633  		}
1634  
1635  		m_IsRunning = true;
1636  		m_Thread.reset (new std::thread (std::bind (&HTTPServer::Run, this)));
1637  		m_Acceptor.listen ();
1638  		Accept ();
1639  
1640  		LoadExtCSS();
1641  	}
1642  
1643  	void HTTPServer::Stop ()
1644  	{
1645  		m_IsRunning = false;
1646  
1647  		boost::system::error_code ec;
1648  		m_Acceptor.cancel(ec);
1649  		if (ec)
1650  			LogPrint (eLogDebug, "HTTPServer: Error while cancelling operations on acceptor: ", ec.message ());
1651  		m_Acceptor.close();
1652  
1653  		m_Service.stop ();
1654  		if (m_Thread)
1655  		{
1656  			m_Thread->join ();
1657  			m_Thread = nullptr;
1658  		}
1659  	}
1660  
1661  	void HTTPServer::Run ()
1662  	{
1663  		i2p::util::SetThreadName("Webconsole");
1664  
1665  		while (m_IsRunning)
1666  		{
1667  			try
1668  			{
1669  				m_Service.run ();
1670  			}
1671  			catch (std::exception& ex)
1672  			{
1673  				LogPrint (eLogError, "HTTPServer: Runtime exception: ", ex.what ());
1674  			}
1675  		}
1676  	}
1677  
1678  	void HTTPServer::Accept ()
1679  	{
1680  		auto newSocket = std::make_shared<boost::asio::ip::tcp::socket> (m_Service);
1681  		m_Acceptor.async_accept (*newSocket, std::bind (&HTTPServer::HandleAccept, this,
1682  			std::placeholders::_1, newSocket));
1683  	}
1684  
1685  	void HTTPServer::HandleAccept(const boost::system::error_code& ecode,
1686  		std::shared_ptr<boost::asio::ip::tcp::socket> newSocket)
1687  	{
1688  		if (!ecode)
1689  			CreateConnection(newSocket);
1690  		else
1691  		{
1692  			if (newSocket) newSocket->close();
1693  			LogPrint(eLogError, "HTTP Server: Error handling accept: ", ecode.message());
1694  		}
1695  		if (m_IsRunning)
1696  			Accept ();
1697  	}
1698  
1699  	void HTTPServer::CreateConnection(std::shared_ptr<boost::asio::ip::tcp::socket> newSocket)
1700  	{
1701  		auto conn = std::make_shared<HTTPConnection> (m_Hostname, newSocket);
1702  		conn->Receive ();
1703  	}
1704  } // http
1705  } // i2p