mirror of
https://github.com/PurpleI2P/i2pd.git
synced 2025-04-29 04:07:49 +02:00
2375 lines
139 KiB
C++
2375 lines
139 KiB
C++
/*
|
||
* 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 4px 0 8px;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 << " " << tr(/* tr: Byte */ "B");
|
||
} else if (numKBytes < 1024) {
|
||
s << numKBytes << " " << tr(/* tr: Kibibit */ "K");
|
||
} else if (numKBytes < 1024 * 1024) {
|
||
s << std::fixed << std::setprecision(1);
|
||
s << numKBytes / 1024 << " " << tr(/* tr: Mebibit */ "M");
|
||
} else if (numKBytes < 1024 * 1024 * 1024) {
|
||
s << std::fixed << std::setprecision(2);
|
||
s << numKBytes / 1024 / 1024 << " " << tr(/* tr: Gibibit */ "G");
|
||
} else {
|
||
s << numKBytes / 1024 / 1024 / 1024 << " " << 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 << "&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 << "&token=" << token
|
||
<< "\" data-tooltip=\"" << tr("Decline transit tunnels")
|
||
<< "\">No transit</a>";
|
||
} else {
|
||
s << "<a id=\"enabletransit\" href=\"" << webroot << "?cmd="
|
||
<< HTTP_COMMAND_ENABLE_TRANSIT << "&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> " << 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 << "…</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 << "…</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 << " " << 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 << " " << 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 << " " << 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\">⇔</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") << "\">--- </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") << "\">--- </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) + "…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\">✕</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") << "\">--- </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") << "\">--- </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") << "\">--- </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") << "\">--- </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\">⇑</span>";
|
||
else
|
||
tmp_s << "<span class=\"arrowdown\">⇓</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\">⇑</span>";
|
||
else
|
||
tmp_s6 << "<span class=\"arrowdown\">⇓</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\">⇑</span>";
|
||
else
|
||
s << "<span class=\"arrowdown\">⇓</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\">⇑</span>";
|
||
else
|
||
s << "<span class=\"arrowdown\">⇓</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\">⇐</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\">⇐</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\">⇐</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\">⇒</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\">⇐</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\">⇐</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>: "
|
||
<< 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>: "
|
||
<< 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>: "
|
||
<< 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>: "
|
||
<< 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>: "
|
||
<< 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>: "
|
||
<< 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>: "
|
||
<< 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>: "
|
||
<< 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>: " << 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
|