i2pd/daemon/HTTPServer.cpp

2375 lines
139 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2013-2020, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#include <iomanip>
#include <sstream>
#include <thread>
#include <memory>
#include <boost/asio.hpp>
#include <boost/algorithm/string.hpp>
#include "Base.h"
#include "FS.h"
#include "Log.h"
#include "Config.h"
#include "Tunnel.h"
#include "Transports.h"
#include "NetDb.hpp"
#include "HTTP.h"
#include "LeaseSet.h"
#include "Destination.h"
#include "RouterContext.h"
#include "ClientContext.h"
#include "HTTPServer.h"
#include "Daemon.h"
#include "util.h"
#include "ECIESX25519AEADRatchetSession.h"
#include "I18N.h"
#ifdef WIN32_APP
#include "Win32App.h"
#endif
// For image and info
#include "version.h"
namespace i2p {
namespace http {
const std::string i2pdfavicon =
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' fill='%23405' rx='5'/%3E%3Ccircle cx='32' cy='32' r='4' fill='%23e580ff'/%3E%3Cg fill='%23d42aff'%3E%3Ccircle cx='20' cy='32' r='4'/%3E%3Ccircle cx='44' cy='32' r='4'/%3E%3Ccircle cx='32' cy='20' r='4'/%3E%3Ccircle cx='32' cy='44' r='4'/%3E%3C/g%3E%3Cg fill='%2380a'%3E%3Ccircle cx='20' cy='56' r='4'/%3E%3Ccircle cx='44' cy='8' r='4'/%3E%3Ccircle cx='44' cy='56' r='4'/%3E%3Ccircle cx='8' cy='44' r='4'/%3E%3Ccircle cx='56' cy='20' r='4'/%3E%3Ccircle cx='56' cy='44' r='4'/%3E%3Ccircle cx='8' cy='20' r='4'/%3E%3Ccircle cx='20' cy='8' r='4'/%3E%3C/g%3E%3Cg fill='%23aa00d4'%3E%3Ccircle cx='32' cy='56' r='4'/%3E%3Ccircle cx='44' cy='20' r='4'/%3E%3Ccircle cx='44' cy='44' r='4'/%3E%3Ccircle cx='8' cy='32' r='4'/%3E%3Ccircle cx='56' cy='32' r='4'/%3E%3Ccircle cx='32' cy='8' r='4'/%3E%3Ccircle cx='20' cy='44' r='4'/%3E%3Ccircle cx='20' cy='20' r='4'/%3E%3C/g%3E%3Cg fill='%23660080'%3E%3Ccircle cx='8' cy='56' r='4'/%3E%3Ccircle cx='56' cy='8' r='4'/%3E%3Ccircle cx='56' cy='56' r='4'/%3E%3Ccircle cx='8' cy='8' r='4'/%3E%3C/g%3E%3C/svg%3E";
// Bundled style
const std::string internalCSS =
"<style title=\"purple royale\">\r\n"
":root {"
"--bodyfont:Open Sans,Noto Sans,Ubuntu,Segoe UI,sans-serif;"
"--monospaced:Droid Sans Mono,Noto Mono,Lucida Console,DejaVu Sans Mono,monospace;"
"--logo:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 19'%3E%3Cg fill='purple'%3E%3Ccircle cx='2.7' cy='2.6' r='1.2'/%3E%3Ccircle cx='2.7' cy='6' r='1.2'/%3E%3Ccircle cx='2.7' cy='9.5' r='1.2'/%3E%3Ccircle cx='2.7' cy='13' r='1.2'/%3E%3Ccircle cx='2.7' cy='16.4' r='1.2'/%3E%3Ccircle cx='9.6' cy='2.6' r='1.2'/%3E%3Ccircle cx='9.6' cy='6' r='1.2'/%3E%3Ccircle cx='9.6' cy='9.5' r='1.2'/%3E%3Ccircle cx='9.6' cy='13' r='1.2'/%3E%3Ccircle cx='9.6' cy='16.4' r='1.2'/%3E%3Ccircle cx='13' cy='6' r='1.2'/%3E%3Ccircle cx='16.5' cy='6' r='1.2'/%3E%3Ccircle cx='16.5' cy='13' r='1.2'/%3E%3Ccircle cx='19.9' cy='6' r='1.2'/%3E%3Ccircle cx='19.9' cy='13' r='1.2'/%3E%3Ccircle cx='23.4' cy='13' r='1.2'/%3E%3Ccircle cx='26.8' cy='2.6' r='1.2'/%3E%3Ccircle cx='26.8' cy='6' r='1.2'/%3E%3Ccircle cx='26.8' cy='9.5' r='1.2'/%3E%3Ccircle cx='26.8' cy='13' r='1.2'/%3E%3Ccircle cx='26.8' cy='16.4' r='1.2'/%3E%3Ccircle cx='33.7' cy='6' r='1.2'/%3E%3Ccircle cx='33.7' cy='13' r='1.2'/%3E%3Ccircle cx='33.7' cy='16.4' r='1.2'/%3E%3Ccircle cx='37.2' cy='6' r='1.2'/%3E%3Ccircle cx='37.2' cy='13' r='1.2'/%3E%3Ccircle cx='37.2' cy='16.4' r='1.2'/%3E%3Ccircle cx='40.6' cy='13' r='1.2'/%3E%3Ccircle cx='40.6' cy='16.4' r='1.2'/%3E%3Ccircle cx='44.1' cy='2.6' r='1.2'/%3E%3Ccircle cx='44.1' cy='6' r='1.2'/%3E%3Ccircle cx='44.1' cy='9.5' r='1.2'/%3E%3Ccircle cx='44.1' cy='13' r='1.2'/%3E%3Ccircle cx='44.1' cy='16.4' r='1.2'/%3E%3Ccircle cx='47.5' cy='2.6' r='1.2'/%3E%3Ccircle cx='47.5' cy='6' r='1.2'/%3E%3Ccircle cx='51' cy='2.6' r='1.2'/%3E%3Ccircle cx='51' cy='6' r='1.2'/%3E%3Ccircle cx='51' cy='13' r='1.2'/%3E%3Ccircle cx='54.4' cy='2.6' r='1.2'/%3E%3Ccircle cx='54.4' cy='6' r='1.2'/%3E%3Ccircle cx='54.4' cy='13' r='1.2'/%3E%3Ccircle cx='61.3' cy='2.6' r='1.2'/%3E%3Ccircle cx='61.3' cy='6' r='1.2'/%3E%3Ccircle cx='61.3' cy='9.5' r='1.2'/%3E%3Ccircle cx='61.3' cy='13' r='1.2'/%3E%3Ccircle cx='61.3' cy='16.4' r='1.2'/%3E%3C/g%3E%3Cg fill='%23f0f'%3E%3Ccircle cx='6.1' cy='2.6' r='1.2'/%3E%3Ccircle cx='6.1' cy='6' r='1.2'/%3E%3Ccircle cx='6.1' cy='9.5' r='1.2'/%3E%3Ccircle cx='6.1' cy='13' r='1.2'/%3E%3Ccircle cx='6.1' cy='16.4' r='1.2'/%3E%3Ccircle cx='13' cy='2.6' r='1.2'/%3E%3Ccircle cx='13' cy='9.5' r='1.2'/%3E%3Ccircle cx='13' cy='13' r='1.2'/%3E%3Ccircle cx='13' cy='16.4' r='1.2'/%3E%3Ccircle cx='16.5' cy='2.6' r='1.2'/%3E%3Ccircle cx='16.5' cy='9.5' r='1.2'/%3E%3Ccircle cx='16.5' cy='16.4' r='1.2'/%3E%3Ccircle cx='19.9' cy='2.6' r='1.2'/%3E%3Ccircle cx='19.9' cy='9.5' r='1.2'/%3E%3Ccircle cx='19.9' cy='16.4' r='1.2'/%3E%3Ccircle cx='23.4' cy='2.6' r='1.2'/%3E%3Ccircle cx='23.4' cy='6' r='1.2'/%3E%3Ccircle cx='23.4' cy='9.5' r='1.2'/%3E%3Ccircle cx='23.4' cy='16.4' r='1.2'/%3E%3Ccircle cx='30.3' cy='2.6' r='1.2'/%3E%3Ccircle cx='30.3' cy='6' r='1.2'/%3E%3Ccircle cx='30.3' cy='9.5' r='1.2'/%3E%3Ccircle cx='30.3' cy='13' r='1.2'/%3E%3Ccircle cx='30.3' cy='16.4' r='1.2'/%3E%3Ccircle cx='33.7' cy='2.6' r='1.2'/%3E%3Ccircle cx='33.7' cy='9.5' r='1.2'/%3E%3Ccircle cx='37.2' cy='2.6' r='1.2'/%3E%3Ccircle cx='37.2' cy='9.5' r='1.2'/%3E%3Ccircle cx='40.6' cy='2.6' r='1.2'/%3E%3Ccircle cx='40.6' cy='6' r='1.2'/%3E%3Ccircle cx='40.6' cy='9.5' r='1.2'/%3E%3Ccircle cx='47.5' cy='9.5' r='1.2'/%3E%3Ccircle cx='47.5' cy='13' r='1.2'/%3E%3Ccircle cx='47.5' cy='16.4' r='1.2'/%3E%3Ccircle cx='51' cy='9.5' r='1.2'/%3E%3Ccircle cx='51' cy='16.4' r='1.2'/%3E%3Ccircle cx='54.4' cy='9.5' r='1.2'/%3E%3Ccircle cx='54.4' cy='16.4' r='1.2'/%3E%3Ccircle cx='57.9' cy='2.6' r='1.2'/%3E%3Ccircle cx='57.9' cy='6' r='1.2'/%3E%3Ccircle cx='57.9' cy='9.5' r='1.2'/%3E%3Ccircle cx='57.9' cy='13' r='1.2'/%3E%3Ccircle cx='57.9' cy='16.4' r='1.2'/%3E%3C/g%3E%3C/svg%3E\");"
"--dropdown:url(\"data:image/svg+xml,%3Csvg viewBox='0 0 64 64' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m5.29 17.93 26.71 28.14 26.71-28.14' fill='none' stroke='%23ae6ba8' stroke-linecap='round' stroke-linejoin='round' stroke-width='10'/%3E%3C/svg%3E\");"
"--dropdown_hover:url(\"data:image/svg+xml,%3Csvg viewBox='0 0 64 64' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m5.29 17.93 26.71 28.14 26.71-28.14' fill='none' stroke='%23fafafa' stroke-linecap='round' stroke-linejoin='round' stroke-width='10'/%3E%3C/svg%3E\");"
"--yes:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cpath fill='%2371c837' d='M55.9 8.6a4.3 4.3 0 00-3 1.3l-31 30.8L11.3 30a4.4 4.4 0 00-6 0l-4 4.2a4.4 4.4 0 000 6L19 57.7a4.4 4.4 0 006 0l37.8-37.9a4.4 4.4 0 000-6l-4-4a4.3 4.3 0 00-3-1.3z'/%3E%3C/svg%3E\");"
"--yes_btn:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cpath fill='%23ae6ba8' d='M55.9 8.6a4.3 4.3 0 00-3 1.3l-31 30.8L11.3 30a4.4 4.4 0 00-6 0l-4 4.2a4.4 4.4 0 000 6L19 57.7a4.4 4.4 0 006 0l37.8-37.9a4.4 4.4 0 000-6l-4-4a4.3 4.3 0 00-3-1.3z'/%3E%3C/svg%3E\");"
"--no:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cpath fill='red' d='M9.7 0c-1 0-2.1.4-3 1.2L1.3 7a4.2 4.2 0 000 5.8L20.6 32 1.3 51.3a4.2 4.2 0 000 5.9l5.6 5.6a4.2 4.2 0 005.9 0L32 43.5l19.2 19.3a4.2 4.2 0 005.9 0l5.6-5.6a4.2 4.2 0 000-5.9L43.5 32l19.2-19.3a4.1 4.1 0 000-5.9l-5.6-5.6a4.2 4.2 0 00-5.8 0L32 20.5 12.6 1.2A4.2 4.2 0 009.7 0z'/%3E%3C/svg%3E\");"
"--info:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cpath fill='%23fcf' stroke='%23313' d='M31.4 3a28.8 28.8 0 00-1.6.1 28.8 28.8 0 00-26.6 29 28.8 28.8 0 1057.6 0A28.8 28.8 0 0031.4 3zm.6 9.3a4.5 4.5 0 014.5 4.5 4.5 4.5 0 01-4.5 4.4 4.5 4.5 0 01-4.5-4.4 4.5 4.5 0 014.5-4.5zm-4.5 13.1h9v26.3h-9V25.4z'/%3E%3C/svg%3E\");"
"--eye:url(\"data:image/svg+xml,%3Csvg viewBox='0 0 64 64' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m63.95 33.1a2.03 2.03 0 0 0 0-1.97c-6.13-11.3-18.1-18.95-31.85-18.95s-25.7 7.66-31.85 18.94a2.03 2.03 0 0 0 0 1.97c6.13 11.3 18.1 18.95 31.85 18.95s25.7-7.67 31.85-18.95z' fill='%23894c84'/%3E%3Cpath d='m32.1 47.4c-8.45 0-15.3-6.85-15.3-15.3s6.85-15.3 15.3-15.3 15.3 6.85 15.3 15.3-6.85 15.3-15.3 15.3z' fill='%23313'/%3E%3Cpath d='m32.1 24.3a7.72 7.72 0 0 0 -1.87.22 4.05 4.05 0 0 1 .99 2.65c0 2.24-1.8 4.04-4.04 4.04-1 0-1.93-.37-2.65-1a7.66 7.66 0 0 0 -.22 1.87 7.79 7.79 0 0 0 7.79 7.79c4.3 0 7.8-3.5 7.8-7.8s-3.5-7.8-7.8-7.8z' fill='%23894c84'/%3E%3C/svg%3E\");"
"--eye_hover:url(\"data:image/svg+xml,%3Csvg viewBox='0 0 64 64' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m63.95 33.1a2.03 2.03 0 0 0 0-1.97c-6.13-11.3-18.1-18.95-31.85-18.95s-25.7 7.66-31.85 18.94a2.03 2.03 0 0 0 0 1.97c6.13 11.3 18.1 18.95 31.85 18.95s25.7-7.67 31.85-18.95z' fill='%23dbd'/%3E%3Cpath d='m32.1 47.4c-8.45 0-15.3-6.85-15.3-15.3s6.85-15.3 15.3-15.3 15.3 6.85 15.3 15.3-6.85 15.3-15.3 15.3z' fill='%23313'/%3E%3Cpath d='m32.1 24.3a7.72 7.72 0 0 0 -1.87.22 4.05 4.05 0 0 1 .99 2.65c0 2.24-1.8 4.04-4.04 4.04-1 0-1.93-.37-2.65-1a7.66 7.66 0 0 0 -.22 1.87 7.79 7.79 0 0 0 7.79 7.79c4.3 0 7.8-3.5 7.8-7.8s-3.5-7.8-7.8-7.8z' fill='%23dbd'/%3E%3C/svg%3E\");"
"--arrow_left:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23dbd' viewBox='0 0 64 64'%3E%3Cpath d='M4.5 32l30-30v20.2h25v19.6h-25V62z'/%3E%3C/svg%3E\");"
"--arrow_right:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23dbd' viewBox='0 0 64 64'%3E%3Cpath d='M59.5 32l-30-30v20.2h-25v19.6h25V62z'/%3E%3C/svg%3E\");"
"--arrow_up:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23dbd' viewBox='0 0 64 64'%3E%3Cpath d='M32 4.5l-30 30h20.2v25h19.6v-25H62z'/%3E%3C/svg%3E\");"
"--arrow_up_transit:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%237f7' viewBox='0 0 64 64'%3E%3Cpath d='M32 4.5l-30 30h20.2v25h19.6v-25H62z'/%3E%3C/svg%3E\");"
"--arrow_down:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23dbd' viewBox='0 0 64 64'%3E%3Cpath d='M32 59.5l-30-30h20.2v-25h19.6v25H62z'/%3E%3C/svg%3E\");"
"--arrow_double:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23dbd' viewBox='0 0 64 64'%3E%3Cpath d='M2.4 32l20.9-20.9v14h6.3v13.7h-6.3v14zM61.6 32L40.7 11.1v14h-6.3v13.7h6.3v14z'/%3E%3C/svg%3E\");"
"--error:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cg stroke-linejoin='round'%3E%3Cpath fill='none' stroke='%23300' stroke-width='10' d='M58 54.6H6l26-45z'/%3E%3Cpath fill='%23fff' stroke='%23b00' stroke-width='3' d='M58 54.6H6l26-45z'/%3E%3C/g%3E%3Cpath d='M29.5 24.5h5v14.7h-5zm0 18.6h5v5.6h-5z'/%3E%3C/svg%3E\");"
"--ibgw:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23dbd' viewBox='0 0 64 64'%3E%3Cpath d='M32 11.32a20.7 20.7 0 00-20.26 16.51.9.9 0 00-.18-.02H1.98a.94.94 0 00-.7.32c-.18.2-.29.48-.28.76v6.22c0 .29.1.56.28.76a.94.94 0 00.7.32h9.59a.9.9 0 00.18-.02 20.68 20.68 0 0040.51 0 .9.9 0 00.18.02h9.58a.94.94 0 00.7-.32c.18-.2.29-.48.28-.76v-6.22c0-.59-.44-1.07-.98-1.07h-9.59a.9.9 0 00-.18.02A20.7 20.7 0 0032 11.32zm0 3.43a17.26 17.26 0 010 34.5 17.26 17.26 0 010-34.5zm-2.14 4.83v10.43h-4.14c-1.04 0-1.46.8-.94 1.78l6.28 11.88c.33.62.78.85 1.2.69.25-.09.49-.32.69-.69l6.28-11.88c.52-.98.1-1.78-.94-1.78h-4.14V19.58z'/%3E%3C/svg%3E\");"
"--obep:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23dbd' viewBox='0 0 64 64'%3E%3Cpath d='M32 52.68a20.7 20.7 0 01-20.26-16.51.98 1.07 0 01-.18.02H1.98A.98 1.07 0 011 35.11v-6.22a.98 1.07 0 01.98-1.08h9.59a.98 1.07 0 01.18.02 20.68 20.68 0 0140.51 0 .98 1.07 0 01.18-.02h9.58a.98 1.07 0 01.98 1.08v6.22a.98 1.07 0 01-.98 1.07h-9.59a.98 1.07 0 01-.18-.02A20.7 20.7 0 0132 52.68zm0-3.43a17.26 17.26 0 000-34.5 17.26 17.26 0 000 34.5zm-2.14-4.83V33.99h-4.14c-1.04 0-1.46-.8-.94-1.78l6.28-11.88c.33-.62.78-.85 1.2-.69.25.09.49.32.69.69l6.28 11.88c.52.98.1 1.78-.94 1.78h-4.14v10.43z'/%3E%3C/svg%3E\");"
"--ptcp:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cpath fill='%23dbd' d='M32 11.32a20.7 20.7 0 00-20.26 16.51.98-1.07 0 00-.17-.02H1.98A.98-1.07 0 001 28.88v6.22a.98-1.07 0 00.98 1.07h9.59a.98-1.07 0 00.18-.02 20.68 20.68 0 0040.51.02.98-1.07 0 00.18.02h9.58a.98-1.07 0 00.98-1.08v-6.22a.98-1.07 0 00-.98-1.08h-9.59a.98-1.07 0 00-.18.02A20.7 20.7 0 0032 11.32zm0 3.43a17.26 17.26 0 010 34.5 17.26 17.26 0 010-34.5z'/%3E%3C/svg%3E\");"
"--success:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Ccircle cx='32' cy='32' r='32' fill='%2371c837'/%3E%3Ccircle cx='32' cy='32' r='27.1' fill='%23fff'/%3E%3Ccircle cx='32' cy='32' r='22.2' fill='%2371c837'/%3E%3Cpath fill='%23fff' d='M44 19.4a2.2 2.2 0 00-1.5.6L27 35.5 21.6 30c-.8-.8-2.3-.8-3 0l-2.1 2.1c-.8.8-.7 2.2 0 3l9 8.9c.8.8 2.2.8 3 0l19-19c.8-.8.8-2.3 0-3l-2-2a2.2 2.2 0 00-1.5-.7z'/%3E%3C/svg%3E\");"
"--planet:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Ccircle cx='32' cy='32' r='32' fill='%231ea6c6'/%3E%3Cpath fill='%23f7cf52' d='M59.5 15.6c-5-3.6-6.9-1.8-7.6-.3a2 2 0 01-1.8 1.1h-.2a2 2 0 01-1.9-2c0-4.2 2.7-8.4 2.7-8.4a32.1 32.1 0 018.8 9.6zM64 32a32 32 0 01-1.9 10.8c-1-1.7-1.4-3.8-1.5-5.6-.1-2-2-3.5-4-3.2a5 5 0 01-5.7-4.3 19.4 19.4 0 01-.2-3.4s.4-4.9 2.8-7.2a4 4 0 011.2-1.1 2.5 2.5 0 011.8-.5c2.3.3 4 0 4 0A31.9 31.9 0 0164 32zM37.2 5.3l-3.9 5c-.5-3.8-5-7.1-3.9-7 6 .5 7.8 2 7.8 2zm-8 5.3a3.2 3.2 0 01-1.3 4.8 26.1 26.1 0 00-8.5 5.6 3 3 0 01-3 1c-1.5-.3-3.4-.3-4.5 1.6-2 3.6 5.8 7.6 5.4 12a3.6 3.6 0 00-2.1-2.4c-2.8-1.2-5.2-3-6.8-5.9C5 21.1 7.4 13.7 9.8 9a32 32 0 0110.3-6.6s5.3 3.3 9 8.3zm7.5 31c3 1.7 3.7 5.8 1.4 8.5l-4 4.7-5 5.3-3.3 3.3c-3-4.3-2-12.5-2-12.5l-3-2.2a7.6 7.6 0 01-3.3-7 17 17 0 00-.2-6c2.6-.8 5.3-1.2 7.2-1.4a5 5 0 014 1.3c2 2 5.4 4.2 8.2 6z'/%3E%3C/svg%3E\");"
"--tunnel:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 64 64'%3E%3Cdefs%3E%3Cpath id='A' fill-opacity='.8' d='M0 0h32v32H0z'/%3E%3C/defs%3E%3ClinearGradient id='B' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23ff0'/%3E%3Cstop offset='.6' stop-color='%23f7cc22'/%3E%3Cstop offset='1' stop-color='%23d4aa00'/%3E%3C/linearGradient%3E%3CradialGradient id='C' cx='282.7' cy='938.5' r='184.6' gradientTransform='matrix(-.19558 -.1369 -.05868 .07823 153.4 13)' xlink:href='%23B'/%3E%3Cfilter id='D' width='1' height='1' x='0' y='0'%3E%3CfeColorMatrix in='SourceGraphic' values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0'/%3E%3C/filter%3E%3CradialGradient id='E' cx='413' cy='807.7' r='151.1' gradientTransform='matrix(-.17603 0 0 .19558 97.6 -124.9)' xlink:href='%23B'/%3E%3CradialGradient id='F' cx='306.1' cy='1055.1' r='184.6' gradientTransform='matrix(-.21514 0 0 .07823 107.6 -30.2)' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23a80'/%3E%3Cstop offset='.8' stop-color='%23a28100'/%3E%3Cstop offset='1' stop-color='%23540'/%3E%3C/radialGradient%3E%3Cmask id='G'%3E%3Cg filter='url(%23D)'%3E%3Cuse fill-opacity='.5' xlink:href='%23A'/%3E%3C/g%3E%3C/mask%3E%3CradialGradient id='H' cx='478.2' cy='713.7' r='76.4' gradientTransform='matrix(-.12 .21 .13 .07 -27.3 -142.6)' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23fff'/%3E%3Cstop offset='1' stop-color='%23fff' stop-opacity='0'/%3E%3C/radialGradient%3E%3CclipPath id='I'%3E%3Cuse xlink:href='%23A'/%3E%3C/clipPath%3E%3Cpath d='M50.3 24.7c1 3.3 1.5 6 2 8.6 4 3.8 10.2 10 10.5 12.4.2 2.4-9.7 11-14.1 12-3 1-10.8.4-19.8-2.5-8.2-2.5-16-6-23.2-10.7-2.8-2-4.5-4.4-4.5-7 0-2.4 1.2-4.6 3.1-6L4.1 29A22.1 22.1 0 0125.8 6.1c14.2.5 21.6 9.6 24.5 18.6z'/%3E%3Cg transform='translate(.2 .1)'%3E%3Cpath fill='url(%23C)' d='M49 25.1c1 3.2 1.4 5.9 1.8 8.2l2.1 1.8c2 1.6 8.4 8 8.4 10.2 0 2.7-5.8 6.4-8.2 8.4-3.3 2.5-4.3 3-11.5 3A91 91 0 016.9 43.4c-2.6-2-4-3.9-4-6.4 0-2 1-4 3-5.5L5.5 29a21 21 0 0120-21.7A24.8 24.8 0 0149 25z'/%3E%3Cpath fill='url(%23E)' d='M50.8 33.3C50.7 45 35.4 47.8 28.4 48 18.2 48 5.7 40.8 5.7 31.6V29c9.2-34.5 39.1-17.5 45.1 4.3z'/%3E%3Cpath fill='url(%23F)' d='M3 36.5c.2 2.1 2.4 4.4 4.8 6.2a101 101 0 0033.7 13c4.2.3 8-.6 11.3-3.1 3.4-2.7 8-5 8.6-7.5.4 2.8-5.7 6.6-8.1 8.6a15 15 0 01-11.8 3A89 89 0 016.9 43.5c-2.7-2-4.1-4.3-4.1-6.4v-.6z'/%3E%3C/g%3E%3Cpath d='M46.9 29.7v1.4C45.7 19.6 31.3 4.8 18.3 8.6c2-.6 4.1-1 6.4-1C36.3 7.8 47 19.4 47 29.7z'/%3E%3Cpath d='M20.3 8.2c8.6 2 16 11.3 17.6 23.8.4 3.4.4 6.7 0 9.8 0-2.5 0-5-.4-7.8-2-13.7-10.3-24-19.5-24.9zm0 0'/%3E%3Cg clip-path='url(%23I)' mask='url(%23G)' transform='matrix(1.95584 0 0 1.95584 .6 .8)'%3E%3Cpath fill='url(%23H)' d='M11 5.6c2.1 1.8 3.8 4.3 5 7.3a28.5 28.5 0 00-12.3 1c-.2-4.7 3-8.2 7.4-8.3zm0 0'/%3E%3C/g%3E%3C/svg%3E\");"
"--established:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cpath fill='%233b3' d='M17.4 2.5l3.4 7c.3.3.7.7 1.3.8l7.5 1c1.3.2 1.9 2 1 2.8L25 19.4c-.4.4-.5 1-.4 1.5l1.2 7.5a1.6 1.6 0 01-2.3 1.7l-6.7-3.5c-.5-.3-1.1-.3-1.5 0L8.5 30c-1.3.7-2.6-.3-2.4-1.7L7.4 21c0-.6 0-1.1-.5-1.5l-5.4-5.3a1.6 1.6 0 01.9-2.8l7.5-1c.5 0 1-.4 1.3-.9l3.4-6.9c.5-1.1 2.2-1.1 2.8 0z'/%3E%3C/svg%3E\");"
"--building:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cpath fill='%23dd0' d='M17.4 2.5l3.4 7c.3.3.7.7 1.3.8l7.5 1c1.3.2 1.9 2 1 2.8L25 19.4c-.4.4-.5 1-.4 1.5l1.2 7.5a1.6 1.6 0 01-2.3 1.7l-6.7-3.5c-.5-.3-1.1-.3-1.5 0L8.5 30c-1.3.7-2.6-.3-2.4-1.7L7.4 21c0-.6 0-1.1-.5-1.5l-5.4-5.3a1.6 1.6 0 01.9-2.8l7.5-1c.5 0 1-.4 1.3-.9l3.4-6.9c.5-1.1 2.2-1.1 2.8 0z'/%3E%3C/svg%3E\");"
"--failed:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cpath fill='%23f00' d='M17.4 2.5l3.4 7c.3.3.7.7 1.3.8l7.5 1c1.3.2 1.9 2 1 2.8L25 19.4c-.4.4-.5 1-.4 1.5l1.2 7.5a1.6 1.6 0 01-2.3 1.7l-6.7-3.5c-.5-.3-1.1-.3-1.5 0L8.5 30c-1.3.7-2.6-.3-2.4-1.7L7.4 21c0-.6 0-1.1-.5-1.5l-5.4-5.3a1.6 1.6 0 01.9-2.8l7.5-1c.5 0 1-.4 1.3-.9l3.4-6.9c.5-1.1 2.2-1.1 2.8 0z'/%3E%3C/svg%3E\");"
"--expiring:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cpath fill='%23999999dd' d='M17.4 2.5l3.4 7c.3.3.7.7 1.3.8l7.5 1c1.3.2 1.9 2 1 2.8L25 19.4c-.4.4-.5 1-.4 1.5l1.2 7.5a1.6 1.6 0 01-2.3 1.7l-6.7-3.5c-.5-.3-1.1-.3-1.5 0L8.5 30c-1.3.7-2.6-.3-2.4-1.7L7.4 21c0-.6 0-1.1-.5-1.5l-5.4-5.3a1.6 1.6 0 01.9-2.8l7.5-1c.5 0 1-.4 1.3-.9l3.4-6.9c.5-1.1 2.2-1.1 2.8 0z'/%3E%3C/svg%3E\");"
"--exploratory:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3CradialGradient id='a' cx='-23' cy='27.6' r='15.6' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%2364b5f6'/%3E%3Cstop offset='1' stop-color='%237bc9ff'/%3E%3C/radialGradient%3E%3Cg transform='matrix(.62496 0 0 .62496 1 1)'%3E%3Cg fill='%23616161' transform='matrix(-1.1993 0 0 1.1993 52.8 -4.8)'%3E%3Cpath d='M29.2 32l2.8-2.8 12 12-2.8 2.8z'/%3E%3Ccircle cx='20' cy='20' r='16'/%3E%3C/g%3E%3Cpath fill='%2337474f' d='M9.7 41.6l-3.3-3.3L0 44.7 3.3 48z'/%3E%3Ccircle cx='-28.8' cy='19.2' r='15.6' fill='url(%23a)' transform='scale(-1 1)'/%3E%3Cpath fill='%23bbdefb' fill-opacity='.9' d='M20.5 9.9a10.8 10.8 0 0116.6 0c.4.4.3 1.3-.2 1.6-.4.5-1.3.4-1.6 0a8.5 8.5 0 00-13 0c-.2.2-.6.4-1 .4l-.7-.2c-.5-.5-.5-1.4-.1-1.8z'/%3E%3C/g%3E%3C/svg%3E\");"
"--local:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3ClinearGradient id='a' x1='7.8' x2='23.1' y1='10.4' y2='33.3' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23ffd5f6'/%3E%3Cstop offset='1' stop-color='%23fae'/%3E%3C/linearGradient%3E%3Cpath fill='%239d93ac' d='M35.2 44.9l1 7.6h-8.4l1-7.6z'/%3E%3Cpath fill='%23beb7c8' d='M60 46c0 1.3-1.1 2.4-2.4 2.4H6.4A2.4 2.4 0 014 46V10c0-1.3 1.1-2.4 2.4-2.4h51.2c1.3 0 2.4 1 2.4 2.4zm-37 6.2h18a2 2 0 012 2v.2a2 2 0 01-2 2H23a2 2 0 01-2-2v-.2a2 2 0 012-2z'/%3E%3Ccircle cx='32' cy='44.8' r='1.3' fill='%23dedbe3'/%3E%3Cpath fill='%23de87cd' d='M8.1 12v29.3h48.1V12z'/%3E%3Cpath fill='url(%23a)' d='M7.5 12.5v29l49-29z' transform='matrix(.98 0 0 1.01 .7 -.6)'/%3E%3Cpath fill='none' stroke='%23442178' stroke-width='2' d='M8 12v29.3h48V12z'/%3E%3C/svg%3E\");"
"--time:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cg transform='translate(1 1)'%3E%3Ccircle cx='31.1' cy='31.1' r='28.1' stroke='%23dbd' stroke-width='6'/%3E%3Cpath fill='none' stroke='%23dbd' stroke-linecap='round' stroke-linejoin='round' stroke-width='5' d='M30.7 13.2v18.5h16.5'/%3E%3Ccircle cx='31.1' cy='31.1' r='4.5' fill='%23dbd'/%3E%3C/g%3E%3C/svg%3E\");"
"--tag:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cpath fill='%23dbd' d='M63 37.9v20.3c-.2 2.3-2.6 4.5-4.9 4.7l-20.3.1a4.3 4.3 0 01-2.9-1.4L2.3 29.2a4.3 4.3 0 010-6.1L23.1 2.3a4.3 4.3 0 016.1 0l32.5 32.5c.9.8 1.3 1.9 1.3 3.2zm-9.3 5.5a7.3 7.3 0 10-10.3 10.2 7.3 7.3 0 0010.3 0 7.1 7.1 0 000-10.2z'/%3E%3C/svg%3E\");"
"--shutdown:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cpath fill='%23717' d='M16 1a3 3 0 00-3 3v9.3a3 3 0 002.9 2.9 3 3 0 003-2.8V4a3 3 0 00-3-3zm7.2 3.2a3 3 0 00-2.8 3 3 3 0 001.1 2.2 8.8 8.8 0 013.3 6.9 8.8 8.8 0 01-9.9 8.8 8.8 8.8 0 01-4.5-15.7 3 3 0 001-2.2c0-2.4-2.7-3.8-4.6-2.3a14.6 14.6 0 00-5.5 12.9 14.7 14.7 0 1023.9-13 2.8 2.8 0 00-1.9-.6z'/%3E%3C/svg%3E\");"
"--shutdown_hover:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cpath fill='%23900' d='M16 1a3 3 0 00-3 3v9.3a3 3 0 002.9 2.9 3 3 0 003-2.8V4a3 3 0 00-3-3zm7.2 3.2a3 3 0 00-2.8 3 3 3 0 001.1 2.2 8.8 8.8 0 013.3 6.9 8.8 8.8 0 01-9.9 8.8 8.8 8.8 0 01-4.5-15.7 3 3 0 001-2.2c0-2.4-2.7-3.8-4.6-2.3a14.6 14.6 0 00-5.5 12.9 14.7 14.7 0 1023.9-13 2.8 2.8 0 00-1.9-.6z'/%3E%3C/svg%3E\");"
"--noshutdown:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cpath fill='%230a4' d='M32.1 12.63a3.89 3.89 0 00-3.89 3.89v12.05a3.89 3.89 0 003.76 3.76 3.89 3.89 0 003.89-3.63V16.52a3.89 3.89 0 00-3.89-3.89zm9.33 4.15a3.89 3.89 0 00-3.63 3.89 3.89 3.89 0 001.43 2.85 11.4 11.4 0 014.28 8.94 11.4 11.4 0 01-12.83 11.4 11.4 11.4 0 01-5.83-20.34 3.89 3.89 0 001.3-2.85c0-3.11-3.5-4.93-5.97-2.98a18.92 18.92 0 00-7.13 16.71 19.05 19.05 0 1030.97-16.84 3.63 3.63 0 00-2.46-.78z'/%3E%3Cpath fill='%23d40000' d='M32 1a31 31 0 100 62 31 31 0 000-62zm0 7.9c4 0 8 1 11.3 2.9a1.9 1.9 0 01.4 3l-29 28.9a1.9 1.9 0 01-3-.5A23 23 0 0132 9zm18.5 10.9a1.9 1.9 0 011.7 1 23.2 23.2 0 01-31.5 31.5 1.9 1.9 0 01-.4-3l29-29a1.9 1.9 0 011.2-.5z'/%3E%3C/svg%3E\");"
"--transit:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cpath fill='%235b5' d='M13.4 21.5H23V34h13V21.5h9.6L29.5 2.4z'/%3E%3Cpath fill='%23ae6ba8' d='M49.2 30.2l-3 1.5a.5.5 0 00-.1 0L40 37.7a.5.5 0 000 .2.5.5 0 00-.2.1.5.5 0 000 .1.5.5 0 000 .1l.5 1.5a.5.5 0 00.3.3l1.4.6a.5.5 0 00.5 0l2.9-2.3 4.5-4.6a.5.5 0 00.2-.2l.8-2a.5.5 0 00-.3-.7l-1-.5a.5.5 0 00-.4 0zm7.3 3.2l-2.2.3a.5.5 0 00-.2 0l-7 6-3 3.6a.5.5 0 000 .6l.8 1.3a.5.5 0 00.3.3l1.5.2a.5.5 0 00.4-.1l5.8-5 4-4.7a.5.5 0 000-.3l.2-1.7a.5.5 0 00-.6-.6zm-45.4 5l-9.9.6a.5.5 0 00-.5.5L1 58.2a.5.5 0 00.4.5l26 3.1a.5.5 0 00.2 0l19.3-2.1a.5.5 0 00.3-.1l9.7-9 6.4-8.6a.5.5 0 00-.1-.7l-3.8-2.8a.5.5 0 00-.6 0c-2.4 2.5-4.6 5-7 7.3l-10 8.2-19.7.5 3.3-4.3 14-.8a.5.5 0 00.6-.5l-.1-5.4a.5.5 0 00-.5-.5l-17.5-1-10.5-3.6a.5.5 0 00-.2 0z'/%3E%3C/svg%3E\");"
"--notransit:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cpath fill='%235b5' d='M12.2 36.8h12v14.4h15.6V36.8h12l-19.2-24z'/%3E%3Cpath fill='%23d40000' d='M32 1a31 31 0 100 62 31 31 0 000-62zm0 7.9c4 0 8 1 11.3 2.9a1.9 1.9 0 01.4 3l-29 28.9a1.9 1.9 0 01-3-.5A23 23 0 0132 9zm18.5 10.9a1.9 1.9 0 011.7 1 23.2 23.2 0 01-31.5 31.5 1.9 1.9 0 01-.4-3l29-29a1.9 1.9 0 011.2-.5z'/%3E%3C/svg%3E\");"
"--testpeer:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cpath fill='%23e3dbdb' d='M0 0h40.5v64H0z'/%3E%3Cpath fill='%23f2484b' d='M8.9 16.1a1 1 0 01-.8-.3l-2-2.1a1 1 0 011.4-1.5L9 13.6l4.9-5a1 1 0 011.4 1.6l-5.6 5.6a1 1 0 01-.7.3z'/%3E%3Cpath fill='%236da8d6' d='M33.7 13.3H17.5a1 1 0 110-2.1h16.2a1 1 0 110 2.1zm-.5 14.4H15.4a1 1 0 110-2.2h17.8a1 1 0 110 2.2zM5.7 23.9h5.4v5.4H5.7zm27.6 16.9H15.5a1 1 0 110-2h17.8a1 1 0 110 2zM5.7 37.1h5.4v5.4H5.7zM33.3 54H15.5a1 1 0 110-2.1h17.8a1 1 0 110 2.1zM5.7 50.3h5.4v5.4H5.7z'/%3E%3Cg fill='%23eeb490' transform='translate(22.9 2) scale(.96766)'%3E%3Ccircle cx='10.8' cy='32.6' r='4'/%3E%3Ccircle cx='31.8' cy='32.6' r='4'/%3E%3C/g%3E%3Cpath fill='%23660080' d='M64 64H22.9v-7.4a6.8 6.8 0 015.9-6.7l9-3.6H49l9.1 3.6a6.8 6.8 0 015.9 6.7z'/%3E%3Cpath fill='%23aa00d4' d='M36 48.2v3.3c0 4.3 3.4 7.8 7.7 7.8 4-.1 7.2-3.4 7.2-7.6v-3.5c0-1.1-1-2.1-2-2.1H38.1c-1.3 0-2.3 1-2.3 2.1z'/%3E%3Cpath fill='%23eeb490' d='M38.4 43.5V49a5.1 5.1 0 1010.2 0v-5.5z'/%3E%3Cpath fill='%23faccb4' d='M48.6 23.4H38.4a4.4 4.4 0 00-4.4 4.3v9.9a9.6 9.6 0 0019.2 0v-9.9a4.4 4.4 0 00-4.5-4.4z'/%3E%3Cg fill='%2356545f'%3E%3Cpath d='M53 19.2s7.4 1 0 14.9v-5.8s-.5-4-3-4.3z'/%3E%3Cpath d='M48 14h-9a5.5 5.5 0 00-1.1 0c-13.9 0-4 20.8-4 20.8 0-8 5-8 5-8H48A6.4 6.4 0 0048 14z'/%3E%3C/g%3E%3C/svg%3E\");"
"--reloadcss:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cpath fill='%23e3dbdb' d='M0 0h45.71v64H0z'/%3E%3Cpath fill='%2304a' d='M19.47 39.69H4.24a1.05 1.05 0 110-2.11h15.23a1.05 1.05 0 110 2.1zm11.57 13.79H4.24a1.05 1.05 0 110-2.1h26.8a1.05 1.05 0 110 2.1zm-6-6.9H4.24a1.05 1.05 0 110-2.1h20.8a1.05 1.05 0 110 2.1zm8 13.8H4.24a1.05 1.05 0 110-2.1h28.8a1.05 1.05 0 110 2.1zM12.3 4.34q-1.52.5-2.43 1.73-.92 1.23-.92 2.8 0 .8.25 2.31.23 1.57.23 2.36 0 1.55-.7 2.47-.45.6-2.01 1.66-.82.57-1.26.82-.82.47-1.34.57 2 .29 3.6 1.98 1.55 1.71 1.55 3.73 0 .76-.23 2.28-.24 1.56-.24 2.32 0 1.48.97 2.68.92 1.17 2.37 1.68v.75h-.1q-2.4 0-4.26-1.55-1.98-1.64-1.98-4 0-.92.37-2.77.32-1.83.32-2.75 0-1.25-1.06-2.4-.95-1.03-2.24-1.5v-.95q.96-.3 2.08-1.26 1.1-.92 1.32-1.78.06-.27.06-1 0-.92-.35-2.77-.37-1.8-.37-2.74 0-2.28 2.08-3.9 1.92-1.46 4.3-1.46zm13.41 15.17q-.76.05-1.98 1.16-1.21 1.13-1.42 1.92-.1.4-.1.92 0 .94.4 2.71.36 1.83.36 2.75 0 2.35-2.05 3.96-1.92 1.49-4.32 1.49v-.7q1.48-.5 2.4-1.7.94-1.2.94-2.75 0-.8-.23-2.32-.27-1.52-.27-2.3 0-1.53.72-2.53.53-.66 2.02-1.7.82-.57 1.26-.83.82-.47 1.35-.56-2.01-.31-3.6-1.99-1.6-1.7-1.6-3.7 0-.77.2-2.32.21-1.55.21-2.3 0-1.49-.89-2.68-.93-1.16-2.38-1.65v-.77h.06q2.36 0 4.26 1.53 2.05 1.58 2.05 3.89 0 .93-.37 2.79-.36 1.84-.36 2.76 0 1.29 1.07 2.45.95 1.05 2.27 1.52z'/%3E%3Cpath fill='%23141' stroke='%23025' stroke-linejoin='round' stroke-width='3.66' d='M41.38 10.81v3.37a17.64 17.64 0 00-15.22 17.78 17.64 17.64 0 0017.42 17.86v.08h.14l3.22 3.29v-3.37a17.64 17.64 0 0015.23-17.86 17.64 17.64 0 00-17.42-17.78v0-.08h-.15zm0 11.27v5.13l4.98-5.13a10.1 10.1 0 01.58 19.76v-5.12l-5.05 5.12a10.1 10.1 0 01-8.2-9.95c0-4.76 3.3-8.78 7.69-9.8z'/%3E%3Cpath fill='%23aef' d='M43.58 14.03h1.1l-1.1 5.05v2.64a10.1 10.1 0 00-9.89 10.1 10.1 10.1 0 009.89 10.1l1.1-.15-1.1 7.83a17.57 17.57 0 01-17.42-17.64 17.64 17.64 0 0117.42-17.93z'/%3E%3Cpath fill='none' stroke='%23025' stroke-width='3.66' d='M47.02 36.8l-8.2 8.12 8.2 8.2'/%3E%3Cg fill='%2300aad4'%3E%3Cpath d='M44.67 49.9h-1.02l1.02-5.06v-2.7a10.1 10.1 0 000-20.2l-1.1.07 1.1-7.83a17.71 17.71 0 0117.5 17.86 17.64 17.64 0 01-17.5 17.86z'/%3E%3Cpath d='M46.94 53.04V36.8l-8.2 8.13z'/%3E%3C/g%3E%3Cpath fill='none' stroke='%23025' stroke-width='3.5' d='M41.3 27.13l8.2-8.12-8.27-8.27'/%3E%3Cpath fill='%23aef' d='M41.38 10.81v16.32l8.2-8.12z'/%3E%3C/svg%3E\");"
"--ping:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cpath fill='%23dbd' d='M32.6 4.14a28.06 28.06 0 00-25.38 16.2c-4.6 9.6-3 21.77 3.96 29.84a28.1 28.1 0 0028.65 9.02 28.1 28.1 0 0020.35-22.22 28.1 28.1 0 00-11.56-27.8A27.87 27.87 0 0032.6 4.14zm0 6.07a22 22 0 0119.92 12.74 21.94 21.94 0 11-41.56 12.83 22.03 22.03 0 019.2-21.72 21.98 21.98 0 0112.43-3.86zm0 13.4a8.6 8.6 0 00-8.23 6.2 8.6 8.6 0 003 9.1 8.6 8.6 0 009.43.68 8.58 8.58 0 004.3-8.38 8.6 8.6 0 00-8.52-7.63z'/%3E%3C/svg%3E\");"
"--scrollbar:#414 #101;"
"--ink:#dbd;"
"--ink-darker:#b9b;"
"--ink-faded:rgba(221,187,221,.5);"
"--notify:#5f5;"
"--page:#120012;"
"--main-boxshadow:0 0 0 1px var(--border),0 0 0 2px #000,0 0 0 4px #313,0 0 0 5px #212,0 0 0 6px #101;"
"--link:#ae6ba8;"
"--link_hover:#fafafa;"
"--border:#515;"
"--border2:#404;"
"--button-border:#313;"
"--button:linear-gradient(#303,#202 50%,#202 50%,#101);"
"--button_hover:linear-gradient(to bottom,#94518e,#733f6f 50%,#733f6f 50%,#42243f);"
"--button_active:linear-gradient(to bottom,#202,#303);"
"--active_shadow:inset 3px 3px 3px rgba(0,0,0,.8);"
"--hr:linear-gradient(to right,#313,#414,#313);"
"--highlight:inset 0 0 0 1px #101;"
"--tr:#180818;"
"--tr-alt:#202;"
"--tr-inner:#240024;"
"--header:linear-gradient(to bottom,#202,#101 50%,#101 50%,#000);"
"--th:linear-gradient(to bottom,#180018,#080008);"
"--th_multicolumn:linear-gradient(to bottom,#202,#101);"
"--sectiontitle:linear-gradient(to bottom,#240024,#140014 50%,#140014 50%,#080008);"
"--section:linear-gradient(to bottom,rgba(0,0,0,.5),rgba(8,0,8,.5));"
"--b64:#101;"
"--b64-ink:#2475c2;"
"--b64-boxshadow:0 0 0 1px #000,inset 0 0 0 1px #202;"
"--input_text:var(--button-border);"
"--menu:#303;"
"--menu-ink:#fff;"
"--textshadow:0 1px 1px rgba(0,0,0.7)"
"}"
"::selection{background:#d50;color:#fff}"
"html,body{min-height:100%;background:var(--page)}"
"html,body,textarea{scrollbar-color:var(--scrollbar)}"
"body{margin:0;padding:0;width:100%;height:100%;display:table;line-height:1.6;position:absolute;top:0;left:0;text-align:center;font:14pt var(--bodyfont);color:var(--ink);background:linear-gradient(to bottom,rgba(0,0,0,.1),rgba(32,0,32,.1),rgba(0,0,0,.1));background-attachment:fixed,fixed,fixed!important}"
".wrapper{margin:0 auto;padding:5px;width:100%;display:table-cell;vertical-align:middle;text-align:center}"
".header{display:inline-block;width:100%;vertical-align:middle;position:relative}"
"a,label,button,#navlinks,.sectiontitle span,.chrome,.notify,#tunnelsummary th:last-child,#tunnelsummary td:last-child{user-select:none}"
".listitem a,.listitem a + span,.b32{user-select:all}"
"#shutdownbutton,#enabletransit,#disabletransit{position:absolute;top:0;right:-5px;display:inline-block;width:28px;height:44px;font-size:0;background:var(--shutdown) no-repeat center center / 24px;mix-blend-mode:luminosity;display:none}"
"#enabletransit,#disabletransit{right:38px;background:var(--notransit) no-repeat center center / 24px}"
"#shutdownbutton:hover,#shutdownbutton:focus{background:var(--shutdown_hover) no-repeat center center / 24px;mix-blend-mode:normal}"
"#enabletransit:hover,#enabletransit:focus{background:var(--transit) no-repeat center center / 24px;mix-blend-mode:normal}"
"#shutdownbutton:active{transform:scale(.85)}"
"#shutdownbutton:active[data-tooltip]::after{display:none}"
"b{font-weight:600}"
".toast b{font-weight:900}"
"#main{margin:0 auto;width:100%;max-width:700px;font-size:85%;border:2px solid var(--border);border-spacing:1px;box-shadow:var(--main-boxshadow)}"
".center,.center form,.register{text-align:center!important}"
".left{text-align:left!important}"
".right{text-align:right!important}"
"form{margin:5px 0}"
"a,.slide label{text-decoration:none;color:var(--link);font-weight:600}"
".slide label{font-weight:700}"
".badge{margin:0 0 1px 4px;padding:0 10px 2px;display:inline-block;vertical-align:baseline;font-size:85%;border-radius:2px;background:var(--ink-darker);color:var(--page);text-shadow:none}"
"#netstatus .badge{margin:0 8px 0 0;padding:1px 6px;font-weight:600}"
"label:hover .badge{background:var(--ink)}"
"a{padding:1px 8px;display:inline-block;border-radius:2px}"
".listitem a{padding:0 1px}"
"a#home{width:calc(100% - 20px);height:44px;display:inline-block;font-size:0;background:var(--logo) no-repeat center center / auto 40px;opacity:.75;vertical-align:top}"
"a#home:hover{opacity:1}"
"a#home:active{opacity:.75;transform:scale(.9)}"
"a.view{padding-left:0;color:var(--ink);width:100%}"
"a.view:hover,a.view:focus,tr:active .view{padding-left:22px;color:var(--link);background:var(--eye) no-repeat left center / 16px}"
"a:hover,.slide label:hover,button:hover,select:hover,input[type=number]:focus,td.streamid:hover{color:var(--link_hover);background:var(--link)}"
"a.button,button,input,select{vertical-align:middle}"
"select,input,button{margin:4px 2px;padding:6px 10px;line-height:1.5;font-family:var(--bodyfont);font-size:90%!important;font-weight:600;color:var(--link);border:1px solid var(--button-border);-moz-appearance:none;-webkit-appearance:none;appearance:none}"
"a,select,button,label{text-shadow:var(--textshadow);cursor:pointer}"
"a.button,button{margin:4px 2px;padding:2px 8px 4px;min-width:64px;display:inline-block;font-size:90%!important;font-weight:700;text-align:center;text-decoration:none;border:1px solid var(--button-border);border-radius:2px;box-shadow:var(--highlight);background:var(--button);appearance:none}"
"a.button{margin:8px 2px}"
"button{padding:6px 12px;min-width:120px}"
"a.button:hover,a.button:focus{color:var(--link_hover);background:var(--button_hover)!important}"
"button:active,a.button:active,.slide label:active,td.streamid:active,.button.selected{box-shadow:var(--highlight),var(--active_shadow);background:var(--button_active)!important}"
".button.selected{pointer-events:none}"
".streamid:hover a{color:var(--link_hover)}"
"button.apply{padding:6px 12px;color:transparent;text-shadow:none!important;background:var(--yes_btn) no-repeat center center / 14px,var(--button)}"
"button.apply:hover,button.apply:focus{color:transparent;background:var(--yes) no-repeat center center / 14px,var(--button_hover)!important}"
"button.apply:active{color:transparent;background:var(--yes) no-repeat center center / 14px,var(--button_active)!important;background-blend-mode:luminosity}"
"select,input[type=number]{width:150px;box-sizing:border-box;font-size:90%!important;background:var(--input_text);text-shadow:0 1px 1px rgba(0,0,0,.6)}"
"input[type=number]{box-shadow:var(--highlight),var(--active_shadow);outline:none;appearance:none;-moz-appearance:textfield}"
"input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none}"
"select{padding:6px 20px 6px 8px;line-height:1.5;background:var(--dropdown) no-repeat right 8px center / 10px,var(--button);box-shadow:var(--highlight)}"
"select:hover,select:focus,select:active{color:var(--link_hover);background:var(--dropdown_hover) no-repeat right 8px center / 10px,var(--button_hover)}"
"select option{color:var(--menu-ink);background:var(--menu)}"
"select,option:hover,option:focus,option:active{outline:none}"
".note{margin:0 -6px;padding:15px 12px!important;font-size:95%;border:1px solid #414;background:radial-gradient(at bottom center,rgba(48,8,48,.3),rgba(0,0,0,0) 70%),linear-gradient(to bottom,rgba(32,0,32,.2),rgba(24,0,24,.2));box-shadow:inset 0 0 0 1px rgba(96,0,96,.2),0 0 0 1px #000;white-space:normal}"
".note::before{margin:-3px 2px 0 -2px;width:24px;height:18px;display:inline-block;vertical-align:middle;background:var(--info) no-repeat center center / 16px;content:''}"
".routerservice{display:inline-block;margin:4px 2px;padding:2px 10px 2px 25px;background:#303 var(--yes) no-repeat 8px center / 10px;border-radius:2px;text-align:left;font-size:90%}"
"table{background:repeating-linear-gradient(to bottom,rgba(24,0,24,.3) 2px,rgba(48,0,48,.3) 4px),repeating-linear-gradient(to right,rgba(48,0,48,.8) 2px,rgba(24,0,24,.5) 4px),linear-gradient(to bottom,#240024,#200020);background-size:100% 4px,4px 100%,100%}"
"tr{border-top:1px solid var(--border);border-bottom:1px solid var(--border)}"
"tr#version,tr#version ~ tr:nth-child(odd),tr.chrome,.listitem:nth-child(odd),tr:not(.chrome):nth-child(odd),#routerinfos tr:nth-child(odd),#tunnelsummary tr:nth-child(even){background:linear-gradient(to bottom,rgba(16,0,16,.5),rgba(8,0,8,.5))}"
"tr#version ~ tr:nth-child(even),.listitem:nth-child(even),tr:not(.chrome):nth-child(even),#routerinfos tr:nth-child(even),#tunnelsummary tr:nth-child(odd){background:linear-gradient(to bottom,rgba(32,0,32,.5),rgba(24,0,24,.5))}"
"tr tr,.tableitem tr:nth-child(odd){background:var(--tr-inner)}"
"tr.chrome{background:linear-gradient(to bottom,rgba(16,0,16,.5),rgba(8,0,8,.5))}"
".tableitem tr:nth-child(even){background:var(--tr)}"
"th,td,.listitem{box-shadow:var(--highlight);font-size:97%}"
"th,td{padding:5px 12px;border:1px solid var(--button-border)}"
"th{padding:6px 12px;font-weight:600;background:var(--th_multicolumn)}"
"th.in,th.out{font-size:0}"
"th.in{background:var(--arrow_down) no-repeat center center / 14px,var(--th_multicolumn)}"
"th.out{background:var(--arrow_up) no-repeat center center / 14px,var(--th_multicolumn)}"
"th,#routerservices{background:linear-gradient(to right,rgba(0,0,0,.4),rgba(0,0,0,0),rgba(0,0,0,.4)),rgba(32,0,32,.4)}"
".slide label{font-size:95%}"
"th{font-size:88%}"
".sectiontitle,.sectiontitle th,.chrome td{border-top:1px solid var(--border)!important}"
".chrome td,#last td{border-bottom:1px solid var(--border)!important}"
".sectiontitle th{padding:0 0 10px!important;font-weight:700;font-size:95%;border-bottom:none;background:var(--th_multicolumn)}"
".sectiontitle span:not(.routerservice):not(.hide):not(.badge){padding:2px 12px 4px;min-width:50%;display:inline-block;white-space:nowrap;line-height:1.6;font-size:95%;border:1px solid var(--border);border-top:none;border-radius:0 0 4px 4px;box-shadow:var(--highlight),0 2px 2px rgba(0,0,0,.4);background:radial-gradient(at top center,rgba(64,16,64,.4),rgba(0,0,0,0) 50%),var(--sectiontitle)}"
"table table th{font-size:80%}"
"tr:first-child{background:var(--header)}"
"td:first-child{width:50%;text-align:right;font-weight:600}"
"td td:first-child{width:auto}"
"td:last-child{text-align:left}"
"#routerinfos td:first-child{font-weight:700}"
".listitem,.tableitem{padding:5px 0;white-space:nowrap;font-size:80%;font-family:var(--monospaced)}"
".listitem{display:inline-block;width:100%;vertical-align:middle;border-top:1px solid var(--button-border)}"
".listitem:last-child{border-bottom:1px solid var(--button-border)}"
".listitem.out .arrowup,.listitem.in .arrowdown{margin:3px 8px 0 16px;float:left}"
".error,.notify{padding:0;font-size:110%;color:#fff;box-shadow:var(--highlight),inset 0 0 3px 3px rgba(0,0,0,.6);text-align:center;background:linear-gradient(to bottom,rgba(32,0,32,.5),rgba(4,0,4,.7))}"
".toast + .toast{display:none}"
".container{margin:12px;padding:40px 20px;display:inline-block;width:calc(100% - 24px);background:linear-gradient(to bottom,rgba(96,16,96,.15),rgba(160,56,160,.15));box-shadow:0 0 0 1px rgba(255,160,255,.3),inset 0 0 0 2px #000,inset 0 0 0 3px #303;box-sizing:border-box;text-shadow:0 1px 1px #000}"
"#warning,#success{margin:-5px 0 10px;width:100%;height:48px;display:block;background:var(--error) no-repeat center top / 44px}"
"#success{background:var(--success) no-repeat center top / 40px}"
".thin{width:1%;white-space:nowrap}"
"#header,#navlinks{background:var(--header)}"
"#header td,#navlinks{border:1px solid var(--border)}"
"#navlinks{padding:10px 2px!important;font-size:100%}"
"#navlinks a:hover{background:var(--button_hover)}"
"#navlinks a:active{color:var(--ink-faded);box-shadow:var(--highlight),var(--active_shadow),0 0 0 1px var(--button-border);background:var(--button_active)}"
".enabled,.disabled{font-size:0;display:inline-block;width:10px;height:10px;vertical-align:middle}"
".enabled{background:var(--yes) no-repeat left 12px center / 10px}"
".disabled{background:var(--no) no-repeat left 12px center / 10px}"
".enabled.fixedsize{margin-top:-5px;width:14px!important;height:14px!important;background:var(--yes) no-repeat center center / 14px!important}"
".role{margin:-2px 3px -2px 6px;width:24px;height:24px;display:inline-block;vertical-align:middle;font-size:0}"
".role ~ .arrowright{display:none}"
".ibgw{background:var(--ibgw) no-repeat center center / 24px}"
".obep{background:var(--obep) no-repeat center center / 24px}"
".ptcp{background:var(--ptcp) no-repeat center center / 24px}"
".arrowright,.arrowleft,.arrowleftright,.arrowup,.arrowdown{width:12px;height:16px;display:inline-block;vertical-align:middle;font-size:0!important}"
".arrowleft{background:var(--arrow_left) no-repeat center center / 11px}"
".arrowright{background:var(--arrow_right) no-repeat center center / 11px}"
".arrowleftright{background:var(--arrow_double) no-repeat center center / 11px}"
".arrowup{background:var(--arrow_up) no-repeat center center / 12px}"
".arrowdown{background:var(--arrow_down) no-repeat center center / 12px}"
".tableitem .button{margin:0!important;padding:1px 4px!important;font-size:100%!important;border:none;background:none;box-shadow:none}"
".streamid .button,.streamid .button:hover,.streamid .button:focus,.streamid .button:active{background:none!important;box-shadow:none!important}"
".tableitem a.button .close{margin:-2px -6px 0 0;width:11px;height:11px;display:inline-block;vertical-align:middle;color:transparent!important;text-shadow:none!important;background:var(--no) no-repeat center center / 9px!important;opacity:.8}"
".tableitem a.button:hover .close,.tableitem a.button:focus .close{opacity:1}"
".tunnel.established{color:#56B734}"
".tunnel.expiring{color:#D3AE3F}"
".tunnel.failed{color:#D33F3F}"
".tunnel.building{color:#434343}"
"caption{font-size:1.5em;text-align:center;color:var(--link)}"
"table{display:table;border-collapse:collapse;text-align:center}"
"td table{width:100%!important}"
"table.extaddr{text-align:left}"
"table.services{width:100%}"
"#b64{margin:2px -4px;padding:3px 4px;width:calc(100% + 8px);word-break:break-all;color:var(--b64-ink);border:1px solid var(--button-border);background:var(--b64);font-family:var(--monospaced);font-size:80%;display:inline-block;line-height:1;box-sizing:border-box;user-select:all;box-shadow:var(--b64-boxshadow);white-space:pre-wrap;margin:4px;width:calc(100% - 8px);text-align:justify}"
".streamdest{width:120px;max-width:240px;overflow:hidden;text-overflow:ellipsis}"
".slide div.slidecontent,.slide [type=checkbox]{display:none}"
".slide [type=checkbox]:checked ~ div.slidecontent{margin-top:0;padding:0;display:block}"
".disabled{color:#D33F3F}"
".enabled{color:#56B734}"
".nopadding{padding:0!important}"
".nopadding table{border:none!important}"
".tunnelid.local,.tunnel{display:inline-block;width:16px;height:16px;vertical-align:middle;font-size:0;background:var(--local) no-repeat center center / 16px}"
".tunnelid:not(.local){padding:2px 4px 0 22px;display:inline-block;width:auto;height:16px;vertical-align:middle;border-radius:2px;box-shadow:0 0 0 1px #000;background:#303 var(--tunnel) no-repeat 4px center / 14px;text-align:left;min-width:86px;border-left:5px solid var(--border2);border-radius:0 2px 2px 0}"
".chain.transit .tunnelid{margin-left:14px}"
".tunnel{margin:1px 5px 0;width:26px;height:16px;float:left;vertical-align:middle;background:var(--established) no-repeat left center / 12px}"
".tunnelid.local + .tunnel{margin-left:4px}"
".tunnel.building{background:var(--building) no-repeat left center / 12px}"
".tunnel.failed{background:var(--failed) no-repeat left center / 12px}"
".tunnel.expiring{background:var(--expiring) no-repeat left center / 12px}"
".tunnel.exploratory{background:var(--established) no-repeat left center / 12px,var(--exploratory) no-repeat right 3px / 12px}"
".tunnel.building.exploratory{background:var(--building) no-repeat left center / 12px,var(--exploratory) no-repeat right 3px / 12px}"
".tunnel.expiring.exploratory{background:var(--expiring) no-repeat left center / 12px,var(--exploratory) no-repeat right 3px / 12px}"
".tunnel.failed.exploratory{background:var(--failed) no-repeat left center / 12px,var(--exploratory) no-repeat right 3px / 12px}"
"span[data-tooltip]{position:relative;text-transform:capitalize}"
"span[data-tooltip]::after{font-family:var(--bodyfont)}"
".hops{text-align:right}"
".hop,.host{padding:1px 4px;display:inline-block;vertical-align:middle;border-radius:2px;box-shadow:0 0 0 1px #000;background:#303}"
".chain.inbound .arrowright:not(.zerohop):first-of-type{display:none!important}"
".host{padding-left:17px;background:#303 var(--planet) no-repeat 4px center / 9px}"
"a[href^=\"https://gwhois\"]:hover,a[href^=\"https://gwhois\"]:focus{background:none!important}"
"a:hover .host,a:focus .host,a:active .host{background:#505 var(--exploratory) no-repeat 2px center / 13px}"
".transferred{display:inline-block;vertical-align:middle;text-align:right}"
".latency,.expiry{padding:2px 5px 2px 20px;min-width:40px;display:inline-block;vertical-align:middle;text-align:right;float:right;background:var(--page) var(--time) no-repeat 5px center / 13px;border-radius:2px}"
".latency{background:var(--page) var(--ping) no-repeat 5px center / 13px}"
".expiry{margin-left:10px;float:none}"
".latency.unknown{color:var(--ink-faded)}"
".sent,.recvd{padding-right:16px;display:inline-block;vertical-align:middle;text-align:right;background:var(--arrow_up) no-repeat right center / 12px}"
".transit.sent{background:var(--arrow_up_transit) no-repeat right center / 12px}"
".recvd{background:var(--arrow_down) no-repeat right center / 12px}"
".hide{display:none}"
".router.sent,.router.recvd,.transit.sent{padding-left:17px;padding-right:0;text-align:left;background-size:14px;background-position:left center}"
".router.sent{margin-left:6px}"
".itag{padding-left:13px;display:inline-block;vertical-align:middle;background:var(--tag) no-repeat left center / 10px}"
"a[href^=\"https://gwhois\"],.cmd{position:relative}"
"span[data-tooltip]:hover::after,span[data-tooltip]:active::after,.itag[data-tooltip]:hover::after,.itag[data-tooltip]:active::after,.header a[data-tooltip][href*=\"cmd\"]:hover::after,.cmd[data-tooltip]:hover::after{padding:3px 6px;display:inline-block;position:absolute;top:-32px;left:-10px;font-size:12px;font-weight:600;color:#444;border:1px solid #444;box-shadow:0 0 1px 1px rgba(0,0,0,.2);background:#fff!important;content:attr(data-tooltip);text-shadow:none!important;white-space:nowrap}"
".header a[data-tooltip][href*=\"cmd\"]:hover::after{top:auto;right:-8px;bottom:42px;left:auto}"
".cmd[data-tooltip]:hover::after{left:-16px}"
".slide label{margin:0;padding:6px 0 6px 20px;width:100%;display:block;border:1px solid var(--border);border-left:none;border-right:none;box-shadow:var(--highlight);background:linear-gradient(to bottom,rgba(48,8,48,.5),rgba(0,0,0,.8));box-sizing:border-box;color:var(--ink)}"
"input[type=checkbox] + label::after{content:\"+\";display:inline-block;vertical-align:middle;float:right;margin:-6px 10px 2px 0;font-size:16pt;font-weight:700;color:var(--ink);opacity:.7}"
"input[type=checkbox]:checked + label::after{content:\"\"}"
".slide label:hover{color:var(--link_hover);background:var(--button_hover);opacity:.9}"
".slide label:active::after{transform:scale(.9)}"
".slide table{width:100%}"
"#rid{margin:5px 8px;padding:2px 4px;display:inline-block;font-size:80%;border-radius:2px;background:#202;user-select:all;word-break:break-all}"
"#loglevel{margin-top:9px}"
"#commands,#routerservices{margin-top:11px}"
"#routerservices{background:none}"
"#consolelang,#maxtransit{margin-top:16px}"
"#commands br{display:none}"
".cmd{margin:4px;padding:8px;display:inline-block;font-size:0;width:42px;height:42px;background:red;min-width:0;box-sizing:border-box;vertical-align:middle;border:1px solid var(--button-border);border-radius:2px;box-shadow:var(--highlight)}"
".cmd:active{box-shadow:var(--highlight),var(--active_shadow);background-blend-mode:luminosity}"
"#testpeer{background:var(--testpeer) no-repeat center center / 30px,var(--button)}"
"#testpeer:hover,#testpeer:focus{background:var(--testpeer) no-repeat center center / 32px,var(--button_hover)}"
"#testpeer:active{background:var(--testpeer) no-repeat center center / 28px,var(--button_active)}"
"#transitaccept{background:var(--transit) no-repeat center center / 30px,var(--button)}"
"#transitaccept:hover,#transitaccept:focus{background:var(--transit) no-repeat center center / 32px,var(--button_hover)}"
"#transitaccept:active{background:var(--transit) no-repeat center center / 28px,var(--button_active)}"
"#transitdecline{background:var(--notransit) no-repeat center center / 30px,var(--button)}"
"#transitdecline:hover,#transitdecline:focus{background:var(--notransit) no-repeat center center / 32px,var(--button_hover)}"
"#transitdecline:active{background:var(--notransit) no-repeat center center / 28px,var(--button_active)}"
"#shutdownforce,#shutdowngraceful{background:var(--shutdown_hover) no-repeat center center / 30px,var(--button)}"
"#shutdownforce:hover,#shutdownforce:focus,#shutdowngraceful:hover,#shutdowngraceful:focus{background:var(--shutdown_hover) no-repeat center center / 32px,var(--button_hover)}"
"#shutdownforce:active,#shutdowngraceful:active{background:var(--shutdown_hover) no-repeat center center / 28px,var(--button_active)}"
"#shutdowngraceful{display:none}"
"#shutdowncancel{background:var(--noshutdown) no-repeat center center / 30px,var(--button)}"
"#shutdowncancel:hover,#shutdowncancel:focus{background:var(--noshutdown) no-repeat center center / 32px,var(--button_hover)}"
"#shutdowncancel:active{background:var(--noshutdown) no-repeat center center / 28px,var(--button_active)}"
"#reloadcss{background:var(--reloadcss) no-repeat center center / 30px,var(--button)}"
"#reloadcss:hover,#reloadcss:focus{background:var(--reloadcss) no-repeat center center / 32px,var(--button_hover)}"
"#reloadcss:active{background:var(--reloadcss) no-repeat center center / 28px,var(--button_active)}"
"#tunnelsummary{position:relative}"
"#tunnelsummary tr:last-child,#tunnelsummary,#tunnelsummary tr:last-child td{border-bottom:none}"
"#tunnelsummary::after{position:absolute;bottom:-2px;left:0;right:0;z-index:10;content:'';display:inline-block;width:100%;height:1px;background:var(--border)}"
"#tunnelsummary th{background:var(--th_multicolumn)}"
"#tunnelsummary th.in{background:var(--arrow_down) no-repeat center center / 14px,var(--th_multicolumn)}"
"#tunnelsummary th.out{background:var(--arrow_up) no-repeat center center / 14px,var(--th_multicolumn)}"
"#tunnelsummary td{text-align:center}"
"#tunnelsummary .button{margin:0;display:inline-block;width:40px;height:16px;background:var(--eye) no-repeat center center / 16px,var(--button)!important;font-size:0!important}"
"#tunnelsummary .button:hover,#tunnelsummary .button:focus{background:var(--eye_hover) no-repeat center center / 16px,var(--button_hover)!important}"
"#tunnelsummary .button:active,#tunnelsummary .button:focus{background:var(--eye) no-repeat center center / 16px,var(--button_active)!important}"
"@media screen and (max-width: 1000px) {"
"body{font-size:13pt!important}"
".listitem{font-size:90%}"
"a{padding:1px 3px}"
".chrome a.button{min-width:40px}"
".b32,.listitem a[href*=\"local_destination&b32\"]{max-width:300px;display:inline-block;overflow:hidden;text-overflow:ellipsis;vertical-align:middle}"
".router.sent,.router.recvd,.transit.sent{min-width:48px;padding-left:15px;background-size:12px}"
".tunnelid:not(.local){display:none}"
".chain.transit .tunnelid{display:inline-block}"
".tunnel,.latency{margin:1px 6px 0 4px}"
".tunnel,.hops{margin-top:2px;display:inline-block;vertical-align:middle}"
".sectiontitle span{font-size:10.5pt}"
"}"
"@media screen and (-webkit-min-device-pixel-ratio: 1.5) {"
"body{font-size:12pt!important}"
".i2ptunnels .b32,.i2cp .b32{max-width:200px!important}"
"}"
"@media screen and (max-width: 800px) {"
"td{padding:5px 10px}"
"td:first-child{width:auto}"
"#rid{margin:5px 10px;padding:0;width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:90%;background:none;vertical-align:middle}"
".b32,.listitem a[href*=\"local_destination&b32\"]{max-width:300px}"
".arrowup,.arrowdown,.tunnel{float:none}"
".hops{min-width:190px}"
".tunnel{width:13px}"
".listitem.out .arrowup,.listitem.in .arrowdown,.tunnel,.latency{margin:0 1px;min-width:0}"
".listitem.out .arrowup,.listitem.in .arrowdown{margin:2px 0 0 1.5%}"
".latency{background-size:11px;background-color:transparent!important;float:left}"
".transferred{margin-right:1.5%;float:right}"
".hop{margin:0 -3px}"
".tunnel.exploratory{background-size:12px,11px!important}"
".itag{margin:2px 1.5% 0 0;min-width:90px;float:right;text-align:right;background:none}"
".itag::before{content:'';display:inline-block;vertical-align:middle;width:14px;height:10px;background:var(--tag) no-repeat left center / 10px}"
"}"
"@media screen and (min-width: 1000px) {"
".tunnelid[data-tooltip]:hover::after,.tunnelid[data-tooltip]:active::after{display:none}"
".hops{display:inline-block;min-width:240px}"
"}"
"@media screen and (min-width: 1200px) {"
"#navlinks a{margin-top:0;margin-bottom:0}"
".tunnelid{background-size:16px}"
".tunnelid:not(.local),.latency,.hops{margin-top:1px;margin-bottom:-1px}"
".tunnelid:not(.local){margin-left:12px;float:right}"
".chain{min-width:560px;display:inline-block;vertical-align:middle}"
"#transports .chain{min-width:580px;text-align:left}"
".chain.transit{min-width:0;text-align:center}"
"#transit.list{columns:2;column-gap:1px;column-rule:1px solid var(--border2)}"
"#transit.list .listitem:nth-child(even):last-child{border-bottom:none}"
".hops{min-width:280px;display:inline-block;text-align:right}"
".recvd,.sent{min-width:64px}"
".router.recvd,.router.sent{min-width:80px}"
".host{min-width:144px}"
".host a{margin-bottom:-1px}"
".SSU .host{min-width:190px}"
".i2ptunnels .listitem a{padding:2px 10px;min-width:100px;text-align:right}"
".i2ptunnels .listitem a:hover,.i2ptunnels .listitem a:focus{text-align:center}"
".listitem.out .arrowup,.listitem.in .arrowdown{margin-top:2px;background-size:14px}"
".i2ptunnels .b32{margin-right:10px}"
".itag,.host{margin-top:1px}"
".itag{padding:2px 5px 2px 20px;float:right;min-width:100px;display:inline-block;border-radius:2px;background-color:var(--menu);background-position:5px center}"
".latency,.expiry{padding-top:3px;padding-bottom:3px;margin-left:12px}"
".expiry{float:right}"
".transferred{min-width:48px}"
".tunnel{margin:2px 0 0 -48px}"
".note{padding:15px 90px!important}"
".button.selected{padding-left:16px;background:var(--yes) no-repeat 10px center / 12px,var(--button_active)!important}"
"}"
"@media screen and (min-width: 1200px) and (min-height: 600px) {"
".wrapper{padding:2%}"
"td,.listitem,.tableitem{padding-top:6px;padding-bottom:6px}"
".host,.hop{padding-top:2px;padding-bottom:2px}"
".tunnelid:not(.local){padding-top:3px;padding-bottom:1px}"
"}"
"@media screen and (min-width: 1400px) {"
"body{background:repeating-linear-gradient(to bottom,#000 2px,#101 4px),linear-gradient(135deg,#000,#180018,#000),linear-gradient(45deg,#000,#180018,#000);background-blend-mode:screen}"
"}\r\n"
"</style>\r\n";
// for external style sheet
std::string externalCSS;
static void LoadExtCSS ()
{
std::stringstream s;
std::string styleFile = i2p::fs::DataDirPath ("webconsole/style.css");
if (i2p::fs::Exists(styleFile)) {
std::ifstream f(styleFile, std::ifstream::binary);
s << f.rdbuf();
externalCSS = s.str();
} else if (externalCSS.length() != 0) { // clean up external style if file was removed
externalCSS = "";
}
}
static void GetStyles (std::stringstream& s)
{
if (externalCSS.length() != 0)
s << "<style>\r\n" << externalCSS << "</style>\r\n";
else
s << internalCSS;
}
const char HTTP_PAGE_TUNNEL_SUMMARY[] = "tunnel_summary";
const char HTTP_PAGE_LOCAL_TUNNELS[] = "local_tunnels";
const char HTTP_PAGE_TRANSIT_TUNNELS[] = "transit_tunnels";
const char HTTP_PAGE_TRANSPORTS[] = "transports";
const char HTTP_PAGE_LOCAL_DESTINATIONS[] = "local_destinations";
const char HTTP_PAGE_LOCAL_DESTINATION[] = "local_destination";
const char HTTP_PAGE_I2CP_LOCAL_DESTINATION[] = "i2cp_local_destination";
const char HTTP_PAGE_SAM_SESSIONS[] = "sam_sessions";
const char HTTP_PAGE_SAM_SESSION[] = "sam_session";
const char HTTP_PAGE_I2P_TUNNELS[] = "i2p_tunnels";
const char HTTP_PAGE_COMMANDS[] = "commands";
const char HTTP_PAGE_LEASESETS[] = "leasesets";
const char HTTP_COMMAND_ENABLE_TRANSIT[] = "enable_transit";
const char HTTP_COMMAND_DISABLE_TRANSIT[] = "disable_transit";
const char HTTP_COMMAND_SHUTDOWN_START[] = "shutdown_start";
const char HTTP_COMMAND_SHUTDOWN_CANCEL[] = "shutdown_cancel";
const char HTTP_COMMAND_SHUTDOWN_NOW[] = "terminate";
const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test";
const char HTTP_COMMAND_RELOAD_CONFIG[] = "reload_config";
const char HTTP_COMMAND_LOGLEVEL[] = "set_loglevel";
const char HTTP_COMMAND_KILLSTREAM[] = "closestream";
const char HTTP_COMMAND_LIMITTRANSIT[] = "limittransit";
const char HTTP_COMMAND_GET_REG_STRING[] = "get_reg_string";
const char HTTP_COMMAND_SETLANGUAGE[] = "setlanguage";
const char HTTP_COMMAND_RELOAD_CSS[] = "reload_css";
const char HTTP_PARAM_SAM_SESSION_ID[] = "id";
const char HTTP_PARAM_ADDRESS[] = "address";
static std::string ConvertTime (uint64_t time)
{
lldiv_t divTime = lldiv(time, 1000);
time_t t = divTime.quot;
struct tm *tm = localtime(&t);
char date[128];
snprintf(date, sizeof(date), "%02d/%02d/%d %02d:%02d:%02d.%03lld",
tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec, divTime.rem);
return date;
}
static void ShowUptime (std::stringstream& s, int seconds)
{
int num;
if ((num = seconds / 86400) > 0) {
s << num << " " << tr("day", "days", num) << ", ";
seconds -= num * 86400;
}
if ((num = seconds / 3600) > 0) {
s << num << " " << tr("hour", "hours", num) << ", ";
seconds -= num * 3600;
}
if ((num = seconds / 60) > 0) {
s << num << " " << tr("minute", "minutes", num) << ", ";
seconds -= num * 60;
}
s << seconds << " " << tr("second", "seconds", seconds);
}
static void ShowTraffic (std::stringstream& s, uint64_t bytes)
{
s << std::fixed << std::setprecision(0);
auto numKBytes = (double) bytes / 1024;
if (numKBytes < 1) {
s << std::fixed << std::setprecision(2);
s << numKBytes * 1024 << "&#8239;" << tr(/* tr: Byte */ "B");
} else if (numKBytes < 1024) {
s << numKBytes << "&#8239;" << tr(/* tr: Kibibit */ "K");
} else if (numKBytes < 1024 * 1024) {
s << std::fixed << std::setprecision(1);
s << numKBytes / 1024 << "&#8239;" << tr(/* tr: Mebibit */ "M");
} else if (numKBytes < 1024 * 1024 * 1024) {
s << std::fixed << std::setprecision(2);
s << numKBytes / 1024 / 1024 << "&#8239;" << tr(/* tr: Gibibit */ "G");
} else {
s << numKBytes / 1024 / 1024 / 1024 << "&#8239;" << tr(/* tr: Tibibit */ "T");
}
}
static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, double bytes)
{
std::string state, stateText;
switch (eState) {
case i2p::tunnel::eTunnelStateBuildReplyReceived :
case i2p::tunnel::eTunnelStatePending : state = "building"; break;
case i2p::tunnel::eTunnelStateBuildFailed :
case i2p::tunnel::eTunnelStateTestFailed :
case i2p::tunnel::eTunnelStateFailed : state = "failed"; break;
case i2p::tunnel::eTunnelStateExpiring : state = "expiring"; break;
case i2p::tunnel::eTunnelStateEstablished : state = "established"; break;
default: state = "unknown"; break;
}
if (state == "building") stateText = tr("building");
else if (state == "failed") stateText = tr("failed");
else if (state == "expiring") stateText = tr("expiring");
else if (state == "established") stateText = tr("established");
else stateText = tr("unknown");
s << "<span class=\"tunnel " << state << ((explr) ? " exploratory" : "")
<< "\" data-tooltip=\"" << stateText << ((explr) ? " (" + tr("exploratory") + ")" : "") << "\">"
<< stateText << ((explr) ? " (" + tr("exploratory") + ")" : "") << "</span>";
s << std::fixed << std::setprecision(0);
if (bytes > 1024 * 1024 * 1024) {
s << std::fixed << std::setprecision(2);
s << " <span class=\"transferred\">" << (double) (bytes / 1024 / 1024 / 1024) << "G</span>\r\n";
} else if (bytes > 1024 * 1024) {
s << std::fixed << std::setprecision(1);
s << " <span class=\"transferred\">" << (double) (bytes / 1024 / 1024) << "M</span>\r\n";
} else if (bytes > 1024) {
s << " <span class=\"transferred\">" << (int) (bytes / 1024) << "K</span>\r\n";
} else {
s << " <span class=\"transferred\">" << (int) (bytes) << "B</span>\r\n";
}
}
static void SetLogLevel (const std::string& level)
{
if (level == "none" || level == "error" || level == "warn" || level == "info" || level == "debug")
i2p::log::Logger().SetLogLevel(level);
else {
LogPrint(eLogError, "HTTPServer: Unknown loglevel set attempted");
return;
}
i2p::log::Logger().Reopen ();
}
static void ShowPageHead (std::stringstream& s)
{
std::string webroot; i2p::config::GetOption("http.webroot", webroot);
// Page language
std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage(); // get current used language
auto it = i2p::i18n::languages.find(currLang);
std::string langCode = it->second.ShortCode;
// SAM
auto sam = i2p::client::context.GetSAMBridge ();
std::map<std::string, std::string> params;
std::string page("");
URL url;
url.parse_query(params);
page = params["page"];
std::string token = params["token"];
s << "<!DOCTYPE html>\r\n"
"<html lang=\"" << langCode << "\">\r\n"
"<head>\r\n" /* TODO: Find something to parse html/template system. This is horrible. */
"<meta charset=\"UTF-8\">\r\n"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n"
"<link rel=\"shortcut icon\" href=\"" << i2pdfavicon << "\">\r\n"
"<title>Purple I2P | " VERSION "</title>\r\n";
GetStyles(s);
s << "</head>\r\n"
"<body>\r\n"
"<div class=\"wrapper\">\r\n<table id=\"main\">\r\n"
"<tr id=\"header\"><td class=\"center\" colspan=\"2\"><span class=\"header\">"
"<a id=\"home\" href=\"" << webroot << "\">" << tr("Main page") << "</a> "
// TODO placeholder for graceful shutdown button (requires token)
"<a id=\"shutdownbutton\" href=\"" << webroot << "?cmd="
<< HTTP_COMMAND_SHUTDOWN_START << "&amp;token=" << token << "\" data-tooltip=\""
<< tr("Start graceful shutdown") << "\">Shutdown</a>";
// placeholder for toggle transit (requires token)
if (i2p::context.AcceptsTunnels ()) {
s << "<a id=\"disabletransit\" href=\"" << webroot << "?cmd="
<< HTTP_COMMAND_DISABLE_TRANSIT << "&amp;token=" << token
<< "\" data-tooltip=\"" << tr("Decline transit tunnels")
<< "\">No transit</a>";
} else {
s << "<a id=\"enabletransit\" href=\"" << webroot << "?cmd="
<< HTTP_COMMAND_ENABLE_TRANSIT << "&amp;token=" << token
<< "\" data-tooltip=\"" << tr("Accept transit tunnels")
<< "\">Accept transit</a>";
}
s << "</span></td></tr>\r\n"
<< "<tr id=\"nav\"><td id=\"navlinks\" class=\"center\" colspan=\"2\">\r\n";
if (i2p::context.IsFloodfill ())
s << "<a href=\"" << webroot << "?page=" << HTTP_PAGE_LEASESETS << "\">" << tr("LeaseSets") << "</a>\r\n";
s << "<a title=\"" << tr("Local destinations currently active") << "\" href=\"" << webroot << "?page="
<< HTTP_PAGE_LOCAL_DESTINATIONS << "\">" << tr("Destinations") << "</a>\r\n"
// "<a title=\"" << tr("Local Service Tunnels") << "\" href=\"" << webroot << "?page=" << HTTP_PAGE_I2P_TUNNELS << "\">" << tr("Services") << "</a>\r\n"
// "<a title=\"" << tr("Active Transit Tunnels") << "\" href=\"" << webroot << "?page=" << HTTP_PAGE_TRANSIT_TUNNELS << "\">" << tr("Transit") << "</a>\r\n"
"<a title=\"" << tr("Router Transports and associated connections") << "\" href=\"" << webroot <<
"?page=" << HTTP_PAGE_TRANSPORTS << "\">" << tr ("Transports") << "</a>\r\n"
"<a title=\"" << tr("All active tunnels") << "\" href=\"" << webroot << "?page="
<< HTTP_PAGE_TUNNEL_SUMMARY << "\">" << tr("Tunnels") << "</a>\r\n";
if (sam && sam->GetSessions ().size ()) {
s << "<a title=\"" << tr("Current SAM sessions") << "\" href=\"" << webroot << "?page="
<< HTTP_PAGE_SAM_SESSIONS << "\">" << tr("SAM Sessions") << "</a>\r\n";
}
s << "<a title=\"" << tr("Router control and temporary configuration") << "\" href=\"" << webroot
<< "?page=" << HTTP_PAGE_COMMANDS << "\">" << tr("Control") << "</a>\r\n</td></tr>\r\n";
}
static void ShowPageTail (std::stringstream& s)
{
s << "</table>\r\n"
"</div>\r\n"
"</body>\r\n"
"</html>\r\n";
}
static void ShowError(std::stringstream& s, const std::string& string)
{
s << "<tr class=\"toast\"><td class=\"center error\" colspan=\"2\"><span class=\"container\"><span id=\"warning\"></span>\r\n<b>" << tr("ERROR")
<< ":</b>&nbsp;" << string << "</span></td></tr>\r\n";
}
static void ShowNetworkStatus (std::stringstream& s, RouterStatus status)
{
switch (status)
{
case eRouterStatusOK: s << tr("OK"); break;
case eRouterStatusTesting: s << tr("Testing"); break;
case eRouterStatusFirewalled: s << tr("Firewalled"); break;
case eRouterStatusUnknown: s << tr("Unknown"); break;
case eRouterStatusProxy: s << tr("Proxy"); break;
case eRouterStatusMesh: s << tr("Mesh"); break;
case eRouterStatusError:
{
s << tr("Error");
switch (i2p::context.GetError ())
{
case eRouterErrorClockSkew:
s << " - " << tr("Clock skew");
break;
case eRouterErrorOffline:
s << " - " << tr("Offline");
break;
case eRouterErrorSymmetricNAT:
s << " - " << tr("Symmetric NAT");
break;
default: ;
}
break;
}
default: s << tr("Unknown");
}
}
void ShowStatus (std::stringstream& s, bool includeHiddenContent, i2p::http::OutputFormatEnum outputFormat)
{
s << "<tr><td>" << tr("Network Status") << "</td><td id=\"netstatus\">";
ShowNetworkStatus (s, i2p::context.GetStatus ());
if (i2p::context.SupportsV6 ()) {
s << "<span class=\"badge\">" << tr("IPv6") << "</span> ";
ShowNetworkStatus (s, i2p::context.GetStatusV6 ());
}
s << "</td></tr>\r\n";
#if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY))
if (auto remains = Daemon.gracefulShutdownInterval) {
s << "<tr><td>" << tr("Shutdown") << "</td><td>";
ShowUptime(s, remains);
s << "&hellip;</td></tr>\r\n";
}
#elif defined(WIN32_APP)
if (i2p::win32::g_GracefulShutdownEndtime != 0) {
uint16_t remains = (i2p::win32::g_GracefulShutdownEndtime - GetTickCount()) / 1000;
s << "<tr><td>" << tr("Shutdown") << "</td><td>";
ShowUptime(s, remains);
s << "&hellip;</td></tr>\r\n";
}
#endif
s << "<tr><td>" << tr("Bandwidth") << "</td><td><span class=\"router recvd\">";
s << std::fixed << std::setprecision(0);
if (i2p::transport::transports.GetInBandwidth () > 1024*1024*1024 ||
i2p::transport::transports.GetInBandwidth () < 1024 &&
i2p::transport::transports.GetInBandwidth () > 0)
s << std::fixed << std::setprecision(2);
else if (i2p::transport::transports.GetInBandwidth () > 1024*1024)
s << std::fixed << std::setprecision(1);
s << (double) i2p::transport::transports.GetInBandwidth () / 1024 << "&#8239;" << tr(/* tr: Kibibit/s */ "K/s");
s << "</span> <span class=\"hide\">/</span> <span class=\"router sent\">";
s << std::fixed << std::setprecision(0);
if (i2p::transport::transports.GetOutBandwidth () > 1024*1024*1024 ||
i2p::transport::transports.GetOutBandwidth () < 1024 &&
i2p::transport::transports.GetOutBandwidth () > 0)
s << std::fixed << std::setprecision(2);
else if (i2p::transport::transports.GetOutBandwidth () > 1024*1024)
s << std::fixed << std::setprecision(1);
s << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << "&#8239;" << tr(/* tr: Kibibit/s */ "K/s");
s << "</span>";
if ((i2p::context.AcceptsTunnels() || i2p::tunnel::tunnels.CountTransitTunnels()) &&
(i2p::transport::transports.GetTotalReceivedBytes () > 0)) {
s << std::fixed << std::setprecision(0);
if (i2p::transport::transports.GetTransitBandwidth () > 1024*1024*1024 ||
i2p::transport::transports.GetTransitBandwidth () < 1024 &&
i2p::transport::transports.GetTransitBandwidth () > 0)
s << std::fixed << std::setprecision(2);
else if (i2p::transport::transports.GetTransitBandwidth () > 1024*1024)
s << std::fixed << std::setprecision(1);
s << " <span class=\"hide\">/</span> <span class=\"transit sent\" data-tooltip=\"";
s << tr("Transit bandwidth usage") << "\">";
s << (double) i2p::transport::transports.GetTransitBandwidth () / 1024;
s << "&#8239;" << tr(/* tr: Kibibit/s */ "K/s") << "</span>";
}
s << "</td></tr>\r\n";
s << "<tr><td>" << tr("Transferred") << "</td><td><span class=\"router recvd\">";
s << std::fixed << std::setprecision(0);
if (i2p::transport::transports.GetTotalReceivedBytes () > 1024*1024*1024)
s << std::fixed << std::setprecision(2);
else if (i2p::transport::transports.GetTotalReceivedBytes () > 1024*1024)
s << std::fixed << std::setprecision(1);
ShowTraffic (s, i2p::transport::transports.GetTotalReceivedBytes ());
s << "</span> <span class=\"hide\">/</span> <span class=\"router sent\">";
s << std::fixed << std::setprecision(0);
if (i2p::transport::transports.GetTotalSentBytes () > 1024*1024*1024)
s << std::fixed << std::setprecision(2);
else if (i2p::transport::transports.GetTotalSentBytes () > 1024*1024)
s << std::fixed << std::setprecision(1);
ShowTraffic (s, i2p::transport::transports.GetTotalSentBytes ());
s << "</span>";
if ((i2p::context.AcceptsTunnels() || i2p::tunnel::tunnels.CountTransitTunnels()) &&
(i2p::transport::transports.GetTotalReceivedBytes () > 0)) {
s << " <span class=\"hide\">/</span> <span class=\"transit sent\" data-tooltip=\"";
s << tr("Total transit data transferred") << "\">";
s << std::fixed << std::setprecision(0); // should set 0 bytes to no decimal places, but doesn't!
if (i2p::transport::transports.GetTotalTransitTransmittedBytes () > 1024*1024*1024)
s << std::fixed << std::setprecision(2);
else if (i2p::transport::transports.GetTotalTransitTransmittedBytes () > 1024*1024)
s << std::fixed << std::setprecision(1);
ShowTraffic (s, i2p::transport::transports.GetTotalTransitTransmittedBytes ());
s << "</span>";
}
s << "</td></tr>\r\n";
s << "<tr><td>" << tr("Build Success") << "</td><td>";
s << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%</td></tr>\r\n";
s << "<tr><td>" << tr("Routers") << "</td><td>" << i2p::data::netdb.GetNumRouters () << "</td></tr>\r\n";
s << "<tr><td>" << tr("Floodfills") << "</td><td>" << i2p::data::netdb.GetNumFloodfills () << "</td></tr>\r\n";
s << "<tr><td>" << tr("LeaseSets") << "</td><td>" << i2p::data::netdb.GetNumLeaseSets () << "</td></tr>\r\n";
size_t clientTunnelCount = i2p::tunnel::tunnels.CountOutboundTunnels();
clientTunnelCount += i2p::tunnel::tunnels.CountInboundTunnels();
std::string webroot; i2p::config::GetOption("http.webroot", webroot);
if (!(i2p::context.AcceptsTunnels () || i2p::tunnel::tunnels.CountTransitTunnels()))
s << "<tr id=\"last\">";
else
s << "<tr>";
s << "<td>" << tr("Local Tunnels") << "</td><td>" << std::to_string(clientTunnelCount) << "</td></tr>\r\n";
if (i2p::context.AcceptsTunnels () || i2p::tunnel::tunnels.CountTransitTunnels()) {
s << "<tr id=\"last\"><td>" << tr("Transit Tunnels") << "</td><td>"
<< std::to_string(i2p::tunnel::tunnels.CountTransitTunnels()) << "</td></tr>\r\n";
}
if(outputFormat==OutputFormatEnum::forWebConsole) {
bool httpproxy = i2p::client::context.GetHttpProxy () ? true : false;
bool socksproxy = i2p::client::context.GetSocksProxy () ? true : false;
bool bob = i2p::client::context.GetBOBCommandChannel () ? true : false;
bool sam = i2p::client::context.GetSAMBridge () ? true : false;
bool i2cp = i2p::client::context.GetI2CPServer () ? true : false;
bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol);
if (httpproxy || socksproxy || bob || sam || i2cp || i2pcontrol) {
s << "<tr class=\"center sectiontitle configuration\">"
<< "<th colspan=\"2\"><span>" << tr("Router Services") << "</span>\r\n";
s << "<div id=\"routerservices\" class=\"center\">";
if (httpproxy)
s << " <span class=\"routerservice\">HTTP " << tr("Proxy") << "</span> ";
if (socksproxy)
s << " <span class=\"routerservice\">SOCKS " << tr("Proxy") << "</span> ";
if (bob)
s << " <span class=\"routerservice\">BOB</span> ";
if (sam)
s << " <span class=\"routerservice\">SAM</span> ";
if (i2cp)
s << " <span class=\"routerservice\">I2CP</span> ";
if (i2pcontrol)
s << " <span class=\"routerservice\">I2PControl</span>";
s << "</div>\r\n</th></tr>\r\n";
}
}
s << "</tbody>\r\n";
}
void ShowLocalDestinations (std::stringstream& s)
{
std::string webroot; i2p::config::GetOption("http.webroot", webroot);
s << "<tr class=\"sectiontitle\"><th colspan=\"2\"><span>" << tr("Client Destinations")
<< "</span></th></tr>\r\n<tr><td class=\"center nopadding\" colspan=\"2\"><div class=\"list\">\r\n";
for (auto& it: i2p::client::context.GetDestinations ())
{
auto ident = it.second->GetIdentHash ();
s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "</a></div>\r\n" << std::endl;
}
s << "</div>\r\n</td></tr>\r\n";
auto i2cpServer = i2p::client::context.GetI2CPServer ();
if (i2cpServer && !(i2cpServer->GetSessions ().empty ()))
{
s << "<tr class=\"sectiontitle\"><th colspan=\"2\"><span>I2CP " << tr("Server Destinations")
<< "</span></th></tr>\r\n<tr><td class=\"center nopadding i2cp\" colspan=\"2\"><div class=\"list\">\r\n";
for (auto& it: i2cpServer->GetSessions ())
{
auto dest = it.second->GetDestination ();
if (dest)
{
auto ident = dest->GetIdentHash ();
auto& name = dest->GetNickname ();
s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_I2CP_LOCAL_DESTINATION << "&i2cp_id=" << it.first << "\">[ ";
s << name << " ]</a> <span class=\"arrowleftright\">&#8660;</span> <span class=\"b32\">"
<< i2p::client::context.GetAddressBook ().ToAddress(ident) <<"</span></div>\r\n" << std::endl;
}
}
s << "</div>\r\n</td></tr>\r\n";
}
}
static void ShowLeaseSetDestination (std::stringstream& s, std::shared_ptr<const i2p::client::LeaseSetDestination> dest, uint32_t token)
{
s << "<tr><td class=\"center nopadding\" colspan=\"2\">\r\n";
s << "<div class=\"slide\"><input hidden type=\"checkbox\" class=\"toggle\" id=\"slide_b64\" />\r\n"
<< "<label for=\"slide_b64\">" << tr("Base64 Address") << "</label>\r\n";
s << "<div class=\"slidecontent\">\r\n<div id=\"b64\">";
s << dest->GetIdentity ()->ToBase64 () << "</div>\r\n</div>\r\n</div>\r\n</td></tr>\r\n";
if (dest->IsEncryptedLeaseSet ())
{
i2p::data::BlindedPublicKey blinded (dest->GetIdentity (), dest->IsPerClientAuth ());
s << "<tr><th colspan=\"2\">" << tr("Encrypted B33 Address") << "</td</th>\r\n";
s << "<tr><td colspan=\"2\">" << blinded.ToB33 () << ".b32.i2p</td></tr>\r\n";
}
if(dest->IsPublic())
{
std::string webroot; i2p::config::GetOption("http.webroot", webroot);
auto base32 = dest->GetIdentHash ().ToBase32 ();
s << "<tr><th class=\"left\" colspan=\"2\">" << tr("Address Registration String") << "</th></tr>\r\n"
"<tr><td colspan=\"2\"><form class=\"register\" method=\"get\" action=\"" << webroot << "\">\r\n"
" <input type=\"hidden\" name=\"cmd\" value=\"" << HTTP_COMMAND_GET_REG_STRING << "\">\r\n"
" <input type=\"hidden\" name=\"token\" value=\"" << token << "\">\r\n"
" <input type=\"hidden\" name=\"b32\" value=\"" << base32 << "\">\r\n"
" <input type=\"text\" maxlength=\"67\" name=\"name\" placeholder=\"domain.i2p\" required>\r\n"
" <button type=\"submit\">" << tr("Generate") << "</button>\r\n"
"</form>\r\n<div class=\"note\">";
s << tr("<b>Note:</b> Result string can be used only for registering 2LD domains (example.i2p).")
<< " " << tr("For registering subdomains, please use i2pd-tools.");
s << "</div>\r\n</td></tr>\r\n";
}
if(dest->GetNumRemoteLeaseSets())
{
s << "<tr><td class=\"center nopadding\" colspan=\"2\">\r\n";
s << "<div class=\"slide\">\r\n<input hidden type=\"checkbox\" class=\"toggle\" id=\"slide_leasesets\" />\r\n"
<< "<label for=\"slide_leasesets\">" << tr("LeaseSets")
<< " <span class=\"hide\">[</span><span class=\"badge\">" << dest->GetNumRemoteLeaseSets ()
<< "</span><span class=\"hide\">]</span></label>\r\n";
s << "<div class=\"slidecontent\">\r\n<table>\r\n<thead>\r\n<tr>"
<< "<th class=\"left\">" << tr("Address") << "</th>"
<< "<th class=\"thin\">" << tr("Type") << "</th>"
<< "<th class=\"thin\">" << tr("EncType") << "</th>"
<< "</thead>\r\n<tbody class=\"tableitem\">\r\n";
for(auto& it: dest->GetLeaseSets ())
s << "<tr><td class=\"left\"><span class=\"b32\">" << it.first.ToBase32 () << "</span></td>\r\n"
<< "<td class=\"center thin\">" << (int)it.second->GetStoreType () << "</td>"
<< "<td class=\"center thin\">" << (int)it.second->GetEncryptionType () <<"</td>"
<< "</tr>\r\n";
s << "</tbody>\r\n</table>\r\n</div>\r\n</div>\r\n</td></tr>\r\n";
} else
s << "<tr><th colspan=\"2\">" << tr("No LeaseSets currently active") << "</th><tr>\r\n";
auto pool = dest->GetTunnelPool ();
if (pool)
{
s << "<tr><td class=\"center nopadding\" colspan=\"2\">\r\n";
s << "<div class=\"slide\">\r\n<input hidden type=\"checkbox\" class=\"toggle\" id=\"slide_tunnels\" />\r\n"
<< "<label for=\"slide_tunnels\">" << tr("Tunnels") << "</label>\r\n";
s << "<div class=\"slidecontent\">\r\n<div class=\"list\">\r\n";
for (auto & it : pool->GetInboundTunnels ()) { // inbound tunnels
s << "<div class=\"listitem in\">"
<< "<span class=\"arrowdown\" data-tooltip=\"" << tr("Inbound") << "\">[" << tr("In") << "] </span>"
<< "<span class=\"chain inbound\">";
it->Print(s);
if(it->LatencyIsKnown()) {
s << " <span class=\"latency\" data-tooltip=\"" << tr("Average tunnel latency") << "\">";
if (it->GetMeanLatency() >= 1000) {
s << std::fixed << std::setprecision(2);
s << (double) it->GetMeanLatency() / 1000 << tr(/* tr: seconds */ "s") << "</span> ";
} else {
s << it->GetMeanLatency() << tr(/* tr: Milliseconds */ "ms") << "</span> ";
}
} else { // placeholder for alignment
s << " <span class=\"latency unknown\" data-tooltip=\"" << tr("Unknown tunnel latency") << "\">---&nbsp;</span> ";
}
ShowTunnelDetails(s, it->GetState (), false, it->GetNumReceivedBytes ());
s << "</span></div>\r\n";
}
for (auto & it : pool->GetOutboundTunnels ()) { // outbound tunnels
s << "<div class=\"listitem out\">"
<< "<span class=\"arrowup\" data-tooltip=\"" << tr("Outbound") << "\">[" << tr("Out") << "] </span>"
<< "<span class=\"chain outbound\">";
it->Print(s);
if(it->LatencyIsKnown()) {
s << " <span class=\"latency\" data-tooltip=\"" << tr("Average tunnel latency") << "\">";
if (it->GetMeanLatency() >= 1000) {
s << std::fixed << std::setprecision(2);
s << (double) it->GetMeanLatency() / 1000 << tr(/* tr: seconds */ "s") << "</span> ";
} else {
s << it->GetMeanLatency() << tr(/* tr: Milliseconds */ "ms") << "</span> ";
}
} else { // placeholder for alignment
s << " <span class=\"latency unknown\" data-tooltip=\"" << tr("Unknown tunnel latency") << "\">---&nbsp;</span> ";
}
ShowTunnelDetails(s, it->GetState (), false, it->GetNumSentBytes ());
s << "</span></div>\r\n";
}
}
s << "</div>\r\n</div>\r\n</div>\r\n</td></tr>\r\n";
if (dest->GetNumIncomingTags () > 0) {
s << "<tr><th colspan=\"2\">" << tr("Incoming Session Tags")
<< " <span class=\"hide\">[</span><span class=\"badge\">"
<< dest->GetNumIncomingTags () << "</span><span class=\"hide\">]</span></th></tr>\r\n";
} else {
s << "<tr><th colspan=\"2\">" << tr("No Incoming Session Tags") << "</th></tr>\r\n";
}
if (!dest->GetSessions ().empty ()) {
std::stringstream tmp_s; uint32_t out_tags = 0;
s << "<tr><td class=\"center nopadding\" colspan=\"2\">";
for (const auto& it: dest->GetSessions ()) {
tmp_s << "<tr><td class=\"left\">" << i2p::client::context.GetAddressBook ().ToAddress(it.first)
<< "</td><td class=\"center thin\">" << it.second->GetNumOutgoingTags () << "</td></tr>\r\n";
out_tags += it.second->GetNumOutgoingTags ();
}
s << "<tr class=\"sectiontitle\"><th colspan=\"2\"><span>" << tr("Outgoing Session Tags")
<< " <span class=\"hide\">[</span><span class=\"badge\">" << out_tags
<< "</span><span class=\"hide\">]</span></th></tr>\r\n"
<< "<tr><td class=\"center nopadding\" colspan=\"2\"><table>\r\n"
<< "<thead>\r\n<tr><th class=\"left\">" << tr("Destination") << "</th><th class=\"thin\">" << tr("Count")
<< "</th></thead>\r\n<tbody class=\"tableitem\">\r\n" << tmp_s.str () << "</tbody></table>\r\n</td></tr>\r\n";
} else
s << "<tr><th colspan=\"2\">" << tr("No Outgoing Session Tags") << "</th></tr>\r\n";
auto numECIESx25519Tags = dest->GetNumIncomingECIESx25519Tags ();
if (numECIESx25519Tags > 0) {
s << "<tr class=\"sectiontitle\"><th colspan=\"2\"><span>ECIESx25519</span></th></tr>";
s << "<tr><th colspan=\"2\">" << tr("Incoming Tags")
<< " <span class=\"hide\">[</span><span class=\"badge\">" << numECIESx25519Tags
<< "</span><span class=\"hide\">]</span></th></tr>\r\n";
if (!dest->GetECIESx25519Sessions ().empty ())
{
std::stringstream tmp_s; uint32_t ecies_sessions = 0;
for (const auto& it: dest->GetECIESx25519Sessions ()) {
tmp_s << "<tr><td class=\"left\">"
<< i2p::client::context.GetAddressBook ().ToAddress(it.second->GetDestination ())
<< "</td><td class=\"center thin\">" << it.second->GetState () << "</td></tr>\r\n";
ecies_sessions++;
}
s << "<tr><td class=\"center nopadding\" colspan=\"2\">\r\n"
<< "<div class=\"slide\"><input hidden type=\"checkbox\" class=\"toggle\" id=\"slide-ecies-sessions\" />\r\n"
<< "<label for=\"slide-ecies-sessions\">" << tr("Tag Sessions")
<< " <span class=\"hide\">[</span><span class=\"badge\">" << ecies_sessions
<< "</span><span class=\"hide\">]</span></label>\r\n"
<< "<div class=\"slidecontent\">\r\n<table>\r\n<thead><th class=\"left\">" << tr("Destination") << "</th><th>"
<< tr("Status") << "</th></thead>\r\n<tbody class=\"tableitem\">\r\n" << tmp_s.str () << "</tbody></table>\r\n</div>\r\n</div>\r\n";
} else
s << "<tr><th coslpan=\"2\">" << tr("No Tag Sessions") << "</th></tr>\r\n";
}
}
void ShowLocalDestination (std::stringstream& s, const std::string& b32, uint32_t token)
{
i2p::data::IdentHash ident;
ident.FromBase32 (b32);
auto dest = i2p::client::context.FindLocalDestination (ident);
if (dest) {
std::string b32Short = b32.substr(0,6);
s << "<tr class=\"sectiontitle\"><th colspan=\"2\"><span>" << tr("Local Destination")
<< " <span class=\"hide\">[</span><span class=\"badge\">" << b32Short
<< "</span><span class=\"hide\">]</span></th></tr>\r\n";
} else
s << "<tr class=\"sectiontitle\"><th colspan=\"2\"><span>" << tr("Local Destination")
<< " <span class=\"hide\">[</span><span class=\"badge\">" << tr("Not Found")
<< "</span><span class=\"hide\">]</span></th></tr>\r\n";
if (dest)
{
ShowLeaseSetDestination (s, dest, token);
// Print table with streams information
s << "<tr><td class=\"center nopadding\" colspan=\"2\">\r\n";
s << "<div class=\"slide\">\r\n<input hidden type=\"checkbox\" class=\"toggle\" id=\"slide-streams\" />\r\n"
<< "<label for=\"slide-streams\">" << tr("Streams") << "</label>\r\n";
s << "<div class=\"slidecontent\">\r\n<table>\r\n<thead>\r\n<tr>";
s << "<th class=\"streamid\">ID</th>";
s << "<th class=\"streamdest\">Destination</th>";
s << "<th>TX</th>";
s << "<th>RX</th>";
s << "<th>Out</th>";
s << "<th>In</th>";
s << "<th>Buf</th>";
s << "<th>RTT</th>";
s << "<th>Win</th>";
s << "<th>Status</th>";
s << "</tr>\r\n</thead>\r\n";
s << "<tbody class=\"tableitem\">\r\n";
for (const auto& it: dest->GetAllStreams ())
{
auto streamDest = i2p::client::context.GetAddressBook ().ToAddress(it->GetRemoteIdentity ());
std::string streamDestShort = streamDest.substr(0,10) + "&hellip;b32.i2p";
s << "<tr>";
s << "<td class=\"center nopadding streamid\">"
<< "<a class=\"button\" href=\"/?cmd=" << HTTP_COMMAND_KILLSTREAM << "&b32=" << b32 << "&streamID="
<< it->GetRecvStreamID () << "&token=" << token << "\" title=\"" << tr("Close stream")
<< "\"><span class=\"close\">&#10005;</span> " << it->GetRecvStreamID () << "</a></td>";
s << "<td class=\"streamdest\" title=\"" << streamDest << "\">" << streamDestShort << "</td>";
s << std::fixed << std::setprecision(0);
if (it->GetNumSentBytes () > 1024 * 1024 * 1024) {
s << std::fixed << std::setprecision(2);
s << "<td>" << (double) it->GetNumSentBytes () / 1024 / 1024 / 1024 << "G</td>";
} else if (it->GetNumSentBytes () > 1024 * 1024) {
s << std::fixed << std::setprecision(2);
s << "<td>" << (double) it->GetNumSentBytes () / 1024 / 1024 << "M</td>";
} else {
s << "<td>" << it->GetNumSentBytes () / 1024 << "K</td>";
}
if (it->GetNumReceivedBytes () > 1024 * 1024 * 1024) {
s << std::fixed << std::setprecision(2);
s << "<td>" << (double) it->GetNumReceivedBytes () / 1024 / 1024 / 1024 << "G</td>";
} else if (it->GetNumReceivedBytes () > 1024 * 1024) {
s << std::fixed << std::setprecision(1);
s << "<td>" << (double) it->GetNumReceivedBytes () / 1024 / 1024 << "M</td>";
} else {
s << "<td>" << it->GetNumReceivedBytes () / 1024 << "K</td>";
}
s << "<td>" << it->GetSendQueueSize () << "</td>";
s << "<td>" << it->GetReceiveQueueSize () << "</td>";
s << "<td>" << it->GetSendBufferSize () << "</td>";
s << "<td>" << it->GetRTT () << "</td>";
s << "<td>" << it->GetWindowSize () << "</td>";
s << "<td class=\"center\">" << (int) it->GetStatus () << "</td>";
s << "</tr>\r\n";
}
s << "</tbody>\r\n</table>\r\n</div>\r\n</div>\r\n</td></tr>";
}
}
void ShowI2CPLocalDestination (std::stringstream& s, const std::string& id)
{
auto i2cpServer = i2p::client::context.GetI2CPServer ();
if (i2cpServer)
{
s << "<b>I2CP " << tr("Local Destination") << ":</b><br>\r\n<br>\r\n";
auto it = i2cpServer->GetSessions ().find (std::stoi (id));
if (it != i2cpServer->GetSessions ().end ())
ShowLeaseSetDestination (s, it->second->GetDestination (), 0);
else
ShowError(s, tr("I2CP session not found"));
}
else
ShowError(s, tr("I2CP is not enabled"));
}
void ShowLeasesSets(std::stringstream& s)
{
if (i2p::data::netdb.GetNumLeaseSets ())
{
s << "<tr><th class=\"nopadding\" colspan=\"2\">" << tr("LeaseSets") << "</th><tr>\r\n<tr><td class=\"center nopadding\"><div class=\"list\">\r\n";
int counter = 1;
// for each lease set
i2p::data::netdb.VisitLeaseSets(
[&s, &counter](const i2p::data::IdentHash dest, std::shared_ptr<i2p::data::LeaseSet> leaseSet)
{
// create copy of lease set so we extract leases
auto storeType = leaseSet->GetStoreType ();
std::unique_ptr<i2p::data::LeaseSet> ls;
if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET)
ls.reset (new i2p::data::LeaseSet (leaseSet->GetBuffer(), leaseSet->GetBufferLen()));
else
ls.reset (new i2p::data::LeaseSet2 (storeType, leaseSet->GetBuffer(), leaseSet->GetBufferLen()));
if (!ls) return;
s << "<div class=\"leaseset listitem";
if (ls->IsExpired())
s << " expired"; // additional css class for expired
s << "\">\r\n";
if (!ls->IsValid())
s << "<div class=\"invalid\">!! " << tr("Invalid") << " !! </div>\r\n";
s << "<div class=\"slide\"><input hidden type=\"checkbox\" class=\"toggle\" id=\"slide" << (counter++) << "\" />\r\n"
<< "<label for=\"slide" << counter << "\">" << dest.ToBase32() << "</label>\r\n";
s << "<div class=\"slidecontent\">\r\n";
s << "<b>" << tr("Store type") << ":</b> " << (int)storeType << "<br>\r\n";
s << "<b>" << tr("Expires") << ":</b> " << ConvertTime(ls->GetExpirationTime()) << "<br>\r\n";
if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET || storeType == i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2)
{
// leases information is available
auto leases = ls->GetNonExpiredLeases();
s << "<b>" << tr("Non Expired Leases") << ": " << leases.size() << "</b><br>\r\n";
for ( auto & l : leases )
{
s << "<b>" << tr("Gateway") << ":</b> " << l->tunnelGateway.ToBase64() << "<br>\r\n";
s << "<b>" << tr("TunnelID") << ":</b> " << l->tunnelID << "<br>\r\n";
s << "<b>" << tr("EndDate") << ":</b> " << ConvertTime(l->endDate) << "<br>\r\n";
}
}
s << "</div>\r\n</div>\r\n</div>\r\n";
}
);
s << "</td></tr>\r\n";
// end for each lease set
}
else if (!i2p::context.IsFloodfill ())
{
s << "<tr><th colspan=\"2\">" << tr("No LeaseSets") << " (" << tr("not floodfill") << ")</th</tr>\r\n";
}
else
{
s << "<tr><th colspan=\"2\">" << tr("No LeaseSets") << "</th</tr>\r\n";
}
}
void ShowTunnels (std::stringstream& s)
{
s << "<tr class=\"sectiontitle\"><th colspan=\"2\"><span>" << tr("Local Tunnels") << "</span></th><tr>\r\n";
s << "<tr><th colspan=\"2\">" << tr("Queue size") << ": " << i2p::tunnel::tunnels.GetQueueSize () << "</th></tr>\r\n";
auto ExplPool = i2p::tunnel::tunnels.GetExploratoryPool ();
s << "<tr><td class=\"center nopadding\" colspan=\"2\">\r\n";
s << "<div class=\"slide\">\r\n<input hidden type=\"checkbox\" class=\"toggle\" id=\"slide_tunnels_exploratory\" />\r\n"
<< "<label for=\"slide_tunnels_exploratory\">" << tr("Exploratory Tunnels") << " <span class=\"hide\">[</span><span class=\"badge\">" << "in/out"
<< "</span><span class=\"hide\">]</span></label>\r\n"; // TODO: separate client & exploratory tunnels into sections and flag individual services?
s << "<div class=\"slidecontent\">\r\n<div class=\"list\">\r\n";
for (auto & it : i2p::tunnel::tunnels.GetInboundTunnels ()) {
if (it->GetTunnelPool () == ExplPool) {
s << "<div class=\"listitem in\">"
<< "<span class=\"arrowdown\" data-tooltip=\"" << tr("Inbound") << "\">[" << tr("In") << "] </span>"
<< "<span class=\"chain inbound\">";
it->Print(s);
if(it->LatencyIsKnown()) {
s << " <span class=\"latency\" data-tooltip=\"" << tr("Average tunnel latency") << "\">";
if (it->GetMeanLatency() >= 1000) {
s << std::fixed << std::setprecision(2);
s << (double) it->GetMeanLatency() / 1000 << tr(/* tr: seconds */ "s") << "</span> ";
} else {
s << it->GetMeanLatency() << tr(/* tr: Milliseconds */ "ms") << "</span> ";
}
} else { // placeholder for alignment
s << " <span class=\"latency unknown\" data-tooltip=\"" << tr("Unknown tunnel latency") << "\">---&nbsp;</span> ";
}
ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumReceivedBytes ());
s << "</span></div>\r\n";
}
}
for (auto & it : i2p::tunnel::tunnels.GetOutboundTunnels ()) {
if (it->GetTunnelPool () == ExplPool) {
s << "<div class=\"listitem out\">"
<< "<span class=\"arrowup\" data-tooltip=\"" << tr("Outbound") << "\">[" << tr("Out") << "] </span>"
<< "<span class=\"chain outbound\">";
it->Print(s);
if(it->LatencyIsKnown()) {
s << " <span class=\"latency\" data-tooltip=\"" << tr("Average tunnel latency") << "\">";
if (it->GetMeanLatency() >= 1000) {
s << std::fixed << std::setprecision(2);
s << (double) it->GetMeanLatency() / 1000 << tr(/* tr: seconds */ "s") << "</span> ";
} else {
s << it->GetMeanLatency() << tr(/* tr: Milliseconds */ "ms") << "</span> ";
}
} else { // placeholder for alignment
s << " <span class=\"latency unknown\" data-tooltip=\"" << tr("Unknown tunnel latency") << "\">---&nbsp;</span> ";
}
ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumSentBytes ());
s << "</span>\r\n</div>\r\n";
}
}
s << "</div>\r\n</div>\r\n</div>\r\n";
s << "<div class=\"slide\">\r\n<input hidden type=\"checkbox\" class=\"toggle\" id=\"slide_tunnels_service\" />\r\n"
<< "<label for=\"slide_tunnels_service\">" << tr("Service Tunnels") << " <span class=\"hide\">[</span><span class=\"badge\">" << "in/out"
<< "</span><span class=\"hide\">]</span></label>\r\n"; // TODO: flag individual services by name
s << "<div class=\"slidecontent\">\r\n<div class=\"list\">\r\n";
for (auto & it : i2p::tunnel::tunnels.GetInboundTunnels ()) {
if (it->GetTunnelPool () != ExplPool) {
s << "<div class=\"listitem in\">"
<< "<span class=\"arrowdown\" data-tooltip=\"" << tr("Inbound") << "\">[" << tr("In") << "] </span>"
<< "<span class=\"chain inbound\">";
it->Print(s);
if(it->LatencyIsKnown()) {
s << " <span class=\"latency\" data-tooltip=\"" << tr("Average tunnel latency") << "\">";
if (it->GetMeanLatency() >= 1000) {
s << std::fixed << std::setprecision(2);
s << (double) it->GetMeanLatency() / 1000 << tr(/* tr: seconds */ "s") << "</span> ";
} else {
s << it->GetMeanLatency() << tr(/* tr: Milliseconds */ "ms") << "</span> ";
}
} else { // placeholder for alignment
s << " <span class=\"latency unknown\" data-tooltip=\"" << tr("Unknown tunnel latency") << "\">---&nbsp;</span> ";
}
ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumReceivedBytes ());
s << "</span></div>\r\n";
}
}
for (auto & it : i2p::tunnel::tunnels.GetOutboundTunnels ()) {
if (it->GetTunnelPool () != ExplPool) {
s << "<div class=\"listitem out\">"
<< "<span class=\"arrowup\" data-tooltip=\"" << tr("Outbound") << "\">[" << tr("Out") << "] </span>"
<< "<span class=\"chain outbound\">";
it->Print(s);
if(it->LatencyIsKnown()) {
s << " <span class=\"latency\" data-tooltip=\"" << tr("Average tunnel latency") << "\">";
if (it->GetMeanLatency() >= 1000) {
s << std::fixed << std::setprecision(2);
s << (double) it->GetMeanLatency() / 1000 << tr(/* tr: seconds */ "s") << "</span> ";
} else {
s << it->GetMeanLatency() << tr(/* tr: Milliseconds */ "ms") << "</span> ";
}
} else { // placeholder for alignment
s << " <span class=\"latency unknown\" data-tooltip=\"" << tr("Unknown tunnel latency") << "\">---&nbsp;</span> ";
}
ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumSentBytes ());
s << "</span>\r\n</div>\r\n";
}
}
s << "</div>\r\n</div>\r\n</div>\r\n</td></tr>\r\n";
}
void ShowTunnelSummary (std::stringstream& s) {
std::string webroot; i2p::config::GetOption("http.webroot", webroot);
size_t localInCount = i2p::tunnel::tunnels.CountInboundTunnels();
size_t localOutCount = i2p::tunnel::tunnels.CountOutboundTunnels();
size_t transitCount = i2p::tunnel::tunnels.CountTransitTunnels();
s << "<tr class=\"sectiontitle\"><th colspan=\"2\"><span>" << tr("Tunnel Summary") << "</span></th></tr>\r\n";
s << "<tr><td class=\"center nopadding\" colspan=\"2\">\r\n";
s << "<table id=\"tunnelsummary\">\r\n<thead>"
<< "<tr><th>" << tr("Type") << "</th>"
<< "<th class=\"in\">" << tr("Inbound") << "</th><th class=\"out\">" << tr("Outbound") << "</th>"
<< "<th>" << tr("View Details") << "</th></tr></thead>\r\n";
s << "<tr><td>" << tr("Local") << "</td><td class=\"in\">" << localInCount << "</td><td class=\"out\">" << localOutCount << "</td>"
<< "<td><a class=\"button\" href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_TUNNELS << "\">View</a></td></tr>\r\n";
if (transitCount > 0) {
s << "<tr><td>" << tr("Transit") << "</td><td colspan=\"2\">" << transitCount << "</td>"
<< "<td><a class=\"button\" href=\"" << webroot << "?page=" << HTTP_PAGE_TRANSIT_TUNNELS << "\">View</a></td></tr>\r\n";
}
s << "</table>\r\n";
s << "<tr><td class=\"center nopadding\" colspan=\"2\">";
ShowI2PTunnels (s);
s << "</td></tr>\r\n";
}
static void ShowCommands (std::stringstream& s, uint32_t token)
{
s << "<tr><td class=\"center nopadding\" colspan=\"2\">\r\n";
s << "<div class=\"slide\">\r\n<input hidden type=\"checkbox\" class=\"toggle\" id=\"slide_routerinfo\" />\r\n"
<< "<label for=\"slide_routerinfo\">i2pd " VERSION "</label>\r\n";
s << "<div class=\"slidecontent\">\r\n<table id=\"routerinfos\">\r\n";
s << "<tr><td>" << tr("Router Identity") << "</td><td class=\"nopadding\"><span id=\"rid\">"
<< i2p::context.GetRouterInfo().GetIdentHashBase64() << "</span></td></tr>\r\n";
s << "<tr><td>" << tr("Router Caps") << "</td><td>" << i2p::context.GetRouterInfo().GetProperty("caps") << "</td></tr>\r\n";
if (!i2p::context.GetRouterInfo().GetProperty("family").empty())
s << "<tr><td>" << tr("Router Family") << "</td><td>"
<< i2p::context.GetRouterInfo().GetProperty("family") << "</td></tr>\r\n";
auto family = i2p::context.GetFamily ();
if (family.length () > 0)
s << "<tr><td>"<< tr("Family") << "</td><td>" << family << "<br>\r\n";
for (const auto& address : i2p::context.GetRouterInfo().GetAddresses())
{
s << "<tr>\r\n";
if (address->IsNTCP2 () && !address->IsPublishedNTCP2 ())
{
s << "<td>NTCP2";
if (address->host.is_v6 ()) s << "v6";
s << "</td><td><span class=\"enabled fixedsize\">" << tr("supported") << "</span></td>\r\n</tr>\r\n";
continue;
}
switch (address->transportStyle)
{
case i2p::data::RouterInfo::eTransportNTCP:
{
s << "<td>NTCP";
if (address->IsPublishedNTCP2 ()) s << "2";
if (address->host.is_v6 ()) s << "v6";
s << "</td>\r\n";
break;
}
case i2p::data::RouterInfo::eTransportSSU:
{
s << "<td>SSU";
if (address->host.is_v6 ())
s << "v6";
s << "</td>\r\n";
break;
}
default:
s << "<td>" << tr("Unknown") << "</td>\r\n";
}
s << "<td>" << address->host.to_string() << ":" << address->port << "</td>\r\n</tr>\r\n";
}
s << "<tr><td>" << tr("Uptime") << "</td><td>";
ShowUptime(s, i2p::context.GetUptime ());
s << "</td></tr>\r\n";
s << "<tr><td>" << tr("Data path") << "</td><td>" << i2p::fs::GetUTF8DataDir() << "</td></tr>\r\n";
s << "</table>\r\n</div>\r\n</div>\r\n</td></tr>\r\n";
std::string webroot; i2p::config::GetOption("http.webroot", webroot);
s << "<tr class=\"sectiontitle\"><th colspan=\"2\"><span>" << tr("Router Commands") << "</span>"
<< "<div id=\"commands\" class=\"chrome\">\r\n";
std::string styleFile = i2p::fs::DataDirPath ("webconsole/style.css");
if (i2p::fs::Exists(styleFile)) {
s << "<a id=\"reloadcss\" class=\"cmd\" href=\"" << webroot << "?cmd="
<< HTTP_COMMAND_RELOAD_CSS << "&token=" << token
<< "\" data-tooltip=\"" << tr("Reload external CSS stylesheet") << "\">"
<< tr("Reload external CSS stylesheet") << "</a>";
}
s << " <a id=\"testpeer\" class=\"cmd\" href=\"" << webroot << "?cmd="
<< HTTP_COMMAND_RUN_PEER_TEST << "&token=" << token
<< "\" data-tooltip=\"" << tr("Run peer test") << "\">"
<< tr("Run peer test") << "</a><br>\r\n";
// s << " <a href=\"/?cmd=" << HTTP_COMMAND_RELOAD_CONFIG << "\">Reload config</a><br>\r\n";
if (i2p::context.AcceptsTunnels ())
s << " <a id=\"transitdecline\" class=\"cmd\" href=\"" << webroot << "?cmd="
<< HTTP_COMMAND_DISABLE_TRANSIT << "&token=" << token
<< "\" data-tooltip=\"" << tr("Decline transit tunnels") << "\">"
<< tr("Decline transit tunnels") << "</a><br>\r\n";
else
s << " <a id=\"transitaccept\" class=\"cmd\" href=\"" << webroot << "?cmd="
<< HTTP_COMMAND_ENABLE_TRANSIT << "&token=" << token
<< "\" data-tooltip=\"" << tr("Accept transit tunnels") << "\">"
<< tr("Accept transit tunnels") << "</a><br>\r\n";
if (i2p::tunnel::tunnels.CountTransitTunnels()) {
#if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY))
if (Daemon.gracefulShutdownInterval)
s << " <a id=\"shutdowncancel\" class=\"cmd\" href=\"" << webroot << "?cmd="
<< HTTP_COMMAND_SHUTDOWN_CANCEL << "&token=" << token
<< "\" data-tooltip=\"" << tr("Cancel graceful shutdown") << "\">"
<< tr("Cancel graceful shutdown") << "</a><br>\r\n";
else
s << " <a id=\"shutdowngraceful\" class=\"cmd\" href=\"" << webroot << "?cmd="
<< HTTP_COMMAND_SHUTDOWN_START << "&token=" << token
<< "\" data-tooltip=\"" << tr("Start graceful shutdown") << "\">"
<< tr("Start graceful shutdown") << "</a><br>\r\n";
#elif defined(WIN32_APP)
if (i2p::util::DaemonWin32::Instance().isGraceful)
s << " <a id=\"shutdowncancel\" class=\"cmd\" href=\"" << webroot << "?cmd="
<< HTTP_COMMAND_SHUTDOWN_CANCEL << "&token=" << token
<< "\" data-tooltip=\"" << tr("Cancel graceful shutdown") << "\">"
<< tr("Cancel graceful shutdown") << "</a><br>\r\n";
else
s << " <a id=\"shutdowngraceful\" class=\"cmd\" href=\"" << webroot << "?cmd="
<< HTTP_COMMAND_SHUTDOWN_START << "&token=" << token
<< "\" data-tooltip=\"" << tr("Start graceful shutdown") << "\">"
<< tr("Start graceful shutdown") << "</a><br>\r\n";
#endif
s << " <a id=\"shutdownforce\" class=\"cmd\" href=\"" << webroot << "?cmd="
<< HTTP_COMMAND_SHUTDOWN_NOW << "&token=" << token
<< "\" data-tooltip=\"" << tr("Force shutdown") << "\">"
<< tr("Force shutdown") << "</a></th></tr>\r\n";
/* TODO graceful shutdown button in header with .notify dialog if transit tunnels
active to offer option to shutdown immediately
only one option? displayed in the header
*/
} else {
s << " <a id=\"shutdownforce\" class=\"cmd\" href=\"" << webroot << "?cmd="
<< HTTP_COMMAND_SHUTDOWN_NOW << "&token=" << token
<< "\" data-tooltip=\"" << tr("Shutdown") << "\">"
<< tr("Shutdown") << "</a>";
}
s << "</div></th></tr>\r\n";
s << "<tr class=\"chrome notice\"><td class=\"center\" colspan=\"2\">\r\n<div class=\"note\">"
<< tr("<b>Note:</b> Configuration changes made here persist for the duration of the router session and will not be saved to your config file.")
<< "</div>\r\n</td></tr>";
const LogLevel loglevel = i2p::log::Logger().GetLogLevel();
s << "<tr class=\"sectiontitle\"><th colspan=\"2\"><span>" << tr("Logging Level") << "</span>\r\n";
s << "<div id=\"loglevel\" class=\"chrome\">";
s << "<a class=\"button" << (loglevel == 0 ? " selected" : "")
<< "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=none&token=" << token << "\">none</a>\r\n";
s << "<a class=\"button" << (loglevel == 1 ? " selected" : "")
<< "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=error&token=" << token << "\">error</a>\r\n";
s << "<a class=\"button" << (loglevel == 2 ? " selected" : "")
<< "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=warn&token=" << token << "\">warn</a>\r\n";
s << "<a class=\"button" << (loglevel == 3 ? " selected" : "")
<< "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=info&token=" << token << "\">info</a>\r\n";
s << "<a class=\"button" << (loglevel == 4 ? " selected" : "")
<< "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=debug&token=" << token << "\">debug</a>"
<< "</div>\r\n</th></tr>\r\n";
if (i2p::context.AcceptsTunnels ()) {
uint16_t maxTunnels = GetMaxNumTransitTunnels ();
s << "<tr class=\"sectiontitle\"><th colspan=\"2\"><span>" << tr("Maximum Transit Tunnels") << "</span>\r\n";
s << "<div id=\"maxtransit\" class=\"chrome\">\r\n";
s << "<form method=\"get\" action=\"" << webroot << "\">\r\n";
s << " <input type=\"hidden\" name=\"cmd\" value=\"" << HTTP_COMMAND_LIMITTRANSIT << "\">\r\n";
s << " <input type=\"hidden\" name=\"token\" value=\"" << token << "\">\r\n";
s << " <input type=\"number\" min=\"0\" max=\"65535\" name=\"limit\" value=\"" << maxTunnels << "\">\r\n";
s << " <button class=\"apply\" type=\"submit\">" << tr("Change") << "</button>\r\n";
s << "</form>\r\n</div>\r\n</th></tr>\r\n";
}
std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage(); // get current used language
s << "<tr class=\"sectiontitle\"><th colspan=\"2\"><span>" << tr("Console Display Language") << "</span>\r\n";
s << "<div id=\"consolelang\" class=\"chrome\">\r\n";
s << "<form method=\"get\" action=\"" << webroot << "\">\r\n";
s << " <input type=\"hidden\" name=\"cmd\" value=\"" << HTTP_COMMAND_SETLANGUAGE << "\">\r\n";
s << " <input type=\"hidden\" name=\"token\" value=\"" << token << "\">\r\n";
s << " <select name=\"lang\" id=\"lang\">\r\n";
for (const auto& it: i2p::i18n::languages)
s << " <option value=\"" << it.first << "\"" << ((it.first.compare(currLang) == 0) ? " selected" : "") << ">" << it.second.LocaleName << "</option>\r\n";
s << " </select>\r\n";
s << " <button class=\"apply\" type=\"submit\">" << tr("Change") << "</button>\r\n";
s << "</form>\r\n</div>\r\n</th></tr>\r\n";
}
void ShowTransitTunnels (std::stringstream& s)
{
if(i2p::tunnel::tunnels.CountTransitTunnels())
{
int count = i2p::tunnel::tunnels.GetTransitTunnels().size();
s << "<tr class=\"sectiontitle configuration\"><th colspan=\"2\"><span>" << tr("Transit Tunnels");
s << " <span class=\"hide\">[</span><span class=\"badge\">" << count << "</span><span class=\"hide\">]</span>"
<< "</span></th></tr>";
s << "<tr><td class=\"center nopadding\" colspan=\"2\">\r\n";
s << "<div ";
if (count > 7)
s << "id=\"transit\" ";
s << "class=\"list\">\r\n";
for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ())
{
const auto& expiry = i2p::tunnel::tunnels.GetTransitTunnelsExpirationTimeout ();
s << "<div class=\"listitem\"><span class=\"chain transit\">";
double bytes = it->GetNumTransmittedBytes ();
s << std::fixed << std::setprecision(0);
if (bytes > 1024 * 1024 * 1024) {
s << std::fixed << std::setprecision(2);
s << "<span class=\"sent\">" << (double) (bytes / 1024 / 1024 / 1024) << "G</span> ";
} else if (bytes > 1024 * 1024) {
s << std::fixed << std::setprecision(1);
s << "<span class=\"sent\">" << (double) (bytes / 1024 / 1024) << "M</span> ";
} else if (bytes > 1024) {
s << "<span class=\"sent\">" << (int) (bytes / 1024) << "K</span> ";
} else {
s << "<span class=\"sent\">" << (int) (bytes) << "B</span> ";
}
// TODO: tunnel expiry per tunnel, not most recent
//s << "<span class=\"expiry\">" << expiry << tr("s" /* translation: seconds */) << "</span> ";
s << "<span class=\"tunnelid\">" << it->GetTunnelID () << "</span> ";
if (std::dynamic_pointer_cast<i2p::tunnel::TransitTunnelGateway>(it))
s << "<span class=\"role ibgw\" data-tooltip=\"" << tr("inbound gateway") << "\">"
<< tr("inbound gateway") << "</span>";
else if (std::dynamic_pointer_cast<i2p::tunnel::TransitTunnelEndpoint>(it))
s << "<span class=\"role obep\"data-tooltip=\"" << tr("outbound endpoint") << "\">"
<< tr("outbound endpoint") << "</span>";
else
s << "<span class=\"role ptcp\" data-tooltip=\"" << tr("participant") << "\">"
<< tr("participant") << "</span>";
s << "</div>\r\n";
}
s << "</span></div></td></tr>\r\n";
}
else
{
s << "<tr><th colspan=\"2\">" << tr("No active transit tunnels") << "</th></tr>\r\n";
}
}
template<typename Sessions>
static void ShowNTCPTransports (std::stringstream& s, const Sessions& sessions, const std::string name)
{
std::stringstream tmp_s, tmp_s6; uint16_t cnt = 0, cnt6 = 0;
for (const auto& it: sessions )
{
if (it.second && it.second->IsEstablished () && !it.second->GetRemoteEndpoint ().address ().is_v6 ())
{
tmp_s << "<div class=\"listitem\">";
if (it.second->IsOutgoing ())
tmp_s << "<span class=\"arrowup\">&#8657;</span>";
else
tmp_s << "<span class=\"arrowdown\">&#8659;</span>";
tmp_s << " <span class=\"chain\">";
tmp_s << "<span class=\"hop\">" << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << "</span>"
<< " <a target=\"_blank\" href=\"https://gwhois.org/"
<< it.second->GetRemoteEndpoint ().address ().to_string () << "\" data-tooltip=\""
<< tr("Lookup address on gwhois.org") << "\"><span class=\"host\">"
<< it.second->GetRemoteEndpoint ().address ().to_string () << "</span></a>";
tmp_s << std::fixed << std::setprecision(0);
if (it.second->GetNumSentBytes () > 1024 * 1024) {
tmp_s << std::fixed << std::setprecision(1);
tmp_s << " <span class=\"sent\">" << (double) it.second->GetNumSentBytes () / 1024 / 1024 << "M</span>";
} else {
tmp_s << " <span class=\"sent\">" << (double) it.second->GetNumSentBytes () / 1024 << "K</span>";
}
tmp_s << std::fixed << std::setprecision(0);
if (it.second->GetNumReceivedBytes () > 1024 * 1024) {
tmp_s << std::fixed << std::setprecision(1);
tmp_s << " <span class=\"recvd\">" << (double) it.second->GetNumReceivedBytes () / 1024 / 1024 << "M</span>";
} else {
tmp_s << " <span class=\"recvd\">" << (double) it.second->GetNumReceivedBytes () / 1024 << "K</span>";
}
tmp_s << "</span></div>\r\n" << std::endl;
cnt++;
}
if (it.second && it.second->IsEstablished () && it.second->GetRemoteEndpoint ().address ().is_v6 ())
{
tmp_s6 << "<div class=\"listitem\">";
if (it.second->IsOutgoing ())
tmp_s6 << "<span class=\"arrowup\">&#8657;</span>";
else
tmp_s6 << "<span class=\"arrowdown\">&#8659;</span>";
tmp_s6 << " <span class=\"chain\">";
tmp_s6 << "<span class=\"hop\">" << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << "</span>"
<< " <a target=\"_blank\" href=\"https://gwhois.org/"
<< it.second->GetRemoteEndpoint ().address ().to_string () << "\" data-tooltip=\""
<< tr("Lookup address on gwhois.org") << "\"><span class=\"host\">"
<< it.second->GetRemoteEndpoint ().address ().to_string () << "</span></a>";
tmp_s6 << std::fixed << std::setprecision(0);
if (it.second->GetNumSentBytes () > 1024 * 1024) {
tmp_s6 << std::fixed << std::setprecision(1);
tmp_s6 << " <span class=\"sent\">" << (double) it.second->GetNumSentBytes () / 1024 / 1024 << "M</span>";
} else {
tmp_s6 << " <span class=\"sent\">" << (double) it.second->GetNumSentBytes () / 1024 << "K</span>";
}
tmp_s6 << " <span class=\"hide\">/</span>";
tmp_s6 << std::fixed << std::setprecision(0);
if (it.second->GetNumReceivedBytes () > 1024 * 1024) {
tmp_s6 << std::fixed << std::setprecision(1);
tmp_s6 << " <span class=\"recvd\">" << (double) it.second->GetNumReceivedBytes () / 1024 / 1024 << "M</span>";
} else {
tmp_s6 << " <span class=\"recvd\">" << (double) it.second->GetNumReceivedBytes () / 1024 << "K</span>";
}
tmp_s6 << "</span></div>\r\n" << std::endl;
cnt6++;
}
}
if (!tmp_s.str ().empty ())
{
s << "<div class=\"slide\"><input hidden type=\"checkbox\" class=\"toggle\" id=\"slide_" << boost::algorithm::to_lower_copy(name)
<< "\" />\r\n<label for=\"slide_" << boost::algorithm::to_lower_copy(name) << "\">" << name
<< " <span class=\"hide\">[</span><span class=\"badge\">" << cnt
<< "</span><span class=\"hide\">]</span></label>\r\n<div class=\"slidecontent list\">"
<< tmp_s.str () << "</div>\r\n</div>\r\n";
}
if (!tmp_s6.str ().empty ())
{
s << "<div class=\"slide\"><input hidden type=\"checkbox\" class=\"toggle\" id=\"slide_" << boost::algorithm::to_lower_copy(name) << "v6\" />\r\n"
<< "<label for=\"slide_" << boost::algorithm::to_lower_copy(name) << "v6\">" << name
<< "v6 <span class=\"hide\">[</span><span class=\"badge\">" << cnt6
<< "</span><span class=\"hide\">]</span></label>\r\n<div class=\"slidecontent list\">"
<< tmp_s6.str () << "</div>\r\n</div>\r\n";
}
}
void ShowTransports (std::stringstream& s)
{
s << "<tr class=\"sectiontitle\"><th colspan=\"2\"><span>" << tr("Transports") << "</span></th></tr>\r\n"
<< "<tr><td id=\"transports\" class=\"center nopadding\" colspan=\"2\">";
auto ntcp2Server = i2p::transport::transports.GetNTCP2Server ();
if (ntcp2Server)
{
auto sessions = ntcp2Server->GetNTCP2Sessions ();
if (!sessions.empty ())
ShowNTCPTransports (s, sessions, "NTCP2");
}
auto ssuServer = i2p::transport::transports.GetSSUServer ();
if (ssuServer)
{
auto sessions = ssuServer->GetSessions ();
if (!sessions.empty ())
{
s << "<div class=\"slide\"><input hidden type=\"checkbox\" class=\"toggle\" id=\"slide_ssu\" />\r\n"
<< "<label for=\"slide_ssu\">SSU <span class=\"hide\">[</span><span class=\"badge\">"
<< (int) sessions.size() << "</span><span class=\"hide\">]</span></label>\r\n"
<< "<div class=\"slidecontent list\">\r\n";
for (const auto& it: sessions)
{
s << "<div class=\"listitem SSU\">";
if (it.second->IsOutgoing ())
s << "<span class=\"arrowup\">&#8657;</span>";
else
s << "<span class=\"arrowdown\">&#8659;</span>";
s << " <span class=\"chain\">";
auto endpoint = it.second->GetRemoteEndpoint ();
// s << " <span class=\"host\">" << endpoint.address ().to_string () << ":" << endpoint.port () << "</span>";
s << " <a target=\"_blank\" href=\"https://gwhois.org/"
<< endpoint.address ().to_string () << "\" data-tooltip=\""
<< tr("Lookup address on gwhois.org") << "\"><span class=\"host\">"
<< endpoint.address ().to_string () << ":" << endpoint.port () << "</span></a>";
s << std::fixed << std::setprecision(0);
if (it.second->GetNumSentBytes () > 1024 * 1024) {
s << std::fixed << std::setprecision(1);
s << " <span class=\"sent\">" << (double) it.second->GetNumSentBytes () / 1024 / 1024 << "M</span>";
} else {
s << " <span class=\"sent\">" << (double) it.second->GetNumSentBytes () / 1024 << "K</span>";
}
s << " <span class=\"hide\">/</span>";
s << std::fixed << std::setprecision(0);
if (it.second->GetNumReceivedBytes () > 1024 * 1024) {
s << std::fixed << std::setprecision(1);
s << " <span class=\"recvd\">" << (double) it.second->GetNumReceivedBytes () / 1024 / 1024 << "M</span>";
} else {
s << " <span class=\"recvd\">" << (double) it.second->GetNumReceivedBytes () / 1024 << "K</span>";
}
if (it.second->GetRelayTag ())
s << " <span class=\"itag\" data-tooltip=\"itag\">" << it.second->GetRelayTag () << "</span>";
s << "</span></div>\r\n" << std::endl;
}
s << "</div>\r\n</div>\r\n";
}
auto sessions6 = ssuServer->GetSessionsV6 ();
if (!sessions6.empty ())
{
s << "<div class=\"slide\">\r\n<input hidden type=\"checkbox\" class=\"toggle\" id=\"slide_ssuv6\" />\r\n"
<< "<label for=\"slide_ssuv6\">SSUv6 <span class=\"hide\">[</span><span class=\"badge\">"
<< (int) sessions6.size() << "</span><span class=\"hide\">]</span></label>\r\n"
<< "<div class=\"slidecontent list\">\r\n";
for (const auto& it: sessions6)
{
s << "<div class=\"listitem SSU\">";
if (it.second->IsOutgoing ())
s << "<span class=\"arrowup\">&#8657;</span>";
else
s << "<span class=\"arrowdown\">&#8659;</span>";
s << " <span class=\"chain\">";
auto endpoint = it.second->GetRemoteEndpoint ();
s << " <span class=\"host\">" << endpoint.address ().to_string () << ":" << endpoint.port () << "</span>";
s << std::fixed << std::setprecision(0);
if (it.second->GetNumSentBytes () > 1024 * 1024) {
s << std::fixed << std::setprecision(1);
s << " <span class=\"sent\">" << (double) it.second->GetNumSentBytes () / 1024 / 1024 << "M</span>";
} else {
s << " <span class=\"sent\">" << (double) it.second->GetNumSentBytes () / 1024 << "K</span>";
}
s << " <span class=\"hide\">/</span>";
s << std::fixed << std::setprecision(0);
if (it.second->GetNumReceivedBytes () > 1024 * 1024) {
s << std::fixed << std::setprecision(1);
s << " <span class=\"recvd\">" << (double) it.second->GetNumReceivedBytes () / 1024 / 1024 << "M</span>";
} else {
s << " <span class=\"recvd\">" << (double) it.second->GetNumReceivedBytes () / 1024 << "K</span>";
}
if (it.second->GetRelayTag ())
s << " <span class=\"itag\" data-tooltip=\"itag\">" << it.second->GetRelayTag () << "</span>";
s << "</span>\r\n</div>\r\n" << std::endl;
}
s << "</div>\r\n</div>\r\n</td></tr>\r\n";
}
}
}
void ShowSAMSessions (std::stringstream& s)
{
std::string webroot; i2p::config::GetOption("http.webroot", webroot);
auto sam = i2p::client::context.GetSAMBridge ();
if (!sam)
{
ShowError(s, tr("SAM disabled"));
return;
}
if (sam->GetSessions ().size ())
{
s << "<tr class=\"sectiontitle\"><th colspan=\"2\"><span>" << tr("SAM sessions")
<< "</span></th><tr>\r\n<tr><td class=\"center nopadding\">\r\n<div class=\"list\">\r\n";
for (auto& it: sam->GetSessions ())
{
auto& name = it.second->GetLocalDestination ()->GetNickname ();
s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_SAM_SESSION << "&sam_id=" << it.first << "\">";
s << name << " (" << it.first << ")</a></div>\r\n" << std::endl;
}
s << "</div>\r\n</td></tr>\r\n";
}
else
s << "<tr><th colspan=\"2\">" << tr("No active SAM sessions") << "</th></tr>\r\n";
}
void ShowSAMSession (std::stringstream& s, const std::string& id)
{
auto sam = i2p::client::context.GetSAMBridge ();
if (!sam) {
ShowError(s, tr("SAM disabled"));
return;
}
auto session = sam->FindSession (id);
if (!session) {
ShowError(s, tr("SAM session not found"));
return;
}
std::string webroot; i2p::config::GetOption("http.webroot", webroot);
s << "<tr><th colspan=\"2\">" << tr("SAM Session") << "</th><tr>\r\n<tr><td class=\"center nopadding\">\r\n<div class=\"list\">\r\n";
auto& ident = session->GetLocalDestination ()->GetIdentHash();
s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "</a></div>\r\n";
s << "<br>\r\n";
s << "<tr><th colspan=\"2\">" << tr("Streams") << "</th><tr>\r\n<div class=\"list\">\r\n";
for (const auto& it: sam->ListSockets(id))
{
s << "<div class=\"listitem\">";
switch (it->GetSocketType ())
{
case i2p::client::eSAMSocketTypeSession : s << "session"; break;
case i2p::client::eSAMSocketTypeStream : s << "stream"; break;
case i2p::client::eSAMSocketTypeAcceptor : s << "acceptor"; break;
case i2p::client::eSAMSocketTypeForward : s << "forward"; break;
default: s << "unknown"; break;
}
s << " [" << it->GetSocket ().remote_endpoint() << "]";
s << "</div>\r\n";
}
s << "</div></td></tr>\r\n";
}
void ShowI2PTunnels (std::stringstream& s)
{
std::string webroot; i2p::config::GetOption("http.webroot", webroot);
s << "<tr class=\"sectiontitle\"><th colspan=\"4\"><span>" << tr("Service Tunnels") << "</span></th></tr>";
s << "<tr><td class=\"center nopadding i2ptunnels\" colspan=\"4\">\r\n";
s << "<div class=\"slide\">\r\n<input hidden type=\"checkbox\" class=\"toggle\" id=\"slide_client_tunnels\" />\r\n"
<< "<label for=\"slide_client_tunnels\">" << tr("Client Tunnels") << " <span class=\"hide\">[</span><span class=\"badge\">"
<< "in / out" << "</span><span class=\"hide\">]</span></label>\r\n";
s << "<div id=\"client_tunnels\" class=\"slidecontent list\">\r\n";
s << "<div class=\"list\">\r\n";
for (auto& it: i2p::client::context.GetClientTunnels ())
{
auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
s << it.second->GetName () << "</a> <span class=\"arrowleft\">&#8656;</span> <span class=\"b32\">";
s << i2p::client::context.GetAddressBook ().ToAddress(ident);
s << "</span></div>\r\n"<< std::endl;
}
auto httpProxy = i2p::client::context.GetHttpProxy ();
if (httpProxy)
{
auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash();
s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
s << "HTTP " << tr("Proxy") << "</a> <span class=\"arrowleft\">&#8656;</span> <span class=\"b32\">";
s << i2p::client::context.GetAddressBook ().ToAddress(ident);
s << "</span></div>\r\n"<< std::endl;
}
auto socksProxy = i2p::client::context.GetSocksProxy ();
if (socksProxy)
{
auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash();
s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
s << "SOCKS " << tr("Proxy") << "</a> <span class=\"arrowleft\">&#8656;</span> <span class=\"b32\">";
s << i2p::client::context.GetAddressBook ().ToAddress(ident);
s << "</span></div>\r\n" << std::endl;
}
s << "</div>\r\n</div>\r\n</div>\r\n";
auto& serverTunnels = i2p::client::context.GetServerTunnels ();
if (!serverTunnels.empty ()) {
s << "\r\n</td></tr>\r\n<tr><td class=\"center nopadding i2ptunnels\" colspan=\"4\">\r\n";
s << "<div class=\"slide\">\r\n<input hidden type=\"checkbox\" class=\"toggle\" id=\"slide_server_tunnels\" />\r\n"
<< "<label for=\"slide_server_tunnels\">" << tr("Server Tunnels") << " <span class=\"hide\">[</span><span class=\"badge\">"
<< "in / out" << "</span><span class=\"hide\">]</span></label>\r\n";
s << "<div id=\"server_tunnels\" class=\"slidecontent list\">\r\n";
s << "<div class=\"list\">\r\n";
for (auto& it: serverTunnels)
{
auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
s << it.second->GetName () << "</a> <span class=\"arrowright\">&#8658;</span> <span class=\"b32\">";
s << i2p::client::context.GetAddressBook ().ToAddress(ident);
s << ":" << it.second->GetLocalPort ();
s << "</span></div>\r\n" << std::endl;
}
s << "</div>\r\n</div>\r\n</div>\r\n</td></tr>\r\n";
}
auto& clientForwards = i2p::client::context.GetClientForwards ();
if (!clientForwards.empty ())
{
s << "\r\n</td></tr>\r\n<tr><td class=\"center nopadding i2ptunnels\" colspan=\"4\">\r\n";
s << "<div class=\"slide\">\r\n<input hidden type=\"checkbox\" class=\"toggle\" id=\"slide_client_forwards\" />\r\n"
<< "<label for=\"slide_client_forwards\">" << tr("Client Forwards") << " <span class=\"hide\">[</span><span class=\"badge\">"
<< "in / out" << "</span><span class=\"hide\">]</span></label>\r\n";
s << "<div id=\"client_forwards\" class=\"slidecontent list\">\r\n";
s << "<div class=\"list\">\r\n";
for (auto& it: clientForwards)
{
auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
s << it.second->GetName () << "</a> <span class=\"arrowleft\">&#8656;</span> <span class=\"b32\">";
s << i2p::client::context.GetAddressBook ().ToAddress(ident);
s << "</span></div>\r\n"<< std::endl;
}
s << "</div>\r\n</div>\r\n</div>\r\n</td></tr>\r\n";
}
auto& serverForwards = i2p::client::context.GetServerForwards ();
if (!serverForwards.empty ())
{
s << "\r\n</td></tr>\r\n<tr><td class=\"center nopadding i2ptunnels\" colspan=\"4\">\r\n";
s << "<div class=\"slide\">\r\n<input hidden type=\"checkbox\" class=\"toggle\" id=\"slide_server_forwards\" />\r\n"
<< "<label for=\"slide_server_forwards\">" << tr("Server Forwards") << " <span class=\"hide\">[</span><span class=\"badge\">"
<< "in / out" << "</span><span class=\"hide\">]</span></label>\r\n";
s << "<div id=\"server_forwards\" class=\"slidecontent list\">\r\n";
s << "<div class=\"list\">\r\n";
for (auto& it: serverForwards)
{
auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
s << it.second->GetName () << "</a> <span class=\"arrowleft\">&#8656;</span> <span class=\"b32\">";
s << i2p::client::context.GetAddressBook ().ToAddress(ident);
s << "</span></div>\r\n"<< std::endl;
}
s << "</div>\r\n</div>\r\n</div>\r\n</td></tr>\r\n";
}
// s << "</div></table>\r\n";
}
HTTPConnection::HTTPConnection (std::string hostname, std::shared_ptr<boost::asio::ip::tcp::socket> socket):
m_Socket (socket), m_BufferLen (0), expected_host(hostname)
{
/* cache options */
i2p::config::GetOption("http.auth", needAuth);
i2p::config::GetOption("http.user", user);
i2p::config::GetOption("http.pass", pass);
}
void HTTPConnection::Receive ()
{
m_Socket->async_read_some (boost::asio::buffer (m_Buffer, HTTP_CONNECTION_BUFFER_SIZE),
std::bind(&HTTPConnection::HandleReceive, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
}
void HTTPConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode) {
if (ecode != boost::asio::error::operation_aborted)
Terminate (ecode);
return;
}
m_Buffer[bytes_transferred] = '\0';
m_BufferLen = bytes_transferred;
RunRequest();
Receive ();
}
void HTTPConnection::RunRequest ()
{
HTTPReq request;
int ret = request.parse(m_Buffer);
if (ret < 0) {
m_Buffer[0] = '\0';
m_BufferLen = 0;
return; /* error */
}
if (ret == 0)
return; /* need more data */
HandleRequest (request);
}
void HTTPConnection::Terminate (const boost::system::error_code& ecode)
{
if (ecode == boost::asio::error::operation_aborted)
return;
boost::system::error_code ignored_ec;
m_Socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec);
m_Socket->close ();
}
bool HTTPConnection::CheckAuth (const HTTPReq & req)
{
/* method #1: http://user:pass@127.0.0.1:7070/ */
if (req.uri.find('@') != std::string::npos) {
URL url;
if (url.parse(req.uri) && url.user == user && url.pass == pass)
return true;
}
/* method #2: 'Authorization' header sent */
auto provided = req.GetHeader ("Authorization");
if (provided.length () > 0)
{
std::string expected = "Basic " + i2p::data::ToBase64Standard (user + ":" + pass);
if (expected == provided) return true;
}
LogPrint(eLogWarning, "HTTPServer: Auth failure from ", m_Socket->remote_endpoint().address ());
return false;
}
void HTTPConnection::HandleRequest (const HTTPReq & req)
{
std::stringstream s;
std::string content;
HTTPRes res;
LogPrint(eLogDebug, "HTTPServer: Request: ", req.uri);
if (needAuth && !CheckAuth(req)) {
res.code = 401;
res.add_header("WWW-Authenticate", "Basic realm=\"WebAdmin\"");
SendReply(res, content);
return;
}
bool strictheaders;
i2p::config::GetOption("http.strictheaders", strictheaders);
if (strictheaders)
{
std::string http_hostname;
i2p::config::GetOption("http.hostname", http_hostname);
std::string host = req.GetHeader("Host");
auto idx = host.find(':');
/* strip out port so it's just host */
if (idx != std::string::npos && idx > 0)
{
host = host.substr(0, idx);
}
if (!(host == expected_host || host == http_hostname))
{
/* deny request as it's from a non whitelisted hostname */
res.code = 403;
content = "host mismatch";
SendReply(res, content);
return;
}
}
// HTML head start
ShowPageHead (s);
if (req.uri.find("summary") != std::string::npos ||
req.uri.find("commands") != std::string::npos ||
(req.uri.find("local_destinations") != std::string::npos &&
req.uri.find("b32") == std::string::npos))
res.add_header("Refresh", "10");
if (req.uri.find("page=") != std::string::npos) {
HandlePage (req, res, s);
} else if (req.uri.find("cmd=") != std::string::npos) {
HandleCommand (req, res, s);
} else {
ShowStatus (s, true, i2p::http::OutputFormatEnum::forWebConsole);
res.add_header("Refresh", "5");
}
ShowPageTail (s);
res.code = 200;
content = s.str ();
SendReply (res, content);
}
std::map<uint32_t, uint32_t> HTTPConnection::m_Tokens;
uint32_t HTTPConnection::CreateToken ()
{
uint32_t token;
RAND_bytes ((uint8_t *)&token, 4);
token &= 0x7FFFFFFF; // clear first bit
auto ts = i2p::util::GetSecondsSinceEpoch ();
for (auto it = m_Tokens.begin (); it != m_Tokens.end (); )
{
if (ts > it->second + TOKEN_EXPIRATION_TIMEOUT)
it = m_Tokens.erase (it);
else
++it;
}
m_Tokens[token] = ts;
return token;
}
void HTTPConnection::HandlePage (const HTTPReq& req, HTTPRes& res, std::stringstream& s)
{
std::map<std::string, std::string> params;
std::string page("");
URL url;
url.parse(req.uri);
url.parse_query(params);
page = params["page"];
if (page == HTTP_PAGE_TRANSPORTS) {
ShowTransports (s);
} else if (page == HTTP_PAGE_TUNNEL_SUMMARY) {
ShowTunnelSummary (s);
/*
ShowTunnels (s);
ShowI2PTunnels (s);
ShowTransitTunnels (s);
*/
} else if (page == HTTP_PAGE_COMMANDS) {
uint32_t token = CreateToken ();
ShowCommands (s, token);
} else if (page == HTTP_PAGE_TRANSIT_TUNNELS) {
ShowTransitTunnels (s);
} else if (page == HTTP_PAGE_LOCAL_DESTINATIONS) {
ShowLocalDestinations (s);
} else if (page == HTTP_PAGE_LOCAL_DESTINATION) {
uint32_t token = CreateToken ();
ShowLocalDestination (s, params["b32"], token);
} else if (page == HTTP_PAGE_I2CP_LOCAL_DESTINATION) {
ShowI2CPLocalDestination (s, params["i2cp_id"]);
} else if (page == HTTP_PAGE_SAM_SESSIONS) {
ShowSAMSessions (s);
} else if (page == HTTP_PAGE_SAM_SESSION) {
ShowSAMSession (s, params["sam_id"]);
} else if (page == HTTP_PAGE_LOCAL_TUNNELS) {
ShowTunnels (s);
} else if (page == HTTP_PAGE_LEASESETS) {
ShowLeasesSets(s);
} else {
res.code = 400;
ShowError(s, tr("Unknown page") + ": " + page);
return;
}
}
void HTTPConnection::HandleCommand (const HTTPReq& req, HTTPRes& res, std::stringstream& s)
{
std::map<std::string, std::string> params;
URL url;
url.parse(req.uri);
url.parse_query(params);
std::string webroot; i2p::config::GetOption("http.webroot", webroot);
std::string redirect = "2; url=" + webroot + "?page=commands";
std::string token = params["token"];
if (token.empty () || m_Tokens.find (std::stoi (token)) == m_Tokens.end ())
{
ShowError(s, tr("Invalid token"));
return;
}
std::string cmd = params["cmd"];
if (cmd == HTTP_COMMAND_RUN_PEER_TEST)
i2p::transport::transports.PeerTest ();
else if (cmd == HTTP_COMMAND_RELOAD_CONFIG)
i2p::client::context.ReloadConfig ();
else if (cmd == HTTP_COMMAND_ENABLE_TRANSIT)
i2p::context.SetAcceptsTunnels (true);
else if (cmd == HTTP_COMMAND_DISABLE_TRANSIT)
i2p::context.SetAcceptsTunnels (false);
else if (cmd == HTTP_COMMAND_SHUTDOWN_START)
{
i2p::context.SetAcceptsTunnels (false);
#if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY))
Daemon.gracefulShutdownInterval = 10*60;
#elif defined(WIN32_APP)
i2p::win32::GracefulShutdown ();
#endif
}
else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL)
{
i2p::context.SetAcceptsTunnels (true);
#if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY))
Daemon.gracefulShutdownInterval = 0;
#elif defined(WIN32_APP)
i2p::win32::StopGracefulShutdown ();
#endif
}
else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW)
{
#ifndef WIN32_APP
Daemon.running = false;
#else
i2p::win32::StopWin32App ();
#endif
}
else if (cmd == HTTP_COMMAND_LOGLEVEL)
{
std::string level = params["level"];
SetLogLevel (level);
}
else if (cmd == HTTP_COMMAND_KILLSTREAM)
{
std::string b32 = params["b32"];
uint32_t streamID = std::stoul(params["streamID"], nullptr);
i2p::data::IdentHash ident;
ident.FromBase32 (b32);
auto dest = i2p::client::context.FindLocalDestination (ident);
if (streamID)
{
if (dest)
{
if(dest->DeleteStream (streamID))
s << "<tr class=\"toast\"><td class=\"notify center\" colspan=\2\">"
<< "<span class=\"container\"><span id=\"success\"></span><b>" << tr("SUCCESS") << "</b>:&nbsp;"
<< tr("Stream closed") << "</span></td></tr>\r\n";
else
s << "<tr class=\"toast\"><td class=\"notify error center\" colspan=\2\">"
<< "<span class=\"container\"><span id=\"warning\"></span>"
<< tr("ERROR") << "</b>:&nbsp;"
<< tr("Stream not found or already was closed") << "</span></td></tr>\r\n";
}
else
s << "<tr class=\"toast\"><td class=\"notify error center\" colspan=\2\">"
<< "<span class=\"container\"><span id=\"warning\"></span>"
<< tr("ERROR") << "</b>:&nbsp;"
<< tr("Destination not found") << "</span></td></tr>\r\n";
}
else
s << "<tr class=\"toast\"><td class=\"notify error center\" colspan=\2\">"
<< "<span class=\"container\"><span id=\"warning\"></span>" << tr("ERROR") << "</b>:&nbsp;"
<< tr("StreamID can't be null") << "</span></td></tr>\r\n";
redirect = "2; url=" + webroot + "?page=local_destination&b32=" + b32;
res.add_header("Refresh", redirect.c_str());
return;
}
else if (cmd == HTTP_COMMAND_LIMITTRANSIT)
{
uint32_t limit = std::stoul(params["limit"], nullptr);
if (limit > 0 && limit <= 65535)
SetMaxNumTransitTunnels (limit);
else {
s << "<tr class=\"toast\"><td class=\"notify error center\" colspan=\2\">"
<< "<span class=\"container\"><span id=\"warning\"></span>"
<< tr("ERROR") << "</b>:&nbsp;"
<< tr("Transit tunnels count must not exceed 65535") << "</span></td></tr>\r\n";
res.add_header("Refresh", redirect.c_str());
return;
}
}
else if (cmd == HTTP_COMMAND_GET_REG_STRING)
{
std::string b32 = params["b32"];
std::string name = i2p::http::UrlDecode(params["name"]);
i2p::data::IdentHash ident;
ident.FromBase32 (b32);
auto dest = i2p::client::context.FindLocalDestination (ident);
if (dest)
{
std::size_t pos;
pos = name.find (".i2p");
if (pos == (name.length () - 4))
{
pos = name.find (".b32.i2p");
if (pos == std::string::npos)
{
auto signatureLen = dest->GetIdentity ()->GetSignatureLen ();
uint8_t * signature = new uint8_t[signatureLen];
char * sig = new char[signatureLen*2];
std::stringstream out;
out << name << "=" << dest->GetIdentity ()->ToBase64 ();
dest->Sign ((uint8_t *)out.str ().c_str (), out.str ().length (), signature);
auto len = i2p::data::ByteStreamToBase64 (signature, signatureLen, sig, signatureLen*2);
sig[len] = 0;
out << "#!sig=" << sig;
s << "<tr class=\"toast\"><td class=\"notify center\" colspan=\"2\"><span class=\"container\">"
<< "<span id=\"success\"></span><b>" << tr("SUCCESS")
<< "</b>:<br>\r\n<form action=\"http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/add\""
<< " method=\"post\" rel=\"noreferrer\" target=\"_blank\">\r\n"
<< "<textarea readonly name=\"record\" cols=\"80\" rows=\"10\">" << out.str () << "</textarea>\r\n<br>\r\n<br>\r\n"
<< "<b>" << tr("Register at reg.i2p") << ":</b>\r\n<br>\r\n"
<< "<b>" << tr("Description") << ":</b>\r\n<input type=\"text\" maxlength=\"64\" name=\"desc\" placeholder=\""
<< tr("Short description of domain") << "\">\r\n"
<< "<input type=\"submit\" value=\"" << tr("Submit") << "\">\r\n"
<< "</form></span></td></tr>\r\n";
delete[] signature;
delete[] sig;
}
else
s << "<tr class=\"toast\"><td class=\"notify error center\" colspan=\"2\"><span class=\"container\"><b>"
<< tr("ERROR") << "</b>:&nbsp;"
<< tr("Domain can't end with .b32.i2p") << "</span></td></tr>\r\n";
}
else
s << "<tr class=\"toast\"><td class=\"notify error center\" colspan=\"2\"><span class=\"container\">"
<< tr("ERROR") << "</b>:&nbsp;"
<< tr("Domain must end with .i2p") << "</span></td></tr>\r\n";
}
else
s << "<tr class=\"toast\"><td class=\"notify error center\" colspan=\"2\"><span class=\"container\">"
<< tr("ERROR") << "</b>:&nbsp;"
<< tr("No such destination found") << "</span></td></tr>\r\n";
// s << "<a href=\"" << webroot << "?page=local_destination&b32=" << b32 << "\">" << tr("Return to destination page") << "</a>\r\n";
return;
}
else if (cmd == HTTP_COMMAND_SETLANGUAGE)
{
std::string lang = params["lang"];
std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage();
if (currLang.compare(lang) != 0)
i2p::i18n::SetLanguage(lang);
}
else if (cmd == HTTP_COMMAND_RELOAD_CSS)
{
std::string styleFile = i2p::fs::DataDirPath ("webconsole/style.css");
if (i2p::fs::Exists(styleFile))
LoadExtCSS();
else
ShowError(s, tr("No CSS file found on disk!"));
}
else
{
res.code = 400;
ShowError(s, tr("Unknown command") + ": " + cmd);
return;
}
s << "<tr class=\"toast\"><td class=\"notify center\" colspan=\"2\"><span class=\"container\">"
<< "<span id=\"success\"></span>";
if (cmd == HTTP_COMMAND_SHUTDOWN_NOW)
s << tr("Immediate router shutdown initiated");
else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL)
s << tr("Router shutdown cancelled");
else if (cmd == HTTP_COMMAND_RELOAD_CSS) {
s << tr("Console CSS stylesheet reloaded");
} else if (cmd == HTTP_COMMAND_LIMITTRANSIT)
s << tr("Maximum transit tunnels configured for session");
else if (cmd == HTTP_COMMAND_ENABLE_TRANSIT)
s << tr("Transit tunnels enabled for session");
else if (cmd == HTTP_COMMAND_DISABLE_TRANSIT)
s << tr("Transit tunnels disabled for session");
else if (cmd == HTTP_COMMAND_SETLANGUAGE)
s << tr("Console language updated");
else if (cmd == HTTP_COMMAND_LOGLEVEL)
s << tr("Log level updated for session");
else
s << "<b>" << tr("SUCCESS") << "</b>:&nbsp;" << tr("Command accepted");
s << "</span></td></tr>\r\n";
res.add_header("Refresh", redirect.c_str());
}
void HTTPConnection::SendReply (HTTPRes& reply, std::string& content)
{
reply.add_header("X-Frame-Options", "SAMEORIGIN");
reply.add_header("X-Content-Type-Options", "nosniff");
reply.add_header("X-XSS-Protection", "1; mode=block");
reply.add_header("Content-Type", "text/html");
reply.add_header("Server", "i2pd " VERSION " webconsole" );
reply.body = content;
m_SendBuffer = reply.to_string();
boost::asio::async_write (*m_Socket, boost::asio::buffer(m_SendBuffer),
std::bind (&HTTPConnection::Terminate, shared_from_this (), std::placeholders::_1));
}
HTTPServer::HTTPServer (const std::string& address, int port):
m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service),
m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::address::from_string(address), port)),
m_Hostname(address)
{
}
HTTPServer::~HTTPServer ()
{
Stop ();
}
void HTTPServer::Start ()
{
bool needAuth; i2p::config::GetOption("http.auth", needAuth);
std::string user; i2p::config::GetOption("http.user", user);
std::string pass; i2p::config::GetOption("http.pass", pass);
/* generate pass if needed */
if (needAuth && pass == "") {
uint8_t random[16];
char alnum[] = "0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
pass.resize(sizeof(random));
RAND_bytes(random, sizeof(random));
for (size_t i = 0; i < sizeof(random); i++) {
pass[i] = alnum[random[i] % (sizeof(alnum) - 1)];
}
i2p::config::SetOption("http.pass", pass);
LogPrint(eLogInfo, "HTTPServer: Password set to ", pass);
}
m_IsRunning = true;
m_Thread.reset (new std::thread (std::bind (&HTTPServer::Run, this)));
m_Acceptor.listen ();
Accept ();
LoadExtCSS();
}
void HTTPServer::Stop ()
{
m_IsRunning = false;
m_Acceptor.close();
m_Service.stop ();
if (m_Thread)
{
m_Thread->join ();
m_Thread = nullptr;
}
}
void HTTPServer::Run ()
{
i2p::util::SetThreadName("Webconsole");
while (m_IsRunning)
{
try
{
m_Service.run ();
}
catch (std::exception& ex)
{
LogPrint (eLogError, "HTTPServer: runtime exception: ", ex.what ());
}
}
}
void HTTPServer::Accept ()
{
auto newSocket = std::make_shared<boost::asio::ip::tcp::socket> (m_Service);
m_Acceptor.async_accept (*newSocket, std::bind (&HTTPServer::HandleAccept, this,
std::placeholders::_1, newSocket));
}
void HTTPServer::HandleAccept(const boost::system::error_code& ecode,
std::shared_ptr<boost::asio::ip::tcp::socket> newSocket)
{
if (ecode)
{
if(newSocket) newSocket->close();
LogPrint(eLogError, "HTTP Server: Error handling accept ", ecode.message());
if(ecode != boost::asio::error::operation_aborted)
Accept();
return;
}
CreateConnection(newSocket);
Accept ();
}
void HTTPServer::CreateConnection(std::shared_ptr<boost::asio::ip::tcp::socket> newSocket)
{
auto conn = std::make_shared<HTTPConnection> (m_Hostname, newSocket);
conn->Receive ();
}
} // http
} // i2p