From 97e06f3f2b9a8d3d1bda70a3b6d739cf28ae8ad2 Mon Sep 17 00:00:00 2001 From: EinMByte Date: Sun, 30 Aug 2015 19:00:26 +0200 Subject: [PATCH 01/31] Add benchmarking code and optimization option. --- .gitignore | 2 + CMakeLists.txt | 9 +++++ benchmark/CMakeLists.txt | 14 +++++++ benchmark/main.cpp | 83 ++++++++++++++++++++++++++++++++++++++++ build/BUILD_NOTES.md | 3 ++ 5 files changed, 111 insertions(+) create mode 100644 benchmark/CMakeLists.txt create mode 100644 benchmark/main.cpp diff --git a/.gitignore b/.gitignore index 6525ba45..9b4bcdf2 100644 --- a/.gitignore +++ b/.gitignore @@ -13,8 +13,10 @@ build/CMakeFiles/* build/tests build/client build/core +build/benchmark build/i2pd build/i2pd-tests +build/i2pd-benchmark *.cmake *.a *.o diff --git a/CMakeLists.txt b/CMakeLists.txt index e03b0f9b..d0312076 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,8 @@ option(WITH_BINARY "Build binary" ON) option(WITH_STATIC "Static build" OFF) option(WITH_UPNP "Include support for UPnP client" OFF) option(WITH_TESTS "Build unit tests" OFF) +option(WITH_BENCHMARK "Build benchmarking code" OFF) +option(WITH_OPTIMIZE "Optmizeation flags" OFF) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/build/cmake_modules") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) @@ -80,6 +82,9 @@ if(WITH_AESNI) add_definitions( "-maes -DAESNI") endif() +if(WITH_OPTIMIZE AND (NOT MSVC)) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3") +endif() # Libraries # TODO: once CMake 3.1+ becomes mainstream, see e.g. @@ -163,6 +168,8 @@ message(STATUS " BINARY : ${WITH_BINARY}") message(STATUS " STATIC BUILD : ${WITH_STATIC}") message(STATUS " UPnP : ${WITH_UPNP}") message(STATUS " TESTS : ${WITH_TESTS}") +message(STATUS " BENCHMARKING : ${WITH_BENCHMARK}") +message(STATUS " OPTIMIZATION : ${WITH_OPTIMIZE}") message(STATUS "---------------------------------------") # Handle paths nicely @@ -171,6 +178,8 @@ include(GNUInstallDirs) set(CORE_NAME "${PROJECT_NAME}-core") set(CLIENT_NAME "${PROJECT_NAME}-client") set(TESTS_NAME "${PROJECT_NAME}-tests") +set(BENCHMARK_NAME "${PROJECT_NAME}-benchmark") add_subdirectory(core) add_subdirectory(client) add_subdirectory(tests) +add_subdirectory(benchmark) diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt new file mode 100644 index 00000000..25d4b12c --- /dev/null +++ b/benchmark/CMakeLists.txt @@ -0,0 +1,14 @@ +set(BENCHMARK_SRC + "main.cpp" +) + +if(WITH_BENCHMARK) + add_executable(${BENCHMARK_NAME} ${BENCHMARK_SRC}) + target_link_libraries( + ${BENCHMARK_NAME} ${CORE_NAME} ${DL_LIB} ${Boost_LIBRARIES} ${CRYPTO++_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} + ) + install(TARGETS + ${BENCHMARK_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ) +endif() diff --git a/benchmark/main.cpp b/benchmark/main.cpp new file mode 100644 index 00000000..387c505d --- /dev/null +++ b/benchmark/main.cpp @@ -0,0 +1,83 @@ +#include "crypto/Signature.h" +#include +#include +#include +#include + +typedef std::function KeyGenerator; + +template +void benchmark(std::size_t count, std::size_t public_key_size, std::size_t private_key_size, + std::size_t signature_size, KeyGenerator generator) +{ + typedef std::chrono::time_point TimePoint; + CryptoPP::AutoSeededRandomPool rng; + + uint8_t private_key[private_key_size] = {}; + uint8_t public_key[public_key_size] = {}; + + generator(rng, private_key, public_key); + + Verifier verifier(public_key); + Signer signer(private_key); + + uint8_t message[512] = {}; + uint8_t output[signature_size] = {}; + + std::chrono::nanoseconds sign_duration(0); + std::chrono::nanoseconds verify_duration(0); + + for(std::size_t i = 0; i < count; ++i) { + rng.GenerateBlock(message, 512); + TimePoint begin1 = std::chrono::high_resolution_clock::now(); + signer.Sign(rng, message, 512, output); + TimePoint end1 = std::chrono::high_resolution_clock::now(); + + sign_duration += std::chrono::duration_cast(end1 - begin1); + + TimePoint begin2 = std::chrono::high_resolution_clock::now(); + verifier.Verify(message, 512, output); + TimePoint end2 = std::chrono::high_resolution_clock::now(); + + verify_duration += std::chrono::duration_cast(end2 - begin2); + } + std::cout << "Conducted " << count << " experiments." << std::endl; + std::cout << "Total sign time: " << std::chrono::duration_cast(sign_duration).count() << std::endl; + std::cout << "Total verify time: " << std::chrono::duration_cast(verify_duration).count() << std::endl; +} + + +int main() +{ + using namespace i2p::crypto; + std::cout << "--------DSA---------" << std::endl; + benchmark( + 1000, DSA_PUBLIC_KEY_LENGTH, + DSA_PRIVATE_KEY_LENGTH, DSA_SIGNATURE_LENGTH, + &CreateDSARandomKeys + ); + std::cout << "-----ECDSAP256------" << std::endl; + benchmark( + 1000, ECDSAP256_KEY_LENGTH, + ECDSAP256_KEY_LENGTH, 64, + &CreateECDSAP256RandomKeys + ); + std::cout << "-----ECDSAP384------" << std::endl; + benchmark( + 1000, ECDSAP384_KEY_LENGTH, + ECDSAP384_KEY_LENGTH, 64, + &CreateECDSAP384RandomKeys + ); + std::cout << "-----ECDSAP521------" << std::endl; + benchmark( + 1000, ECDSAP521_KEY_LENGTH, + ECDSAP521_KEY_LENGTH, 64, + &CreateECDSAP521RandomKeys + ); + std::cout << "-----EDDSA25519-----" << std::endl; + benchmark( + 1000, EDDSA25519_PUBLIC_KEY_LENGTH, + EDDSA25519_PRIVATE_KEY_LENGTH, 64, + &CreateEDDSARandomKeys + ); +} diff --git a/build/BUILD_NOTES.md b/build/BUILD_NOTES.md index c688a839..874f27c9 100644 --- a/build/BUILD_NOTES.md +++ b/build/BUILD_NOTES.md @@ -6,6 +6,9 @@ Available cmake options: * CMAKE_BUILD_TYPE -- build profile (Debug/Release) * WITH_AESNI -- AES-NI support (ON/OFF) * WITH_HARDENING -- enable hardening features (ON/OFF) (gcc only) +* WITH_TESTS -- build tests (ON/OFF) +* WITH_BENCHMARK -- build bechmarking code (ON/OFF) +* WITH_OPTIMIZE -- enable optimization flags (ON/OFF) Debian ------ From 0b1d6b3c01117d7e64eb93271e9f17a5baa633ad Mon Sep 17 00:00:00 2001 From: EinMByte Date: Sun, 30 Aug 2015 19:07:26 +0200 Subject: [PATCH 02/31] Fix typos. --- CMakeLists.txt | 2 +- build/BUILD_NOTES.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d0312076..a1f785db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ option(WITH_STATIC "Static build" OFF) option(WITH_UPNP "Include support for UPnP client" OFF) option(WITH_TESTS "Build unit tests" OFF) option(WITH_BENCHMARK "Build benchmarking code" OFF) -option(WITH_OPTIMIZE "Optmizeation flags" OFF) +option(WITH_OPTIMIZE "Optimization flags" OFF) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/build/cmake_modules") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) diff --git a/build/BUILD_NOTES.md b/build/BUILD_NOTES.md index 874f27c9..0ddcd4f7 100644 --- a/build/BUILD_NOTES.md +++ b/build/BUILD_NOTES.md @@ -8,7 +8,7 @@ Available cmake options: * WITH_HARDENING -- enable hardening features (ON/OFF) (gcc only) * WITH_TESTS -- build tests (ON/OFF) * WITH_BENCHMARK -- build bechmarking code (ON/OFF) -* WITH_OPTIMIZE -- enable optimization flags (ON/OFF) +* WITH_OPTIMIZE -- enable optimization flags (ON/OFF) (not for MSVC) Debian ------ @@ -28,7 +28,7 @@ Optional packages: FreeBSD ------- -Branch 9.X has gcc v4.2, that knows nothing about required c++11 standart. +Branch 9.X has gcc v4.2, that knows nothing about required c++11 standard. Required ports: From 6857dcb911752a5462637004686659c9a8a15928 Mon Sep 17 00:00:00 2001 From: EinMByte Date: Sat, 5 Sep 2015 21:02:44 +0200 Subject: [PATCH 03/31] Make cmake copy webui files to the i2pd data folder. --- CMakeLists.txt | 19 +++++++++++++++++++ README.md | 8 ++++++-- build/BUILD_NOTES.md | 1 + core/util/util.cpp | 12 +++++++----- webui/index.html | 1 + 5 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 webui/index.html diff --git a/CMakeLists.txt b/CMakeLists.txt index a1f785db..183c782f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ option(WITH_UPNP "Include support for UPnP client" OFF) option(WITH_TESTS "Build unit tests" OFF) option(WITH_BENCHMARK "Build benchmarking code" OFF) option(WITH_OPTIMIZE "Optimization flags" OFF) +option(I2PD_DATA_PATH "The path to the i2pd data folder") set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/build/cmake_modules") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) @@ -153,6 +154,18 @@ include_directories( "core/" ) +if(I2PD_DATA_PATH) + set(I2PD_DATA_DIR ${I2PD_DATA_PATH}) + # Using custom path, make sure the code knows about this + add_definitions(-DI2PD_CUSTOM_DATA_PATH="${I2PD_DATA_PATH}") +elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows") + set(I2PD_DATA_DIR "$ENV{APPDATA}\i2pd") +elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(I2PD_DATA_DIR "$ENV{HOME}/Library/Application Support/i2pd") +else() + set(I2PD_DATA_DIR "$ENV{HOME}/.i2pd") +endif() + # Show summary message(STATUS "---------------------------------------") message(STATUS "Build type : ${CMAKE_BUILD_TYPE}") @@ -160,6 +173,7 @@ message(STATUS "Compiler vendor : ${CMAKE_CXX_COMPILER_ID}") message(STATUS "Compiler version : ${CMAKE_CXX_COMPILER_VERSION}") message(STATUS "Compiler path : ${CMAKE_CXX_COMPILER}") message(STATUS "Install prefix: : ${CMAKE_INSTALL_PREFIX}") +message(STATUS "I2PD data directory: ${I2PD_DATA_DIR}") message(STATUS "Options:") message(STATUS " AESNI : ${WITH_AESNI}") message(STATUS " HARDENING : ${WITH_HARDENING}") @@ -183,3 +197,8 @@ add_subdirectory(core) add_subdirectory(client) add_subdirectory(tests) add_subdirectory(benchmark) + +if(WITH_BINARY) + file(MAKE_DIRECTORY "${I2PD_DATA_DIR}/webui") + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/webui" DESTINATION "${I2PD_DATA_DIR}") +endif() diff --git a/README.md b/README.md index a50fe65d..46e82d99 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,8 @@ Build Statuses - Microsoft VC13 - To be added -Testing -------- +Building from source +-------------------- First, build it. @@ -58,6 +58,10 @@ By default, the web console is located at http://localhost:7070/. For a list of cmake options, see build/BUILD_NOTES.md +Note that cmake will automatically create a directory for storing configuration files. +To change the location of this directory, you must set the I2PD_DATA_DIR flag. +This does not apply to core-only builds. + Building Unit Tests ------------------- diff --git a/build/BUILD_NOTES.md b/build/BUILD_NOTES.md index 0ddcd4f7..d4d289dd 100644 --- a/build/BUILD_NOTES.md +++ b/build/BUILD_NOTES.md @@ -9,6 +9,7 @@ Available cmake options: * WITH_TESTS -- build tests (ON/OFF) * WITH_BENCHMARK -- build bechmarking code (ON/OFF) * WITH_OPTIMIZE -- enable optimization flags (ON/OFF) (not for MSVC) +* I2PD_DATA_DIR -- directory where i2pd will store data Debian ------ diff --git a/core/util/util.cpp b/core/util/util.cpp index d55f380b..cb53e786 100644 --- a/core/util/util.cpp +++ b/core/util/util.cpp @@ -195,23 +195,24 @@ namespace filesystem boost::filesystem::path GetDefaultDataDir() { + // Custom path, or default path: // Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd // Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd // Mac: ~/Library/Application Support/i2pd - // Unix: ~/.i2pd or /var/lib/i2pd is system=1 - + // Unix: ~/.i2pd +#ifdef I2PD_CUSTOM_DATA_PATH + return boost::filesystem::path(std::string(I2PD_CUSTOM_DATA_PATH)); +#else #ifdef WIN32 // Windows char localAppData[MAX_PATH]; SHGetFolderPath(NULL, CSIDL_APPDATA, 0, NULL, localAppData); return boost::filesystem::path(std::string(localAppData) + "\\" + appName); #else - if(i2p::util::config::GetArg("-service", 0)) // use system folder - return boost::filesystem::path(std::string ("/var/lib/") + appName); boost::filesystem::path pathRet; char* pszHome = getenv("HOME"); if(pszHome == NULL || strlen(pszHome) == 0) - pathRet = boost::filesystem::path("/"); + pathRet = boost::filesystem::path("/"); else pathRet = boost::filesystem::path(pszHome); #ifdef MAC_OSX @@ -223,6 +224,7 @@ namespace filesystem // Unix return pathRet / (std::string (".") + appName); #endif +#endif #endif } diff --git a/webui/index.html b/webui/index.html new file mode 100644 index 00000000..3d98ba3d --- /dev/null +++ b/webui/index.html @@ -0,0 +1 @@ +Nothing here yet. From e3b891de41fc40c5fbe64449dde588d76962056a Mon Sep 17 00:00:00 2001 From: EinMByte Date: Sun, 6 Sep 2015 16:15:46 +0200 Subject: [PATCH 04/31] Add util::GetWebuiDataDir(), HTTP parsing utilities and tests. --- core/CMakeLists.txt | 1 + core/util/HTTP.cpp | 60 +++++++++++++++++++++++++++++++++++++++++++++ core/util/HTTP.h | 42 +++++++++++++++++++++++++++++++ core/util/util.cpp | 5 ++++ core/util/util.h | 4 +++ tests/Utility.cpp | 26 ++++++++++++++++++++ 6 files changed, 138 insertions(+) create mode 100644 core/util/HTTP.cpp create mode 100644 core/util/HTTP.h diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index a9a51945..95a10d02 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -11,6 +11,7 @@ set(CORE_SRC "util/base64.cpp" "util/util.cpp" "util/Log.cpp" + "util/HTTP.cpp" "tunnel/TransitTunnel.cpp" "tunnel/Tunnel.cpp" "tunnel/TunnelGateway.cpp" diff --git a/core/util/HTTP.cpp b/core/util/HTTP.cpp new file mode 100644 index 00000000..2addeda3 --- /dev/null +++ b/core/util/HTTP.cpp @@ -0,0 +1,60 @@ +#include "HTTP.h" +#include +#include + +namespace i2p { +namespace util { +namespace http { + +void Request::parseRequestLine(const std::string& line) +{ + std::stringstream ss(line); + ss >> method; + ss >> uri; +} + +void Request::parseHeaderLine(const std::string& line) +{ + const std::size_t pos = line.find_first_of(':'); + headers[boost::trim_copy(line.substr(0, pos))] = boost::trim_copy(line.substr(pos + 1)); +} + +Request::Request(const std::string& data) +{ + std::stringstream ss(data); + std::string line; + std::getline(ss, line); + parseRequestLine(line); + + while(std::getline(ss, line)) + parseHeaderLine(line); +} + +std::string Request::getMethod() const +{ + return method; +} + +std::string Request::getUri() const +{ + return uri; +} + +std::string Request::getHost() const +{ + return host; +} + +int Request::getPort() const +{ + return port; +} + +std::string Request::getHeader(const std::string& name) const +{ + return headers.at(name); +} + +} +} +} diff --git a/core/util/HTTP.h b/core/util/HTTP.h new file mode 100644 index 00000000..ceb3d931 --- /dev/null +++ b/core/util/HTTP.h @@ -0,0 +1,42 @@ +#ifndef _HTTP_H__ +#define _HTTP_H__ + +#include +#include + +namespace i2p { +namespace util { +namespace http { + +class Request { + void parseRequestLine(const std::string& line); + void parseHeaderLine(const std::string& line); +public: + Request(const std::string& data); + + std::string getMethod() const; + + std::string getUri() const; + + std::string getHost() const; + + int getPort() const; + + /** + * @throw std::out_of_range if no such header exists + */ + std::string getHeader(const std::string& name) const; + +private: + std::string method; + std::string uri; + std::string host; + int port; + std::map headers; +}; + +} +} +} + +#endif // _HTTP_H__ diff --git a/core/util/util.cpp b/core/util/util.cpp index cb53e786..a576bd09 100644 --- a/core/util/util.cpp +++ b/core/util/util.cpp @@ -193,6 +193,11 @@ namespace filesystem return pathTunnelsConfigFile; } + boost::filesystem::path GetWebuiDataDir() + { + return GetDataDir() / "webui"; + } + boost::filesystem::path GetDefaultDataDir() { // Custom path, or default path: diff --git a/core/util/util.h b/core/util/util.h index 29f68de5..0fb000ed 100644 --- a/core/util/util.h +++ b/core/util/util.h @@ -80,6 +80,10 @@ namespace util */ boost::filesystem::path GetDefaultDataDir(); + /** + * @return the default directory for webui data + */ + boost::filesystem::path GetWebuiDataDir(); /** * Read a configuration file and store its contents in the given maps. diff --git a/tests/Utility.cpp b/tests/Utility.cpp index dbbe2bb6..8317669f 100644 --- a/tests/Utility.cpp +++ b/tests/Utility.cpp @@ -1,5 +1,6 @@ #include #include "util/util.h" +#include "util/HTTP.h" BOOST_AUTO_TEST_SUITE(UtilityTests) @@ -88,4 +89,29 @@ BOOST_AUTO_TEST_CASE(ParseUrlPassword) BOOST_CHECK_EQUAL(url("").pass_, ""); } +BOOST_AUTO_TEST_CASE(ParseHTTPRequestNoHeaders) +{ + Request req1("GET /index.html HTTP/1.1"); + Request req2("POST / HTTP/1.0\r\n"); + BOOST_CHECK_EQUAL(req1.getMethod(), "GET"); + BOOST_CHECK_EQUAL(req1.getUri(), "/index.html"); + BOOST_CHECK_EQUAL(req2.getMethod(), "POST"); + BOOST_CHECK_EQUAL(req2.getUri(), "/"); +} + +BOOST_AUTO_TEST_CASE(ParseHTTPRequestWithHeaders) +{ + Request req1( + "GET /index.html HTTP/1.1\r\n" + "Host: localhost\r\n" + ); + Request req2( + "POST / HTTP/1.1\r\n" + "Host: localhost:123 \r\n" + ); + BOOST_CHECK_EQUAL(req1.getHeader("Host"), "localhost"); + BOOST_CHECK_EQUAL(req2.getHeader("Host"), "localhost:123"); +} + + BOOST_AUTO_TEST_SUITE_END() From 3d30b4bbbcb3989dd323cb5afeee77c38f84f233 Mon Sep 17 00:00:00 2001 From: EinMByte Date: Sun, 6 Sep 2015 18:51:19 +0200 Subject: [PATCH 05/31] Add Response type to util::http. --- core/util/HTTP.cpp | 44 ++++++++++++++++++++++++++++++++++++++++++++ core/util/HTTP.h | 27 +++++++++++++++++++++++++++ tests/Utility.cpp | 23 +++++++++++++++++++++++ 3 files changed, 94 insertions(+) diff --git a/core/util/HTTP.cpp b/core/util/HTTP.cpp index 2addeda3..db740f55 100644 --- a/core/util/HTTP.cpp +++ b/core/util/HTTP.cpp @@ -55,6 +55,50 @@ std::string Request::getHeader(const std::string& name) const return headers.at(name); } +Response::Response(int status) + : status(status), headers() +{ + +} + +void Response::setHeader(const std::string& name, const std::string& value) +{ + headers[name] = value; +} + +std::string Response::toString() const +{ + std::stringstream ss; + ss << "HTTP/1.1 " << status << ' ' << getStatusMessage() << "\r\n"; + for(auto& pair : headers) + ss << pair.first << ": " << pair.second << "\r\n"; + ss << "\r\n"; + return ss.str(); +} + +std::string Response::getStatusMessage() const +{ + switch(status) { + case 105: + return "Name Not Resolved"; + case 200: + return "OK"; + case 400: + return "Bad Request"; + case 404: + return "Not Found"; + case 408: + return "Request Timeout"; + case 500: + return "Internal Server Error"; + case 502: + return "Not Implemented"; + case 504: + return "Gateway Timeout"; + default: + return std::string(); + } +} } } } diff --git a/core/util/HTTP.h b/core/util/HTTP.h index ceb3d931..252b1533 100644 --- a/core/util/HTTP.h +++ b/core/util/HTTP.h @@ -9,11 +9,15 @@ namespace util { namespace http { class Request { + void parseRequestLine(const std::string& line); + void parseHeaderLine(const std::string& line); public: Request(const std::string& data); + Request(); + std::string getMethod() const; std::string getUri() const; @@ -35,6 +39,29 @@ private: std::map headers; }; +class Response { +public: + + Response(int status); + + /** + * @note overrides existing header values with the same name + */ + void setHeader(const std::string& name, const std::string& value); + + std::string toString() const; + + /** + * @return the message associated with the satus of this response, or the + * empty string if the status number is invalid + */ + std::string getStatusMessage() const; + +private: + int status; + std::map headers; +}; + } } } diff --git a/tests/Utility.cpp b/tests/Utility.cpp index 8317669f..ec285b4e 100644 --- a/tests/Utility.cpp +++ b/tests/Utility.cpp @@ -113,5 +113,28 @@ BOOST_AUTO_TEST_CASE(ParseHTTPRequestWithHeaders) BOOST_CHECK_EQUAL(req2.getHeader("Host"), "localhost:123"); } +BOOST_AUTO_TEST_CASE(HTTPResponseStatusMessage) +{ + BOOST_CHECK_EQUAL(Response(0).getStatusMessage(), ""); + BOOST_CHECK_EQUAL(Response(105).getStatusMessage(), "Name Not Resolved"); + BOOST_CHECK_EQUAL(Response(200).getStatusMessage(), "OK"); + BOOST_CHECK_EQUAL(Response(400).getStatusMessage(), "Bad Request"); + BOOST_CHECK_EQUAL(Response(404).getStatusMessage(), "Not Found"); + BOOST_CHECK_EQUAL(Response(408).getStatusMessage(), "Request Timeout"); + BOOST_CHECK_EQUAL(Response(500).getStatusMessage(), "Internal Server Error"); + BOOST_CHECK_EQUAL(Response(502).getStatusMessage(), "Not Implemented"); + BOOST_CHECK_EQUAL(Response(504).getStatusMessage(), "Gateway Timeout"); +} +BOOST_AUTO_TEST_CASE(WriteHTTPResponse) +{ + Response rsp(200); + rsp.setHeader("Connection", "close"); + BOOST_CHECK_EQUAL( + rsp.toString(), + "HTTP/1.1 200 OK\r\n" + "Connection: close\r\n\r\n" + ); +} + BOOST_AUTO_TEST_SUITE_END() From e7350a3af48982f99c76221b6cdb6be40a1aee9c Mon Sep 17 00:00:00 2001 From: EinMByte Date: Sun, 6 Sep 2015 20:34:50 +0200 Subject: [PATCH 06/31] Started rewrite of HTTPServer. --- client/HTTPServer.cpp | 1107 ++++++----------------------------------- client/HTTPServer.h | 162 +++--- core/util/HTTP.cpp | 12 +- core/util/HTTP.h | 10 +- tests/Utility.cpp | 12 + webui/index.html | 12 +- 6 files changed, 259 insertions(+), 1056 deletions(-) diff --git a/client/HTTPServer.cpp b/client/HTTPServer.cpp index 617be9b5..f09289fc 100644 --- a/client/HTTPServer.cpp +++ b/client/HTTPServer.cpp @@ -2,8 +2,10 @@ #include #include #include +#include #include "util/base64.h" #include "util/Log.h" +#include "util/util.h" #include "tunnel/Tunnel.h" #include "tunnel/TransitTunnel.h" #include "transport/Transports.h" @@ -18,977 +20,194 @@ // For image and info #include "version.h" -namespace i2p +namespace i2p { +namespace util { + +const char HTTP_COMMAND_TUNNELS[] = "tunnels"; +const char HTTP_COMMAND_TRANSIT_TUNNELS[] = "transit_tunnels"; +const char HTTP_COMMAND_TRANSPORTS[] = "transports"; +const char HTTP_COMMAND_START_ACCEPTING_TUNNELS[] = "start_accepting_tunnels"; +const char HTTP_COMMAND_STOP_ACCEPTING_TUNNELS[] = "stop_accepting_tunnels"; +const char HTTP_COMMAND_LOCAL_DESTINATIONS[] = "local_destinations"; +const char HTTP_COMMAND_LOCAL_DESTINATION[] = "local_destination"; +const char HTTP_PARAM_BASE32_ADDRESS[] = "b32"; +const char HTTP_COMMAND_SAM_SESSIONS[] = "sam_sessions"; +const char HTTP_COMMAND_SAM_SESSION[] = "sam_session"; +const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; + +void HTTPConnection::Terminate() { -namespace util -{ - - const std::string HTTPConnection::itoopieImage = - "\"ICToopie"; - - const std::string HTTPConnection::itoopieFavicon = - "data:image/vnd.microsoft.icon;base64," - "AAABAAEAQEAAAAEAIAAoQgAAFgAAACgAAABAAAAAgAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAABsAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgQAAAIMAAACEAAAAg" - "wAAAIAAAACCAAAASQAAACUAAAAmAAAAOwAAAFIAAABgAAAAVAAAACcAAAAVAAAADQAAAAcAAAADAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAAAA/wAAAP4AAAD+AAAA/gAAAP4AAAD+A" - "AAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/" - "AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAACbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAP8AA" - "AD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4A" - "AAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/wAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAKQAAAAAAAAAAAAAAAADbAAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP8AAAD/AAAA/gAA" - "AP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAIA/gAAAPwAAAD/AAAA/gAAAP4AAAD+AAAA/gA" - "AAP4AAAD+AAAA/wAAAFkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApAAAAAAAAAAAAHAAAAP8AAAD+AAAA/gAAAPwACwD9ABEA" - "/QASAPgAFgD4ABUA+AATAP0ADQD8AAIA/AAAAP8AAAD+AAAA/gAAAP8ADQD7ACMA/QAlAP8AJwD/ACI" - "A/QATAPwAAAD8AAAA/gAAAP4AAAD+AAAA/gAAAKIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADe" - "AAAA/gAAAPwAJAD+AAwA+wAAAP8AAAD/AAAA/wAEAPwAEgD8ACQA/gAmAP8AHAD8AAEA/gAAAP4ACwD" - "8ACUA/wAkAP8AFwD8AAkA/AAQAP0AIwD+ACQA/wANAPsAAAD+AAAA/gAAAP8AAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAEAAABNAAAA/wAAAP8AGAD8AAAA/QAYAPMARQDyAFEA9wBOAPIAPAD0ABEA8QAAAP8" - "AGgD7ACMA/wAVAP0AAAD9ACMA/QAkAP8ADwD9AAgA+ABBAPUALADyAAAA/AAUAPwAJgD/ABgA/AAAAP" - "0AAAD/AAAAdgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAP4ADQD8AAgA/wAqAPAAcAD/AGoA/wB" - "qAP8AagD/AGoA/wBvAP8ATwDvAAAA/QAbAP0AEwD8AAYA/QAkAP8AGAD8AAsA9wBuAPgAagD/AGsA/w" - "BjAPkADAD3AAQA/QAiAP4AGgD9AAAA/gAAAP8AAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZgAAAP8AAAD+ABY" - "A+wAQAPQAcQD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBTAPEAAAD/AAcA/AAMAPwAIQD9AA" - "AA+gBoAPgAagD/AGoA/wBqAP8AagD/AHAA/gAuAO0AAAD/AB8A/QAPAPwAAAD+AAAA+AAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAANIAAAD+AAwA+QAAAP0AZQD1AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8Abg" - "D/ABcA8QAAAP4ACAD9AAAA+QBeAPkAagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AbQD/ADkA7wAAAP8AJ" - "AD9AAAA/gAAAP8AAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAALi8AAAAAAAAAAAAAAAD/AAAA/QAAAP8AKwD1AGsA/wBqAP8AagD/AGoA/wBqAP" - "8AagD/AGoA/wBqAP8AagD/AGoA/wA9APUAAAD/AAAA/wBKAPMAagD/AGoA/wBqAP8AagD/AGoA/wBqA" - "P8AagD/AGoA/wBxAP8ADgD1ABMA/AAHAPsAAAD/AAAARQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzPAIAAAAgAAAA/wAAAPwAAAD/AGQA9A" - "BqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8ASQD1AAAA/wASAPMAcAD/AGoA/" - "wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AawD/ACsA+QAOAP4ADQD8AAAA/wAAAEUAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7gAAAAAAAADgwAQAYLwAA//8AAA" - "ICIwAAAP8AAAD+AAYA8wBwAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/A" - "EIA9QAAAP8AMADvAGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGsA/wApAP0AEQD8" - "AAIA/QAAAP8AAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAVgEAAA" - "AAACJ5AQA2ygAAND8BAHV+AgAAAAAAAAH/AAAA/gAcAPQAbAD/AGoA/wBqAP8AagD/AGoA/wBqAP8Aa" - "gD/AGoA/wBqAP8AagD/AGsA/wApAPYAAAD+AB4A+gBsAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8A" - "agD/AGoA/wBrAP8ALADxAAAA/AAAAP4AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAABYAhQAfAE8ABgBWAQAAAAAAIoABAAAAAAA5UgEAAAAAAAAA+AAAAP4AJQD2AGsA/wBqA" - "P8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBsAPgAAAD6AAAA/gANAPAAbgD/AGoA/wBq" - "AP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/ADsA+QAAAP8AAAD+AAAA4AAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtQAAAAAAAABYABgAZgEGAIMBAAAAAAA+fgEAAAAAAAAAA" - "AACA8wAAAH+ACkA9wBgAPgARQDwADoA9gA5APYASgD0AHAA9wBrAP8AagD/AGoA/wBxAP0AFQD0AAAA" - "/gAAAP4AAAD5AHMA/QBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBvAP0ATgDxAEwA9AA1AOkAAAD/AAA" - "A/gAAAP8AAAB3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAvAAzAKcBAADXAAAAAAAcAFYBF" - "gB4ASAAZwEAXT8BADpdAgAAAAAAAQG+AAEB/gAAAf8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAPsAJAD1" - "AEQA9ABEAPMACQD3AAAA/wAAAP4AAAD+AAAA/wAiAPMASAD4AE8A9gBuAPgAbQD/AGoA/wBvAP8AFQD" - "yAAAA/wAAAP8AAAD/AAAA/gAAAP4AAAD+AAAA/wAAABoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "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" - "f7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf8/wCq5v4AERj+AAEA/gAAAP4AAAD+A" - "AAA/gAAAP4AAAD/AAAAdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOgAAAD+AAAA/gAAAP4Aeqf9ALz//wC3+/8At/" - "v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At" - "/r/ALz//gAcJvwAAQH+AAAA/gAAAP4AAAD/AAAAfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI0AAAD/AAAA/gAAAP" - "4Ac579ALn+/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+" - "/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8Avf/+ABcg/QAAAf4AAAD+AAAA/wAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAABIAAAD/AAAA/gAAAP4AP1X9AL7//wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/" - "wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wCy8v4AAAD+AAAA" - "/gAAAP8AAACKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAA/QAAAP4ABAb9AL7//wC3+/8At/v/ALf7/wC3+/8At/v/A" - "Lf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/" - "ALf7/wC3+/8Atvr/AHmm/QAAAP4AAAD+AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAA/wAAAP4AAAD+AHWh/QC2+v8At" - "/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8A" - "t/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wDD//8AGSP9AAAA/gAAAP8AAABuAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbQAAA" - "P8AAAD+AAUH/QDE//8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3" - "+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8Atvv/AH+s/gA" - "AAP4AAAD+AAAA2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAALMAAAD+AAAA/gBZd/0At/z/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7" - "/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf" - "7/wC3+/8At/v/ALf7/wDF//8AAAD9AAAA/gAAAP8AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAA/gAAAP4Al9D9ALf7/wC3+/8At/v/" - "ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v" - "/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8Auf7/AERe/QAAAP4AAAD/AAAAUQAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP4A" - "AAD9AMb//gC3+/8At/v/ALf7/wC3+/8Auf7/AMb//gC2+f4AwP/+AMT//wC4/f8At/v/ALf7/wC3+/8" - "At/v/ALf7/wC3+/8At/v/ALf7/wC8//8Avf//ALn+/wC3+/8At/v/ALf7/wC3+/8At/v/ALb7/wBvlf" - "4AAAD+AAAA/gAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAACgAAAP8AAAD+ABgg/QDA//8At/v/ALf7/wC2+v8AuPz+AEVe/QAAAP0AAAD+AAAA/gA" - "EBv0AU2/9ALz//wC3+/8At/v/ALf7/wC3+/8Atvr/AL7//wBmi/0ALj/9ACQx/QBJZP0Ai739AMD//w" - "C3+/8At/v/ALf7/wC3+/8Aksj+AAAA/gAAAP4AAACZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcAAAD/AAAA/gAtPf4AvP//ALf7/wC3+/8As/T+AAo" - "O/QAAAP0SEhLtAAAA/gAAAP4AAAD+AAAA/gAWH/0Av///ALf7/wC3+/8At/v/ALHx/gANEv0AAAD+AA" - "AA/gAAAP4AAAD/AAAA/wAuP/0Avv//ALf7/wC3+/8At/v/AKjm/QAAAP4AAAD+AAAApwAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATAAAA/wAAAP4AL0H" - "+ALz//wC3+/8AvP//ADJE/QAAAPfc3Nz1TExM6wAAAP4AAAD+AAAA/gAAAP8AAAD+AGeN/gC3+/8At/" - "v/AML//wAeKf0AAAH/AAAA/gAAAP4AAAD+cXFx3YSEhOoAAAD9AEVe/QC6//8At/v/ALf7/wCs7P8AA" - "AD+AAAA/gAAAKEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAP8AAAD+ACAt/QC+//8At/v/ALP1/gAAAP95eXns/////2FhYeoBAQH/AAAA/gEBAf" - "9eXl7lDQ0N9gAgLP0Av///ALf7/wCNwf4AAAD/NDQ03AAAAP8AAAD+AAAA/6Ojo+b/////eXl55AAAA" - "P8AtPf+ALf7/wC3+/8AoNv9AAAA/gAAAP4AAACHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/gAAAP0Ax///ALf7/wCIu/0AAAD/ysrK6/" - "/////8/Pz6Hx8f6wAAAP8kJCTm/f39/WVlZe4ABAX8AMT//wC3/P8AV3X9ERER7/////9MTEzrAAAA6" - "ElJSeb///////////X19e8AAAD/AHuo/QC3+/8At/v/AIW2/gAAAP4AAAD/AAAAZAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0wAAAP4AAAD+AK" - "fl/gC3+/8AjcH+AAAA/83Nze/////////////////////w//////////9gYGDsAA8V/QDC//8Auv//A" - "EJa/UFBQeb////////////////////////////////////vAAAA/wBvlfwAt/v/ALf7/wBdff0AAAD+" - "AAAA/wAAAC0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAI4AAAD+AAAA/gBnjP0At/v/ALz//gAAAP+CgoLx/////////////////////////////" - "///CAkJ8QBCXP4Auf7/ALj8/wBScf0DAwPw////////////////////////////////tra26AAAAP8A" - "hrf+ALf7/wDA//8AHCf9AAAA/gAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3AAAA/wAAAP4AEBb9AMT//wC6//8AQVn+AAAA9+Pj4" - "/L/////////////////////cnJy1gAAAP8AlMr9ALf7/wC3+/8AhLT+AAAA/5mZmeL/////////////" - "/////////////R0dHesAAAD9AL///gC3+/8ApuX+AAAA/gAAAP4AAAC1AAAAAAAAAAAAAAAAAAAAAAA" - "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" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIMAAAD/AAAA/gAAAP4AAAD9AFl3/QCt7/4AwP//ALj9/w" - "C2+/8At/v/ALf7/wC3+/8At/v/ALz//wDB//4AeKP+ABki/QAAAP4AAAD+AAAA/wAAAOEAAAASAAAAA" - "QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAAOUAAAD/AA" - "AA/gAAAP4AAAD+ABcg/QBQbv0AbJD9AH2s/gCBsP0Ac5v+AFt6/QAtPv0AAAD9AAAA/gAAAP4AAAD+A" - "AAA/wAAAIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAWgAAAOIAAAD/AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AA" - "AD+AAAA/gAAAP8AAAD/AAAAmQAAAAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAAIgAAADRAAAA/wAAA" - "P8AAAD/AAAA/wAAAP8AAAD/AAAA5QAAAJoAAABWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAIAAAACQAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///gAf///////4AAAAD/////wAAAAP////" - "/AAAAB/////+AAAAH/////4AAAA//////gAAAD/////8AAAAH/////wAAAAP////+AAAAA/////4AAA" - "AD/////gAAAAP////+AAAAA/////4AAAAD/////gAAAAP////+AAAAA/////4AAAAB/////gAAAAD//" - "//+AAAAAP////wAAAAA////+AAAAAD////wAAAAAP///8AAAAAA////gAAAAAB///8AAAAAAH///gAA" - "AAAAf//+AAAAAAA///4AAAAAAD///4AAAAAAP///wAAAAAAf///wAAAAAD////gAAAAAP////gAAAAB" - "/////AAAAAP////+AAAAD/////wAAAAf////+AAAAB/////4AAAAD/////AAAAAP////8AAAAA/////" - "wAAAAB////+AAAAAH////4AAAAAf////gAAAAA////+AAAAAD////4AAAAAP////gAAAAA////+AAAA" - "AD////4AAAAAf////gAAAAB////+AAAAAH////8AAAAAf////wAAAAD/////gAAAAP////+AAAAB///" - "//8AAAAH/////wAAAA//////gAAAH//////AAAA//////+AAAH//////+AAA///////+AAP///////+" - "AH//////////////8="; - - const char HTTP_COMMAND_TUNNELS[] = "tunnels"; - const char HTTP_COMMAND_TRANSIT_TUNNELS[] = "transit_tunnels"; - const char HTTP_COMMAND_TRANSPORTS[] = "transports"; - const char HTTP_COMMAND_START_ACCEPTING_TUNNELS[] = "start_accepting_tunnels"; - const char HTTP_COMMAND_STOP_ACCEPTING_TUNNELS[] = "stop_accepting_tunnels"; - const char HTTP_COMMAND_LOCAL_DESTINATIONS[] = "local_destinations"; - const char HTTP_COMMAND_LOCAL_DESTINATION[] = "local_destination"; - const char HTTP_PARAM_BASE32_ADDRESS[] = "b32"; - const char HTTP_COMMAND_SAM_SESSIONS[] = "sam_sessions"; - const char HTTP_COMMAND_SAM_SESSION[] = "sam_session"; - const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; - - namespace misc_strings - { - - const char name_value_separator[] = { ':', ' ' }; - const char crlf[] = { '\r', '\n' }; - - } // namespace misc_strings - - std::vector HTTPConnection::reply::to_buffers(int status) - { - std::vector buffers; - if (headers.size () > 0) - { - buffers.push_back(boost::asio::buffer("HTTP/1.1 ", 9)); - buffers.push_back(boost::asio::buffer(boost::lexical_cast(status), 3)); - buffers.push_back(boost::asio::buffer(" ", 1)); - std::string status_string; - switch (status) - { - case 105: status_string = "Name Not Resolved"; break; - case 200: status_string = "OK"; break; - case 400: status_string = "Bad Request"; break; - case 404: status_string = "Not Found"; break; - case 408: status_string = "Request Timeout"; break; - case 500: status_string = "Internal Server Error"; break; - case 502: status_string = "Bad Gateway"; break; - case 503: status_string = "Not Implemented"; break; - case 504: status_string = "Gateway Timeout"; break; - } - buffers.push_back(boost::asio::buffer(status_string, status_string.size())); - buffers.push_back(boost::asio::buffer(misc_strings::crlf)); - for (std::size_t i = 0; i < headers.size(); ++i) - { - header& h = headers[i]; - buffers.push_back(boost::asio::buffer(h.name)); - buffers.push_back(boost::asio::buffer(misc_strings::name_value_separator)); - buffers.push_back(boost::asio::buffer(h.value)); - buffers.push_back(boost::asio::buffer(misc_strings::crlf)); - } - buffers.push_back(boost::asio::buffer(misc_strings::crlf)); - } - buffers.push_back(boost::asio::buffer(content)); - return buffers; - } - - void HTTPConnection::Terminate () - { - m_Socket->close (); - } - - 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) { - m_Buffer[bytes_transferred] = 0; - m_BufferLen = bytes_transferred; - RunRequest(); - } - else if (ecode != boost::asio::error::operation_aborted) - Terminate (); - } - - void HTTPConnection::RunRequest () - { - auto address = ExtractAddress (); - if (address.length () > 1 && address[1] != '?') // not just '/' or '/?' - return; // TODO: error handling - - HandleRequest (address); - } - - std::string HTTPConnection::ExtractAddress () - { - char * get = strstr (m_Buffer, "GET"); - if (get) - { - char * http = strstr (get, "HTTP"); - if (http) - return std::string (get + 4, http - get - 5); - } - return ""; - } - - void HTTPConnection::ExtractParams (const std::string& str, std::map& params) - { - if (str[0] != '&') return; - size_t pos = 1, end; - do - { - end = str.find ('&', pos); - std::string param = str.substr (pos, end - pos); - LogPrint (param); - size_t e = param.find ('='); - if (e != std::string::npos) - params[param.substr(0, e)] = param.substr(e+1); - pos = end + 1; - } - while (end != std::string::npos); - } - - void HTTPConnection::HandleWriteReply (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - boost::system::error_code ignored_ec; - m_Socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); - Terminate (); - } - } - - void HTTPConnection::HandleRequest (const std::string& address) - { - std::stringstream s; - // Html5 head start - s << "\n"; // TODO: Add support for locale. - s << ""; // TODO: Find something to parse html/template system. This is horrible. - s << "Purple I2P " << VERSION " Webconsole"; - // Head end - if (address.length () > 1) - HandleCommand (address.substr (2), s); - else - FillContent (s); - s << ""; - SendReply (s.str ()); - } - - void HTTPConnection::FillContent (std::stringstream& s) - { - s << "

Welcome to the Webconsole!


"; - s << "Uptime: " << boost::posix_time::to_simple_string ( - boost::posix_time::time_duration (boost::posix_time::seconds ( - i2p::context.GetUptime ()))) << "
"; - s << "Status: "; - switch (i2p::context.GetStatus ()) - { - case eRouterStatusOK: s << "OK"; break; - case eRouterStatusTesting: s << "Testing"; break; - case eRouterStatusFirewalled: s << "Firewalled"; break; - default: s << "Unknown"; - } - s << "
"; - s << "Tunnel creation success rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%
"; - s << "Received: " << i2p::transport::transports.GetTotalReceivedBytes ()/1000 << "K"; - s << " (" << i2p::transport::transports.GetInBandwidth () <<" Bps)
"; - s << "Sent: " << i2p::transport::transports.GetTotalSentBytes ()/1000 << "K"; - s << " (" << i2p::transport::transports.GetOutBandwidth () <<" Bps)
"; - s << "Data path: " << i2p::util::filesystem::GetDataDir().string() << "

"; - s << "Our external address:" << "
" ; - for (auto& address : i2p::context.GetRouterInfo().GetAddresses()) - { - switch (address.transportStyle) - { - case i2p::data::RouterInfo::eTransportNTCP: - if (address.host.is_v6 ()) - s << "NTCP6  "; - else - s << "NTCP  "; - break; - case i2p::data::RouterInfo::eTransportSSU: - if (address.host.is_v6 ()) - s << "SSU6     "; - else - s << "SSU     "; - break; - default: - s << "Unknown  "; - } - s << address.host.to_string() << ":" << address.port << "
"; - } - s << "
Routers: " << i2p::data::netdb.GetNumRouters () << " "; - s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << " "; - s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "
"; - - s << "
Local destinations"; - s << "
Tunnels"; - s << "
Transit tunnels"; - s << "
Transports"; - if (i2p::client::context.GetSAMBridge ()) - s << "
SAM sessions"; - s << "
"; - - if (i2p::context.AcceptsTunnels ()) - s << "
Stop accepting tunnels
"; - else - s << "
Start accepting tunnels
"; - } + m_Socket->close(); +} - void HTTPConnection::HandleCommand (const std::string& command, std::stringstream& s) - { - size_t paramsPos = command.find('&'); - std::string cmd = command.substr (0, paramsPos); - if (cmd == HTTP_COMMAND_TRANSPORTS) - ShowTransports (s); - else if (cmd == HTTP_COMMAND_TUNNELS) - ShowTunnels (s); - else if (cmd == HTTP_COMMAND_TRANSIT_TUNNELS) - ShowTransitTunnels (s); - else if (cmd == HTTP_COMMAND_START_ACCEPTING_TUNNELS) - StartAcceptingTunnels (s); - else if (cmd == HTTP_COMMAND_STOP_ACCEPTING_TUNNELS) - StopAcceptingTunnels (s); - else if (cmd == HTTP_COMMAND_LOCAL_DESTINATIONS) - ShowLocalDestinations (s); - else if (cmd == HTTP_COMMAND_LOCAL_DESTINATION) - { - std::map params; - ExtractParams (command.substr (paramsPos), params); - auto b32 = params[HTTP_PARAM_BASE32_ADDRESS]; - ShowLocalDestination (b32, s); - } - else if (cmd == HTTP_COMMAND_SAM_SESSIONS) - ShowSAMSessions (s); - else if (cmd == HTTP_COMMAND_SAM_SESSION) - { - std::map params; - ExtractParams (command.substr (paramsPos), params); - auto id = params[HTTP_PARAM_SAM_SESSION_ID]; - ShowSAMSession (id, s); - } - } +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::ShowTransports (std::stringstream& s) - { - auto ntcpServer = i2p::transport::transports.GetNTCPServer (); - if (ntcpServer) - { - s << "NTCP
"; - for (auto it: ntcpServer->GetNTCPSessions ()) - { - if (it.second && it.second->IsEstablished ()) - { - // incoming connection doesn't have remote RI - auto outgoing = it.second->GetRemoteRouter (); - if (outgoing) s << "-->"; - s << it.second->GetRemoteIdentity ().GetIdentHash ().ToBase64 ().substr (0, 4) << ": " - << it.second->GetSocket ().remote_endpoint().address ().to_string (); - if (!outgoing) s << "-->"; - s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; - s << "
"; - } - s << std::endl; - } - } - auto ssuServer = i2p::transport::transports.GetSSUServer (); - if (ssuServer) - { - s << "
SSU
"; - for (auto it: ssuServer->GetSessions ()) - { - // incoming connections don't have remote router - auto outgoing = it.second->GetRemoteRouter (); - auto endpoint = it.second->GetRemoteEndpoint (); - if (outgoing) s << "-->"; - s << endpoint.address ().to_string () << ":" << endpoint.port (); - if (!outgoing) s << "-->"; - s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; - if (it.second->GetRelayTag ()) - s << " [itag:" << it.second->GetRelayTag () << "]"; - s << "
"; - s << std::endl; - } - } - } - - void HTTPConnection::ShowTunnels (std::stringstream& s) - { - s << "Queue size:" << i2p::tunnel::tunnels.GetQueueSize () << "
"; +void HTTPConnection::HandleReceive(const boost::system::error_code& e, std::size_t nb_bytes) +{ + if(!e) { + m_Buffer[nb_bytes] = 0; + m_BufferLen = nb_bytes; + RunRequest(); + } else if(e != boost::asio::error::operation_aborted) + Terminate(); +} - for (auto it: i2p::tunnel::tunnels.GetOutboundTunnels ()) - { - it->GetTunnelConfig ()->Print (s); - auto state = it->GetState (); - if (state == i2p::tunnel::eTunnelStateFailed) - s << " " << "Failed"; - else if (state == i2p::tunnel::eTunnelStateExpiring) - s << " " << "Exp"; - s << " " << (int)it->GetNumSentBytes () << "
"; - s << std::endl; - } +void HTTPConnection::RunRequest() +{ + m_Request = i2p::util::http::Request(std::string(m_Buffer, m_Buffer + m_BufferLen)); + HandleRequest(); +} - for (auto it: i2p::tunnel::tunnels.GetInboundTunnels ()) - { - it.second->GetTunnelConfig ()->Print (s); - auto state = it.second->GetState (); - if (state == i2p::tunnel::eTunnelStateFailed) - s << " " << "Failed"; - else if (state == i2p::tunnel::eTunnelStateExpiring) - s << " " << "Exp"; - s << " " << (int)it.second->GetNumReceivedBytes () << "
"; - s << std::endl; - } +void HTTPConnection::ExtractParams(const std::string& str, std::map& params) +{ + if(str[0] != '&') return; + size_t pos = 1, end; + do + { + end = str.find('&', pos); + std::string param = str.substr(pos, end - pos); + LogPrint(param); + size_t e = param.find('='); + if(e != std::string::npos) + params[param.substr(0, e)] = param.substr(e+1); + pos = end + 1; } + while(end != std::string::npos); +} - void HTTPConnection::ShowTransitTunnels (std::stringstream& s) - { - for (auto it: i2p::tunnel::tunnels.GetTransitTunnels ()) - { - if (dynamic_cast(it.second)) - s << it.second->GetTunnelID () << "-->"; - else if (dynamic_cast(it.second)) - s << "-->" << it.second->GetTunnelID (); - else - s << "-->" << it.second->GetTunnelID () << "-->"; - s << " " << it.second->GetNumTransmittedBytes () << "
"; - } +void HTTPConnection::HandleWriteReply(const boost::system::error_code& e) +{ + if(e != boost::asio::error::operation_aborted) { + boost::system::error_code ignored_ec; + m_Socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); + Terminate(); } +} - void HTTPConnection::ShowLocalDestinations (std::stringstream& s) - { - for (auto& it: i2p::client::context.GetDestinations ()) - { - auto ident = it.second->GetIdentHash ();; - s << ""; - s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
" << std::endl; - } - } - - void HTTPConnection::ShowLocalDestination (const std::string& b32, std::stringstream& s) - { - i2p::data::IdentHash ident; - ident.FromBase32 (b32); - auto dest = i2p::client::context.FindLocalDestination (ident); - if (dest) - { - s << "Base64:
" << dest->GetIdentity ().ToBase64 () << "

"; - s << "LeaseSets: " << dest->GetNumRemoteLeaseSets () << "
"; - auto pool = dest->GetTunnelPool (); - if (pool) - { - s << "Tunnels:
"; - for (auto it: pool->GetOutboundTunnels ()) - { - it->GetTunnelConfig ()->Print (s); - auto state = it->GetState (); - if (state == i2p::tunnel::eTunnelStateFailed) - s << " " << "Failed"; - else if (state == i2p::tunnel::eTunnelStateExpiring) - s << " " << "Exp"; - s << "
" << std::endl; - } - for (auto it: pool->GetInboundTunnels ()) - { - it->GetTunnelConfig ()->Print (s); - auto state = it->GetState (); - if (state == i2p::tunnel::eTunnelStateFailed) - s << " " << "Failed"; - else if (state == i2p::tunnel::eTunnelStateExpiring) - s << " " << "Exp"; - s << "
" << std::endl; - } - } - s << "
Streams:
"; - for (auto it: dest->GetStreamingDestination ()->GetStreams ()) - { - s << it.first << "->" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetRemoteIdentity ()) << " "; - s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; - s << " [out:" << it.second->GetSendQueueSize () << "][in:" << it.second->GetReceiveQueueSize () << "]"; - s << "[buf:" << it.second->GetSendBufferSize () << "]"; - s << "[RTT:" << it.second->GetRTT () << "]"; - s << "[Window:" << it.second->GetWindowSize () << "]"; - s << "[Status:" << (int)it.second->GetStatus () << "]"; - s << "
"<< std::endl; - } - } - } +void HTTPConnection::HandleRequest() +{ + boost::system::error_code e; - void HTTPConnection::ShowSAMSessions (std::stringstream& s) - { - auto sam = i2p::client::context.GetSAMBridge (); - if (sam) - { - for (auto& it: sam->GetSessions ()) - { - s << ""; - s << it.first << "
" << std::endl; - } - } - } + std::string uri = m_Request.getUri(); + if(uri == "/") + uri = "index.html"; - void HTTPConnection::ShowSAMSession (const std::string& id, std::stringstream& s) - { - auto sam = i2p::client::context.GetSAMBridge (); - if (sam) - { - auto session = sam->FindSession (id); - if (session) - { - auto& ident = session->localDestination->GetIdentHash(); - s << ""; - s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
" << std::endl; - s << "Streams:
"; - for (auto it: session->sockets) - { - switch (it->GetSocketType ()) - { - case i2p::client::eSAMSocketTypeSession: - s << "session"; - break; - case i2p::client::eSAMSocketTypeStream: - s << "stream"; - break; - case i2p::client::eSAMSocketTypeAcceptor: - s << "acceptor"; - break; - default: - s << "unknown"; - } - s << " [" << it->GetSocket ().remote_endpoint() << "]"; - s << "
" << std::endl; - } - } - } - } + // Use cannonical to avoid .. or . in path + const std::string address = boost::filesystem::canonical( + i2p::util::filesystem::GetWebuiDataDir() / uri, e + ).string(); - void HTTPConnection::StartAcceptingTunnels (std::stringstream& s) - { - i2p::context.SetAcceptsTunnels (true); - s << "Accepting tunnels started" << std::endl; - } + std::ifstream ifs(address); + if(e || !ifs || !isAllowed(address)) { + m_Reply = i2p::util::http::Response(404, ""); + return SendReply(); + } + + std::string str; + ifs.seekg(0, ifs.end); + str.resize(ifs.tellg()); + ifs.seekg(0, ifs.beg); + ifs.read(&str[0], str.size()); + ifs.close(); + + m_Reply = i2p::util::http::Response(200, str); + SendReply(); +} - void HTTPConnection::StopAcceptingTunnels (std::stringstream& s) - { - i2p::context.SetAcceptsTunnels (false); - s << "Accepting tunnels stopped" << std::endl; - } +bool HTTPConnection::isAllowed(const std::string& address) +{ + const std::size_t pos_dot = address.find_last_of('.'); + const std::size_t pos_slash = address.find_last_of('/'); + if(pos_dot == std::string::npos || pos_dot == address.size() - 1) + return false; + if(pos_slash != std::string::npos && pos_dot < pos_slash) + return false; + return true; +} - void HTTPConnection::SendReply (const std::string& content, int status) - { - m_Reply.content = content; - m_Reply.headers.resize(3); - // we need the date header to be compliant with HTTP 1.1 - std::time_t time_now = std::time(nullptr); - char time_buff[128]; - if ( std::strftime(time_buff, sizeof(time_buff), "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(&time_now)) ) { - m_Reply.headers[0].name = "Date"; - m_Reply.headers[0].value = std::string(time_buff); - m_Reply.headers[1].name = "Content-Length"; - m_Reply.headers[1].value = boost::lexical_cast(m_Reply.content.size()); - m_Reply.headers[2].name = "Content-Type"; - m_Reply.headers[2].value = "text/html"; - } - boost::asio::async_write (*m_Socket, m_Reply.to_buffers(status), - std::bind (&HTTPConnection::HandleWriteReply, shared_from_this (), std::placeholders::_1)); - } +void HTTPConnection::SendReply() +{ + // we need the date header to be compliant with HTTP 1.1 + std::time_t time_now = std::time(nullptr); + char time_buff[128]; + if(std::strftime(time_buff, sizeof(time_buff), "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(&time_now)) ) { + m_Reply.setHeader("Date", std::string(time_buff)); + m_Reply.setContentLength(); + m_Reply.setHeader("Content-Type", "text/html"); + } + boost::asio::async_write( + *m_Socket, boost::asio::buffer(m_Reply.toString()), + std::bind(&HTTPConnection::HandleWriteReply, shared_from_this(), std::placeholders::_1) + ); +} - HTTPServer::HTTPServer (const std::string& address, int port): - 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_NewSocket (nullptr) - { +HTTPServer::HTTPServer(const std::string& address, int port): + 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_NewSocket(nullptr) +{ - } +} - HTTPServer::~HTTPServer () - { - Stop (); - } +HTTPServer::~HTTPServer() +{ + Stop(); +} - void HTTPServer::Start () - { - m_Thread = new std::thread (std::bind (&HTTPServer::Run, this)); - m_Acceptor.listen (); - Accept (); - } +void HTTPServer::Start() +{ + m_Thread = new std::thread(std::bind(&HTTPServer::Run, this)); + m_Acceptor.listen(); + Accept(); +} - void HTTPServer::Stop () +void HTTPServer::Stop() +{ + m_Acceptor.close(); + m_Service.stop(); + if(m_Thread) { - m_Acceptor.close(); - m_Service.stop (); - if (m_Thread) - { - m_Thread->join (); - delete m_Thread; - m_Thread = nullptr; - } + m_Thread->join(); + delete m_Thread; + m_Thread = nullptr; } +} - void HTTPServer::Run () - { - m_Service.run (); - } +void HTTPServer::Run() +{ + m_Service.run(); +} - void HTTPServer::Accept () - { - m_NewSocket = new boost::asio::ip::tcp::socket (m_Service); - m_Acceptor.async_accept (*m_NewSocket, boost::bind (&HTTPServer::HandleAccept, this, - boost::asio::placeholders::error)); - } +void HTTPServer::Accept() +{ + m_NewSocket = new boost::asio::ip::tcp::socket(m_Service); + m_Acceptor.async_accept(*m_NewSocket, boost::bind(&HTTPServer::HandleAccept, this, + boost::asio::placeholders::error)); +} - void HTTPServer::HandleAccept(const boost::system::error_code& ecode) +void HTTPServer::HandleAccept(const boost::system::error_code& ecode) +{ + if(!ecode) { - if (!ecode) - { - CreateConnection(m_NewSocket); - Accept (); - } + CreateConnection(m_NewSocket); + Accept(); } +} - void HTTPServer::CreateConnection(boost::asio::ip::tcp::socket * m_NewSocket) - { - auto conn = std::make_shared (m_NewSocket); - conn->Receive (); - } +void HTTPServer::CreateConnection(boost::asio::ip::tcp::socket * m_NewSocket) +{ + auto conn = std::make_shared(m_NewSocket); + conn->Receive(); +} } } diff --git a/client/HTTPServer.h b/client/HTTPServer.h index dcb74b3e..5bf253f2 100644 --- a/client/HTTPServer.h +++ b/client/HTTPServer.h @@ -6,119 +6,71 @@ #include #include #include +#include "util/HTTP.h" -namespace i2p -{ -namespace util -{ - const size_t HTTP_CONNECTION_BUFFER_SIZE = 8192; - const int HTTP_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds - class HTTPConnection: public std::enable_shared_from_this - { - protected: - - struct header - { - std::string name; - std::string value; - }; - - struct request - { - std::string method; - std::string uri; - std::string host; - int port; - int http_version_major; - int http_version_minor; - std::vector
headers; - }; - - struct reply - { - std::vector
headers; - std::string content; - - std::vector to_buffers (int status); - }; - - public: - - HTTPConnection (boost::asio::ip::tcp::socket * socket): - m_Socket (socket), m_Timer (socket->get_io_service ()), - m_BufferLen (0) {}; - ~HTTPConnection() { delete m_Socket; } - void Receive (); - - private: - - void Terminate (); - void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); - void HandleWriteReply(const boost::system::error_code& ecode); - void SendReply (const std::string& content, int status = 200); - - void HandleRequest (const std::string& address); - void HandleCommand (const std::string& command, std::stringstream& s); - void ShowTransports (std::stringstream& s); - void ShowTunnels (std::stringstream& s); - void ShowTransitTunnels (std::stringstream& s); - void ShowLocalDestinations (std::stringstream& s); - void ShowLocalDestination (const std::string& b32, std::stringstream& s); - void ShowSAMSessions (std::stringstream& s); - void ShowSAMSession (const std::string& id, std::stringstream& s); - void StartAcceptingTunnels (std::stringstream& s); - void StopAcceptingTunnels (std::stringstream& s); - void FillContent (std::stringstream& s); - std::string ExtractAddress (); - void ExtractParams (const std::string& str, std::map& params); - - - protected: - - boost::asio::ip::tcp::socket * m_Socket; - boost::asio::deadline_timer m_Timer; - char m_Buffer[HTTP_CONNECTION_BUFFER_SIZE + 1], m_StreamBuffer[HTTP_CONNECTION_BUFFER_SIZE + 1]; - size_t m_BufferLen; - request m_Request; - reply m_Reply; - - protected: - - virtual void RunRequest (); - - public: - - static const std::string itoopieImage; - static const std::string itoopieFavicon; - }; +namespace i2p { +namespace util { - class HTTPServer - { - public: +const size_t HTTP_CONNECTION_BUFFER_SIZE = 8192; +const int HTTP_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds - HTTPServer (const std::string& address, int port); - virtual ~HTTPServer (); +class HTTPConnection: public std::enable_shared_from_this { +public: - void Start (); - void Stop (); + HTTPConnection(boost::asio::ip::tcp::socket * socket) + : m_Socket(socket), m_Timer(socket->get_io_service()), + m_BufferLen(0) {}; + ~HTTPConnection() { delete m_Socket; } + void Receive(); + +private: - private: + void Terminate(); + void HandleReceive(const boost::system::error_code& ecode, std::size_t bytes_transferred); + void RunRequest(); + void HandleWriteReply(const boost::system::error_code& ecode); + void SendReply(); - void Run (); - void Accept (); - void HandleAccept(const boost::system::error_code& ecode); - - private: + void HandleRequest(); + void ExtractParams(const std::string& str, std::map& params); + + bool isAllowed(const std::string& address); +private: + boost::asio::ip::tcp::socket* m_Socket; + boost::asio::deadline_timer m_Timer; + char m_Buffer[HTTP_CONNECTION_BUFFER_SIZE + 1]; + char m_StreamBuffer[HTTP_CONNECTION_BUFFER_SIZE + 1]; + size_t m_BufferLen; + util::http::Request m_Request; + util::http::Response m_Reply; +}; + +class HTTPServer { +public: + + HTTPServer(const std::string& address, int port); + virtual ~HTTPServer(); + + void Start(); + void Stop(); + +private: + + void Run(); + void Accept(); + void HandleAccept(const boost::system::error_code& ecode); + +private: - std::thread * m_Thread; - boost::asio::io_service m_Service; - boost::asio::io_service::work m_Work; - boost::asio::ip::tcp::acceptor m_Acceptor; - boost::asio::ip::tcp::socket * m_NewSocket; + std::thread * m_Thread; + boost::asio::io_service m_Service; + boost::asio::io_service::work m_Work; + boost::asio::ip::tcp::acceptor m_Acceptor; + boost::asio::ip::tcp::socket * m_NewSocket; - protected: - virtual void CreateConnection(boost::asio::ip::tcp::socket * m_NewSocket); - }; +protected: + virtual void CreateConnection(boost::asio::ip::tcp::socket * m_NewSocket); +}; } } diff --git a/core/util/HTTP.cpp b/core/util/HTTP.cpp index db740f55..28c5236d 100644 --- a/core/util/HTTP.cpp +++ b/core/util/HTTP.cpp @@ -55,8 +55,8 @@ std::string Request::getHeader(const std::string& name) const return headers.at(name); } -Response::Response(int status) - : status(status), headers() +Response::Response(int status, const std::string& content) + : status(status), content(content), headers() { } @@ -72,7 +72,7 @@ std::string Response::toString() const ss << "HTTP/1.1 " << status << ' ' << getStatusMessage() << "\r\n"; for(auto& pair : headers) ss << pair.first << ": " << pair.second << "\r\n"; - ss << "\r\n"; + ss << "\r\n" << content; return ss.str(); } @@ -99,6 +99,12 @@ std::string Response::getStatusMessage() const return std::string(); } } + +void Response::setContentLength() +{ + setHeader("Content-Length", std::to_string(content.size())); +} + } } } diff --git a/core/util/HTTP.h b/core/util/HTTP.h index 252b1533..b587dd30 100644 --- a/core/util/HTTP.h +++ b/core/util/HTTP.h @@ -14,9 +14,9 @@ class Request { void parseHeaderLine(const std::string& line); public: - Request(const std::string& data); + Request() = default; - Request(); + Request(const std::string& data); std::string getMethod() const; @@ -41,8 +41,9 @@ private: class Response { public: + Response() = default; - Response(int status); + Response(int status, const std::string& content = ""); /** * @note overrides existing header values with the same name @@ -57,8 +58,11 @@ public: */ std::string getStatusMessage() const; + void setContentLength(); + private: int status; + std::string content; std::map headers; }; diff --git a/tests/Utility.cpp b/tests/Utility.cpp index ec285b4e..07d77c53 100644 --- a/tests/Utility.cpp +++ b/tests/Utility.cpp @@ -125,6 +125,7 @@ BOOST_AUTO_TEST_CASE(HTTPResponseStatusMessage) BOOST_CHECK_EQUAL(Response(502).getStatusMessage(), "Not Implemented"); BOOST_CHECK_EQUAL(Response(504).getStatusMessage(), "Gateway Timeout"); } + BOOST_AUTO_TEST_CASE(WriteHTTPResponse) { Response rsp(200); @@ -136,5 +137,16 @@ BOOST_AUTO_TEST_CASE(WriteHTTPResponse) ); } +BOOST_AUTO_TEST_CASE(WriteHTTPResponseWithContent) +{ + Response rsp(200, "Test content."); + rsp.setHeader("Connection", "close"); + BOOST_CHECK_EQUAL( + rsp.toString(), + "HTTP/1.1 200 OK\r\n" + "Connection: close\r\n\r\n" + "Test content." + ); +} BOOST_AUTO_TEST_SUITE_END() diff --git a/webui/index.html b/webui/index.html index 3d98ba3d..8d82aeb0 100644 --- a/webui/index.html +++ b/webui/index.html @@ -1 +1,11 @@ -Nothing here yet. + + + +Purple I2P 0.10.0 Webconsole + + + + +

Nothing here yet.

+ + From 719bfbc89b69db3d47c1f6798647880b6bdc4edc Mon Sep 17 00:00:00 2001 From: EinMByte Date: Mon, 7 Sep 2015 12:31:57 +0200 Subject: [PATCH 07/31] Added basic webui (layout from l-n-s). --- client/HTTPServer.cpp | 55 +++++-- client/HTTPServer.h | 12 +- core/util/HTTP.cpp | 12 +- core/util/HTTP.h | 3 + tests/Utility.cpp | 15 ++ webui/css/main.css | 257 +++++++++++++++++++++++++++++++++ webui/index.html | 88 ++++++++++- webui/javascript/I2PControl.js | 84 +++++++++++ 8 files changed, 511 insertions(+), 15 deletions(-) create mode 100644 webui/css/main.css create mode 100644 webui/javascript/I2PControl.js diff --git a/client/HTTPServer.cpp b/client/HTTPServer.cpp index f09289fc..8aea3ae7 100644 --- a/client/HTTPServer.cpp +++ b/client/HTTPServer.cpp @@ -35,6 +35,14 @@ const char HTTP_COMMAND_SAM_SESSIONS[] = "sam_sessions"; const char HTTP_COMMAND_SAM_SESSION[] = "sam_session"; const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; +HTTPConnection::HTTPConnection(boost::asio::ip::tcp::socket* socket, + std::shared_ptr session) + : m_Socket(socket), m_Timer(socket->get_io_service()), + m_BufferLen(0), m_Session(session) +{ + +} + void HTTPConnection::Terminate() { m_Socket->close(); @@ -62,8 +70,18 @@ void HTTPConnection::HandleReceive(const boost::system::error_code& e, std::size void HTTPConnection::RunRequest() { - m_Request = i2p::util::http::Request(std::string(m_Buffer, m_Buffer + m_BufferLen)); - HandleRequest(); + try { + m_Request = i2p::util::http::Request(std::string(m_Buffer, m_Buffer + m_BufferLen)); + if(m_Request.getMethod() == "GET") + return HandleRequest(); + if(m_Request.getHeader("Content-Type").find("application/json") != std::string::npos) + return HandleI2PControlRequest(); + } catch(...) { + // Ignore the error for now, probably Content-Type doesn't exist + } + // Unsupported method + m_Reply = i2p::util::http::Response(502, ""); + SendReply(); } void HTTPConnection::ExtractParams(const std::string& str, std::map& params) @@ -119,6 +137,23 @@ void HTTPConnection::HandleRequest() ifs.close(); m_Reply = i2p::util::http::Response(200, str); + + // TODO: get rid of this hack, actually determine the MIME type + if(address.substr(address.find_last_of(".")) == ".css") + m_Reply.setHeader("Content-Type", "text/css"); + else if(address.substr(address.find_last_of(".")) == ".js") + m_Reply.setHeader("Content-Type", "text/javascript"); + else + m_Reply.setHeader("Content-Type", "text/html"); + SendReply(); +} + +void HTTPConnection::HandleI2PControlRequest() +{ + std::stringstream ss(m_Request.getContent()); + const client::I2PControlSession::Response rsp = m_Session->handleRequest(ss); + m_Reply = i2p::util::http::Response(200, rsp.toJsonString()); + m_Reply.setHeader("Content-Type", "application/json"); SendReply(); } @@ -141,7 +176,6 @@ void HTTPConnection::SendReply() if(std::strftime(time_buff, sizeof(time_buff), "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(&time_now)) ) { m_Reply.setHeader("Date", std::string(time_buff)); m_Reply.setContentLength(); - m_Reply.setHeader("Content-Type", "text/html"); } boost::asio::async_write( *m_Socket, boost::asio::buffer(m_Reply.toString()), @@ -153,7 +187,9 @@ HTTPServer::HTTPServer(const std::string& address, int port): 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_NewSocket(nullptr) + ), + m_NewSocket(nullptr), + m_Session(std::make_shared(m_Service)) { } @@ -167,11 +203,13 @@ void HTTPServer::Start() { m_Thread = new std::thread(std::bind(&HTTPServer::Run, this)); m_Acceptor.listen(); + m_Session->start(); Accept(); } void HTTPServer::Stop() { + m_Session->stop(); m_Acceptor.close(); m_Service.stop(); if(m_Thread) @@ -196,18 +234,17 @@ void HTTPServer::Accept() void HTTPServer::HandleAccept(const boost::system::error_code& ecode) { - if(!ecode) - { + if(!ecode) { CreateConnection(m_NewSocket); Accept(); } } -void HTTPServer::CreateConnection(boost::asio::ip::tcp::socket * m_NewSocket) +void HTTPServer::CreateConnection(boost::asio::ip::tcp::socket* m_NewSocket) { - auto conn = std::make_shared(m_NewSocket); + auto conn = std::make_shared(m_NewSocket, m_Session); conn->Receive(); } + } } - diff --git a/client/HTTPServer.h b/client/HTTPServer.h index 5bf253f2..9cc149fe 100644 --- a/client/HTTPServer.h +++ b/client/HTTPServer.h @@ -6,6 +6,7 @@ #include #include #include +#include "i2pcontrol/I2PControl.h" #include "util/HTTP.h" namespace i2p { @@ -17,9 +18,9 @@ const int HTTP_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds class HTTPConnection: public std::enable_shared_from_this { public: - HTTPConnection(boost::asio::ip::tcp::socket * socket) - : m_Socket(socket), m_Timer(socket->get_io_service()), - m_BufferLen(0) {}; + HTTPConnection(boost::asio::ip::tcp::socket* socket, + std::shared_ptr session); + ~HTTPConnection() { delete m_Socket; } void Receive(); @@ -32,6 +33,7 @@ private: void SendReply(); void HandleRequest(); + void HandleI2PControlRequest(); void ExtractParams(const std::string& str, std::map& params); bool isAllowed(const std::string& address); @@ -43,6 +45,7 @@ private: size_t m_BufferLen; util::http::Request m_Request; util::http::Response m_Reply; + std::shared_ptr m_Session; }; class HTTPServer { @@ -67,9 +70,10 @@ private: boost::asio::io_service::work m_Work; boost::asio::ip::tcp::acceptor m_Acceptor; boost::asio::ip::tcp::socket * m_NewSocket; + std::shared_ptr m_Session; protected: - virtual void CreateConnection(boost::asio::ip::tcp::socket * m_NewSocket); + void CreateConnection(boost::asio::ip::tcp::socket* m_NewSocket); }; } } diff --git a/core/util/HTTP.cpp b/core/util/HTTP.cpp index 28c5236d..1f94c6c5 100644 --- a/core/util/HTTP.cpp +++ b/core/util/HTTP.cpp @@ -26,8 +26,13 @@ Request::Request(const std::string& data) std::getline(ss, line); parseRequestLine(line); - while(std::getline(ss, line)) + while(std::getline(ss, line) && !boost::trim_copy(line).empty()) parseHeaderLine(line); + + if(ss) { + const std::string current = ss.str(); + content = current.substr(ss.tellg()); + } } std::string Request::getMethod() const @@ -55,6 +60,11 @@ std::string Request::getHeader(const std::string& name) const return headers.at(name); } +std::string Request::getContent() const +{ + return content; +} + Response::Response(int status, const std::string& content) : status(status), content(content), headers() { diff --git a/core/util/HTTP.h b/core/util/HTTP.h index b587dd30..a559938d 100644 --- a/core/util/HTTP.h +++ b/core/util/HTTP.h @@ -31,10 +31,13 @@ public: */ std::string getHeader(const std::string& name) const; + std::string getContent() const; + private: std::string method; std::string uri; std::string host; + std::string content; int port; std::map headers; }; diff --git a/tests/Utility.cpp b/tests/Utility.cpp index 07d77c53..795fa517 100644 --- a/tests/Utility.cpp +++ b/tests/Utility.cpp @@ -113,6 +113,21 @@ BOOST_AUTO_TEST_CASE(ParseHTTPRequestWithHeaders) BOOST_CHECK_EQUAL(req2.getHeader("Host"), "localhost:123"); } +BOOST_AUTO_TEST_CASE(ParseHTTPRequestWithContent) +{ + Request req1( + "GET /index.html HTTP/1.1\r\n" + "Host: localhost\r\n\r\n" + "Random content." + ); + Request req2( + "GET /index.html HTTP/1.0\r\n\r\n" + "Random content.\r\nTest content." + ); + BOOST_CHECK_EQUAL(req1.getContent(), "Random content."); + BOOST_CHECK_EQUAL(req2.getContent(), "Random content.\r\nTest content."); +} + BOOST_AUTO_TEST_CASE(HTTPResponseStatusMessage) { BOOST_CHECK_EQUAL(Response(0).getStatusMessage(), ""); diff --git a/webui/css/main.css b/webui/css/main.css new file mode 100644 index 00000000..b6f47251 --- /dev/null +++ b/webui/css/main.css @@ -0,0 +1,257 @@ +/*! +Pure v0.6.0 +Copyright 2014 Yahoo! Inc. All rights reserved. +Licensed under the BSD License. +https://github.com/yahoo/pure/blob/master/LICENSE.md +*/ +/*! +normalize.css v^3.0 | MIT License | git.io/normalize +Copyright (c) Nicolas Gallagher and Jonathan Neal +*/ +/*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap;-ms-align-content:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-1-12,.pure-u-2-24{width:8.3333%;*width:8.3023%}.pure-u-1-8,.pure-u-3-24{width:12.5%;*width:12.469%}.pure-u-1-6,.pure-u-4-24{width:16.6667%;*width:16.6357%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-1-4,.pure-u-6-24{width:25%;*width:24.969%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-1-3,.pure-u-8-24{width:33.3333%;*width:33.3023%}.pure-u-3-8,.pure-u-9-24{width:37.5%;*width:37.469%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-5-12,.pure-u-10-24{width:41.6667%;*width:41.6357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-1-2,.pure-u-12-24{width:50%;*width:49.969%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-7-12,.pure-u-14-24{width:58.3333%;*width:58.3023%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-5-8,.pure-u-15-24{width:62.5%;*width:62.469%}.pure-u-2-3,.pure-u-16-24{width:66.6667%;*width:66.6357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-3-4,.pure-u-18-24{width:75%;*width:74.969%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-5-6,.pure-u-20-24{width:83.3333%;*width:83.3023%}.pure-u-7-8,.pure-u-21-24{width:87.5%;*width:87.469%}.pure-u-11-12,.pure-u-22-24{width:91.6667%;*width:91.6357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%}.pure-button{display:inline-block;zoom:1;line-height:normal;white-space:nowrap;vertical-align:middle;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button{font-family:inherit;font-size:100%;padding:.5em 1em;color:#444;color:rgba(0,0,0,.8);border:1px solid #999;border:0 rgba(0,0,0,0);background-color:#E6E6E6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:hover,.pure-button:focus{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000', GradientType=0);background-image:-webkit-gradient(linear,0 0,0 100%,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:-moz-linear-gradient(top,rgba(0,0,0,.05) 0,rgba(0,0,0,.1));background-image:-o-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;border-color:#000\9}.pure-button[disabled],.pure-button-disabled,.pure-button-disabled:hover,.pure-button-disabled:focus,.pure-button-disabled:active{border:0;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);filter:alpha(opacity=40);-khtml-opacity:.4;-moz-opacity:.4;opacity:.4;cursor:not-allowed;box-shadow:none}.pure-button-hidden{display:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;vertical-align:middle;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=text]:focus,.pure-form input[type=password]:focus,.pure-form input[type=email]:focus,.pure-form input[type=url]:focus,.pure-form input[type=date]:focus,.pure-form input[type=month]:focus,.pure-form input[type=time]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=week]:focus,.pure-form input[type=number]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=color]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;border-color:#129FEA}.pure-form input:not([type]):focus{outline:0;border-color:#129FEA}.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus,.pure-form input[type=checkbox]:focus{outline:thin solid #129FEA;outline:1px auto #129FEA}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=text][disabled],.pure-form input[type=password][disabled],.pure-form input[type=email][disabled],.pure-form input[type=url][disabled],.pure-form input[type=date][disabled],.pure-form input[type=month][disabled],.pure-form input[type=time][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=week][disabled],.pure-form input[type=number][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=color][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background-color:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form textarea:focus:invalid,.pure-form select:focus:invalid{color:#b94a48;border-color:#e9322d}.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus,.pure-form input[type=checkbox]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{height:2.25em;border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=text],.pure-form-stacked input[type=password],.pure-form-stacked input[type=email],.pure-form-stacked input[type=url],.pure-form-stacked input[type=date],.pure-form-stacked input[type=month],.pure-form-stacked input[type=time],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=week],.pure-form-stacked input[type=number],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=color],.pure-form-stacked input[type=file],.pure-form-stacked select,.pure-form-stacked label,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned textarea,.pure-form-aligned select,.pure-form-aligned .pure-help-inline,.pure-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 11em}.pure-form input.pure-input-rounded,.pure-form .pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input,.pure-form .pure-group textarea{display:block;padding:10px;margin:0 0 -1px;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus,.pure-form .pure-group textarea:focus{z-index:3}.pure-form .pure-group input:first-child,.pure-form .pure-group textarea:first-child{top:1px;border-radius:4px 4px 0 0;margin:0}.pure-form .pure-group input:first-child:last-child,.pure-form .pure-group textarea:first-child:last-child{top:1px;border-radius:4px;margin:0}.pure-form .pure-group input:last-child,.pure-form .pure-group textarea:last-child{top:-2px;border-radius:0 0 4px 4px;margin:0}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form .pure-help-inline,.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=text],.pure-group input[type=password],.pure-group input[type=email],.pure-group input[type=url],.pure-group input[type=date],.pure-group input[type=month],.pure-group input[type=time],.pure-group input[type=datetime],.pure-group input[type=datetime-local],.pure-group input[type=week],.pure-group input[type=number],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=color]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form .pure-help-inline,.pure-form-message-inline,.pure-form-message{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-menu-fixed{position:fixed;left:0;top:0;z-index:3}.pure-menu-list,.pure-menu-item{position:relative}.pure-menu-list{list-style:none;margin:0;padding:0}.pure-menu-item{padding:0;margin:0;height:100%}.pure-menu-link,.pure-menu-heading{display:block;text-decoration:none;white-space:nowrap}.pure-menu-horizontal{width:100%;white-space:nowrap}.pure-menu-horizontal .pure-menu-list{display:inline-block}.pure-menu-horizontal .pure-menu-item,.pure-menu-horizontal .pure-menu-heading,.pure-menu-horizontal .pure-menu-separator{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu-item .pure-menu-item{display:block}.pure-menu-children{display:none;position:absolute;left:100%;top:0;margin:0;padding:0;z-index:3}.pure-menu-horizontal .pure-menu-children{left:0;top:auto;width:inherit}.pure-menu-allow-hover:hover>.pure-menu-children,.pure-menu-active>.pure-menu-children{display:block;position:absolute}.pure-menu-has-children>.pure-menu-link:after{padding-left:.5em;content:"\25B8";font-size:small}.pure-menu-horizontal .pure-menu-has-children>.pure-menu-link:after{content:"\25BE"}.pure-menu-scrollable{overflow-y:scroll;overflow-x:hidden}.pure-menu-scrollable .pure-menu-list{display:block}.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list{display:inline-block}.pure-menu-horizontal.pure-menu-scrollable{white-space:nowrap;overflow-y:hidden;overflow-x:auto;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;padding:.5em 0}.pure-menu-horizontal.pure-menu-scrollable::-webkit-scrollbar{display:none}.pure-menu-separator{background-color:#ccc;height:1px;margin:.3em 0}.pure-menu-horizontal .pure-menu-separator{width:1px;height:1.3em;margin:0 .3em}.pure-menu-heading{text-transform:uppercase;color:#565d64}.pure-menu-link{color:#777}.pure-menu-children{background-color:#fff}.pure-menu-link,.pure-menu-disabled,.pure-menu-heading{padding:.5em 1em}.pure-menu-disabled{opacity:.5}.pure-menu-disabled .pure-menu-link:hover{background-color:transparent}.pure-menu-active>.pure-menu-link,.pure-menu-link:hover,.pure-menu-link:focus{background-color:#eee}.pure-menu-selected .pure-menu-link,.pure-menu-selected .pure-menu-link:visited{color:#000}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table td:first-child,.pure-table th:first-child{border-left-width:0}.pure-table thead{background-color:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child>td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child>td{border-bottom-width:0}body { + color: #777; +} + +.pure-img-responsive { + max-width: 100%; + height: auto; +} + +/* +Add transition to containers so they can push in and out. +*/ +#layout, +#menu, +.menu-link { + -webkit-transition: all 0.2s ease-out; + -moz-transition: all 0.2s ease-out; + -ms-transition: all 0.2s ease-out; + -o-transition: all 0.2s ease-out; + transition: all 0.2s ease-out; +} + +/* +This is the parent `
` that contains the menu and the content area. +*/ +#layout { + position: relative; + padding-left: 0; +} + #layout.active #menu { + left: 150px; + width: 150px; + } + + #layout.active .menu-link { + left: 150px; + } +/* +The content `
` is where all your content goes. +*/ +.content { + margin: 0 auto; + padding: 0 2em; + max-width: 800px; + margin-bottom: 50px; + line-height: 1.6em; +} + +.header { + margin: 0; + color: #333; + text-align: center; + padding: 2.5em 2em 0; + border-bottom: 1px solid #eee; + } + .header h1 { + margin: 0.2em 0; + font-size: 3em; + font-weight: 300; + } + .header h2 { + font-weight: 300; + color: #ccc; + padding: 0; + margin-top: 0; + } + +.content-subhead { + margin: 50px 0 20px 0; + font-weight: 300; + color: #888; +} + + + +/* +The `#menu` `
` is the parent `
` that contains the `.pure-menu` that +appears on the left side of the page. +*/ + +#menu { + margin-left: -150px; /* "#menu" width */ + width: 150px; + position: fixed; + top: 0; + left: 0; + bottom: 0; + z-index: 1000; /* so the menu or its navicon stays above all content */ + background: #191818; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} + /* + All anchors inside the menu should be styled like this. + */ + #menu a { + color: #999; + border: none; + padding: 0.6em 0 0.6em 0.6em; + } + + /* + Remove all background/borders, since we are applying them to #menu. + */ + #menu .pure-menu, + #menu .pure-menu ul { + border: none; + background: transparent; + } + + /* + Add that light border to separate items into groups. + */ + #menu .pure-menu ul, + #menu .pure-menu .menu-item-divided { + border-top: 1px solid #333; + } + /* + Change color of the anchor links on hover/focus. + */ + #menu .pure-menu li a:hover, + #menu .pure-menu li a:focus { + background: #333; + } + + /* + This styles the selected menu item `
  • `. + */ + #menu .pure-menu-selected, + #menu .pure-menu-heading { + background: #1f8dd6; + } + /* + This styles a link within a selected menu item `
  • `. + */ + #menu .pure-menu-selected a { + color: #fff; + } + + /* + This styles the menu heading. + */ + #menu .pure-menu-heading { + font-size: 110%; + color: #fff; + margin: 0; + } + +/* -- Dynamic Button For Responsive Menu -------------------------------------*/ + +/* +The button to open/close the Menu is custom-made and not part of Pure. Here's +how it works: +*/ + +/* +`.menu-link` represents the responsive menu toggle that shows/hides on +small screens. +*/ +.menu-link { + position: fixed; + display: block; /* show this only on small screens */ + top: 0; + left: 0; /* "#menu width" */ + background: #000; + background: rgba(0,0,0,0.7); + font-size: 10px; /* change this value to increase/decrease button size */ + z-index: 10; + width: 2em; + height: auto; + padding: 2.1em 1.6em; +} + + .menu-link:hover, + .menu-link:focus { + background: #000; + } + + .menu-link span { + position: relative; + display: block; + } + + .menu-link span, + .menu-link span:before, + .menu-link span:after { + background-color: #fff; + width: 100%; + height: 0.2em; + } + + .menu-link span:before, + .menu-link span:after { + position: absolute; + margin-top: -0.6em; + content: " "; + } + + .menu-link span:after { + margin-top: 0.6em; + } + + +/* -- Responsive Styles (Media Queries) ------------------------------------- */ + +/* +Hides the menu at `48em`, but modify this based on your app's needs. +*/ +@media (min-width: 48em) { + + .header, + .content { + padding-left: 2em; + padding-right: 2em; + } + + #layout { + padding-left: 150px; /* left col width "#menu" */ + left: 0; + } + #menu { + left: 150px; + } + + .menu-link { + position: fixed; + left: 150px; + display: none; + } + + #layout.active .menu-link { + left: 150px; + } +} + +@media (max-width: 48em) { + /* Only apply this when the window is small. Otherwise, the following + case results in extra padding on the left: + * Make the window small. + * Tap the menu to trigger the active state. + * Make the window large again. + */ + #layout.active { + position: relative; + left: 150px; + } +} diff --git a/webui/index.html b/webui/index.html index 8d82aeb0..8ddbdda3 100644 --- a/webui/index.html +++ b/webui/index.html @@ -4,8 +4,94 @@ Purple I2P 0.10.0 Webconsole + + + + -

    Nothing here yet.

    +
    +

    i2pd router console

    +

    Version: , uptime:

    +

    Network status:

    +

    + + + +

    +
    + +
    +

    Tunnels participating:

    +

    Active peers:

    +

    Known peers:

    +

    Bandwidth: + in Bps / + out Bps +

    +
    + +
    +

    I2P configuration

    +
    + +
    +

    Not yet implemented :)

    +
    + + +
    + + + + + + +
    + +
    +
    diff --git a/webui/javascript/I2PControl.js b/webui/javascript/I2PControl.js new file mode 100644 index 00000000..6473ad3b --- /dev/null +++ b/webui/javascript/I2PControl.js @@ -0,0 +1,84 @@ +var I2PControl = I2PControl || {} + +I2PControl.Session = function(password) { + this.token = ""; + this.ready = false; + this.error = false; + this.password = password; +}; + +I2PControl.Session.prototype = { + + request : function(method, params, handler) { + var request = new XMLHttpRequest(); + request.open("POST", "", true); + request.setRequestHeader('Content-Type', 'application/json'); + var self = this; + request.onreadystatechange = function() { + if(this.readyState == 4 && this.status == "200" && this.responseText != "") { + var result = JSON.parse(this.responseText).result; + if(result.hasOwnProperty("error")) { + self.error = true; + } + handler(result, self); + } + }; + if(this.token != "") + params["Token"] = this.token; + + var rpc = { + "id" : 0, + "method" : method , + "params" : params, + "jsonrpc": "2.0" + } + request.send(JSON.stringify(rpc)); + }, + + start : function(onReady) { + var self = this; + + var handleAuthenticate = function(result) { + self.token = result["Token"]; + self.ready = true; + onReady(); + }; + + this.request( + "Authenticate", + {"API" : 1, "Password" : this.password}, + handleAuthenticate + ); + }, + +}; + +I2PControl.statusToString = function(status) { + switch(status) { + case 0: return "OK"; + case 1: return "TESTING"; + case 2: return "FIREWALLED"; + case 3: return "HIDDEN"; + case 4: return "WARN_FIREWALLED_AND_FAST"; + case 5: return "WARN_FIREWALLED_AND_FLOODFILL"; + case 6: return "WARN_FIREWALLED_WITH_INBOUND_TCP"; + case 7: return "WARN_FIREWALLED_WITH_UDP_DISABLED"; + case 8: return "ERROR_I2CP"; + case 9: return "ERROR_CLOCK_SKEW"; + case 10: return "ERROR_PRIVATE_TCP_ADDRESS"; + case 11: return "ERROR_SYMMETRIC_NAT"; + case 12: return "ERROR_UDP_PORT_IN_USE"; + case 13: return "ERROR_NO_ACTIVE_PEERS_CHECK_CONNECTION_AND_FIREWALL"; + case 14: return "ERROR_UDP_DISABLED_AND_TCP_UNSET"; + default: return "UNKNOWN"; + } +}; + +I2PControl.updateDocument = function(values) { + + for(id in values) { + if(!values.hasOwnProperty(id)) + continue; + document.getElementById(id).innerHTML = values[id]; + } +}; From 4d6d0321156c01d46a14e5dba31fe2883e10fe56 Mon Sep 17 00:00:00 2001 From: EinMByte Date: Mon, 7 Sep 2015 13:41:48 +0200 Subject: [PATCH 08/31] Add more information to webui. --- webui/index.html | 15 ++++++++++++++- webui/javascript/I2PControl.js | 10 ++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/webui/index.html b/webui/index.html index 8ddbdda3..2c8392ac 100644 --- a/webui/index.html +++ b/webui/index.html @@ -13,6 +13,12 @@ function updateRouterInfo(result, session) { I2PControl.updateDocument({ "version" : result["i2p.router.version"], "status" : I2PControl.statusToString(result["i2p.router.net.status"]), + "uptime" : I2PControl.msToString(result["i2p.router.uptime"]), + "knownpeers" : result["i2p.router.netdb.knownpeers"], + "activepeers" : result["i2p.router.netdb.activepeers"], + "tunnels-participating" : result["i2p.router.net.tunnels.participating"], + "bw-in" : result["i2p.router.net.bw.inbound.1s"], + "bw-out" : result["i2p.router.net.bw.outbound.1s"] }); } @@ -20,7 +26,14 @@ window.onload = function() { var session = new I2PControl.Session("itoopie"); session.start(function() { session.request("RouterInfo", { - "i2p.router.version" : "", "i2p.router.net.status" : "" + "i2p.router.version" : "", + "i2p.router.net.status" : "", + "i2p.router.uptime" : "", + "i2p.router.netdb.knownpeers" : "", + "i2p.router.netdb.activepeers" : "", + "i2p.router.net.tunnels.participating" : "", + "i2p.router.net.bw.inbound.1s" : "", + "i2p.router.net.bw.outbound.1s" : "" }, updateRouterInfo); }); }; diff --git a/webui/javascript/I2PControl.js b/webui/javascript/I2PControl.js index 6473ad3b..120a23a5 100644 --- a/webui/javascript/I2PControl.js +++ b/webui/javascript/I2PControl.js @@ -74,6 +74,16 @@ I2PControl.statusToString = function(status) { } }; +I2PControl.msToString = function(mseconds) { + var seconds = mseconds / 1000; + var numdays = Math.floor(seconds / 86400); + var numhours = Math.floor((seconds % 86400) / 3600); + var numminutes = Math.floor(((seconds % 86400) % 3600) / 60); + var numseconds = ((seconds % 86400) % 3600) % 60; + + return numdays + "d " + numhours + "h " + numminutes + "m " + numseconds + "s"; +} + I2PControl.updateDocument = function(values) { for(id in values) { From d3cede7995487b8974a686ded1e20644b7e85540 Mon Sep 17 00:00:00 2001 From: EinMByte Date: Sat, 12 Sep 2015 14:19:12 +0200 Subject: [PATCH 09/31] Fix operating system detection in util.cpp. --- core/util/util.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/util/util.cpp b/core/util/util.cpp index a576bd09..870a771d 100644 --- a/core/util/util.cpp +++ b/core/util/util.cpp @@ -208,7 +208,7 @@ namespace filesystem #ifdef I2PD_CUSTOM_DATA_PATH return boost::filesystem::path(std::string(I2PD_CUSTOM_DATA_PATH)); #else -#ifdef WIN32 +#ifdef _WIN32 // Windows char localAppData[MAX_PATH]; SHGetFolderPath(NULL, CSIDL_APPDATA, 0, NULL, localAppData); @@ -220,7 +220,7 @@ namespace filesystem pathRet = boost::filesystem::path("/"); else pathRet = boost::filesystem::path(pszHome); -#ifdef MAC_OSX +#ifdef __APPLE__ // Mac pathRet /= "Library/Application Support"; boost::filesystem::create_directory(pathRet); From 0c376117b0510e7010740892fd84f6a6c192fc76 Mon Sep 17 00:00:00 2001 From: EinMByte Date: Sat, 12 Sep 2015 14:43:30 +0200 Subject: [PATCH 10/31] Simplify CSS for webui. --- webui/css/main.css | 270 ++++++++------------------------------------- webui/index.html | 76 +++++++------ 2 files changed, 84 insertions(+), 262 deletions(-) diff --git a/webui/css/main.css b/webui/css/main.css index b6f47251..d7bcecf1 100644 --- a/webui/css/main.css +++ b/webui/css/main.css @@ -1,257 +1,73 @@ -/*! -Pure v0.6.0 -Copyright 2014 Yahoo! Inc. All rights reserved. -Licensed under the BSD License. -https://github.com/yahoo/pure/blob/master/LICENSE.md -*/ -/*! -normalize.css v^3.0 | MIT License | git.io/normalize -Copyright (c) Nicolas Gallagher and Jonathan Neal -*/ -/*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap;-ms-align-content:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-1-12,.pure-u-2-24{width:8.3333%;*width:8.3023%}.pure-u-1-8,.pure-u-3-24{width:12.5%;*width:12.469%}.pure-u-1-6,.pure-u-4-24{width:16.6667%;*width:16.6357%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-1-4,.pure-u-6-24{width:25%;*width:24.969%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-1-3,.pure-u-8-24{width:33.3333%;*width:33.3023%}.pure-u-3-8,.pure-u-9-24{width:37.5%;*width:37.469%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-5-12,.pure-u-10-24{width:41.6667%;*width:41.6357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-1-2,.pure-u-12-24{width:50%;*width:49.969%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-7-12,.pure-u-14-24{width:58.3333%;*width:58.3023%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-5-8,.pure-u-15-24{width:62.5%;*width:62.469%}.pure-u-2-3,.pure-u-16-24{width:66.6667%;*width:66.6357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-3-4,.pure-u-18-24{width:75%;*width:74.969%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-5-6,.pure-u-20-24{width:83.3333%;*width:83.3023%}.pure-u-7-8,.pure-u-21-24{width:87.5%;*width:87.469%}.pure-u-11-12,.pure-u-22-24{width:91.6667%;*width:91.6357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%}.pure-button{display:inline-block;zoom:1;line-height:normal;white-space:nowrap;vertical-align:middle;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button{font-family:inherit;font-size:100%;padding:.5em 1em;color:#444;color:rgba(0,0,0,.8);border:1px solid #999;border:0 rgba(0,0,0,0);background-color:#E6E6E6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:hover,.pure-button:focus{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000', GradientType=0);background-image:-webkit-gradient(linear,0 0,0 100%,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:-moz-linear-gradient(top,rgba(0,0,0,.05) 0,rgba(0,0,0,.1));background-image:-o-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;border-color:#000\9}.pure-button[disabled],.pure-button-disabled,.pure-button-disabled:hover,.pure-button-disabled:focus,.pure-button-disabled:active{border:0;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);filter:alpha(opacity=40);-khtml-opacity:.4;-moz-opacity:.4;opacity:.4;cursor:not-allowed;box-shadow:none}.pure-button-hidden{display:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;vertical-align:middle;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=text]:focus,.pure-form input[type=password]:focus,.pure-form input[type=email]:focus,.pure-form input[type=url]:focus,.pure-form input[type=date]:focus,.pure-form input[type=month]:focus,.pure-form input[type=time]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=week]:focus,.pure-form input[type=number]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=color]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;border-color:#129FEA}.pure-form input:not([type]):focus{outline:0;border-color:#129FEA}.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus,.pure-form input[type=checkbox]:focus{outline:thin solid #129FEA;outline:1px auto #129FEA}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=text][disabled],.pure-form input[type=password][disabled],.pure-form input[type=email][disabled],.pure-form input[type=url][disabled],.pure-form input[type=date][disabled],.pure-form input[type=month][disabled],.pure-form input[type=time][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=week][disabled],.pure-form input[type=number][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=color][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background-color:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form textarea:focus:invalid,.pure-form select:focus:invalid{color:#b94a48;border-color:#e9322d}.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus,.pure-form input[type=checkbox]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{height:2.25em;border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=text],.pure-form-stacked input[type=password],.pure-form-stacked input[type=email],.pure-form-stacked input[type=url],.pure-form-stacked input[type=date],.pure-form-stacked input[type=month],.pure-form-stacked input[type=time],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=week],.pure-form-stacked input[type=number],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=color],.pure-form-stacked input[type=file],.pure-form-stacked select,.pure-form-stacked label,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned textarea,.pure-form-aligned select,.pure-form-aligned .pure-help-inline,.pure-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 11em}.pure-form input.pure-input-rounded,.pure-form .pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input,.pure-form .pure-group textarea{display:block;padding:10px;margin:0 0 -1px;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus,.pure-form .pure-group textarea:focus{z-index:3}.pure-form .pure-group input:first-child,.pure-form .pure-group textarea:first-child{top:1px;border-radius:4px 4px 0 0;margin:0}.pure-form .pure-group input:first-child:last-child,.pure-form .pure-group textarea:first-child:last-child{top:1px;border-radius:4px;margin:0}.pure-form .pure-group input:last-child,.pure-form .pure-group textarea:last-child{top:-2px;border-radius:0 0 4px 4px;margin:0}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form .pure-help-inline,.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=text],.pure-group input[type=password],.pure-group input[type=email],.pure-group input[type=url],.pure-group input[type=date],.pure-group input[type=month],.pure-group input[type=time],.pure-group input[type=datetime],.pure-group input[type=datetime-local],.pure-group input[type=week],.pure-group input[type=number],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=color]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form .pure-help-inline,.pure-form-message-inline,.pure-form-message{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-menu-fixed{position:fixed;left:0;top:0;z-index:3}.pure-menu-list,.pure-menu-item{position:relative}.pure-menu-list{list-style:none;margin:0;padding:0}.pure-menu-item{padding:0;margin:0;height:100%}.pure-menu-link,.pure-menu-heading{display:block;text-decoration:none;white-space:nowrap}.pure-menu-horizontal{width:100%;white-space:nowrap}.pure-menu-horizontal .pure-menu-list{display:inline-block}.pure-menu-horizontal .pure-menu-item,.pure-menu-horizontal .pure-menu-heading,.pure-menu-horizontal .pure-menu-separator{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu-item .pure-menu-item{display:block}.pure-menu-children{display:none;position:absolute;left:100%;top:0;margin:0;padding:0;z-index:3}.pure-menu-horizontal .pure-menu-children{left:0;top:auto;width:inherit}.pure-menu-allow-hover:hover>.pure-menu-children,.pure-menu-active>.pure-menu-children{display:block;position:absolute}.pure-menu-has-children>.pure-menu-link:after{padding-left:.5em;content:"\25B8";font-size:small}.pure-menu-horizontal .pure-menu-has-children>.pure-menu-link:after{content:"\25BE"}.pure-menu-scrollable{overflow-y:scroll;overflow-x:hidden}.pure-menu-scrollable .pure-menu-list{display:block}.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list{display:inline-block}.pure-menu-horizontal.pure-menu-scrollable{white-space:nowrap;overflow-y:hidden;overflow-x:auto;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;padding:.5em 0}.pure-menu-horizontal.pure-menu-scrollable::-webkit-scrollbar{display:none}.pure-menu-separator{background-color:#ccc;height:1px;margin:.3em 0}.pure-menu-horizontal .pure-menu-separator{width:1px;height:1.3em;margin:0 .3em}.pure-menu-heading{text-transform:uppercase;color:#565d64}.pure-menu-link{color:#777}.pure-menu-children{background-color:#fff}.pure-menu-link,.pure-menu-disabled,.pure-menu-heading{padding:.5em 1em}.pure-menu-disabled{opacity:.5}.pure-menu-disabled .pure-menu-link:hover{background-color:transparent}.pure-menu-active>.pure-menu-link,.pure-menu-link:hover,.pure-menu-link:focus{background-color:#eee}.pure-menu-selected .pure-menu-link,.pure-menu-selected .pure-menu-link:visited{color:#000}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table td:first-child,.pure-table th:first-child{border-left-width:0}.pure-table thead{background-color:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child>td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child>td{border-bottom-width:0}body { - color: #777; +html { + font-family: sans-serif; } -.pure-img-responsive { - max-width: 100%; - height: auto; +.header { + margin: 0px; + text-align: center; + padding: 2.5em 2em 0px; + border-bottom: 1px solid #EEE; } -/* -Add transition to containers so they can push in and out. -*/ -#layout, -#menu, -.menu-link { - -webkit-transition: all 0.2s ease-out; - -moz-transition: all 0.2s ease-out; - -ms-transition: all 0.2s ease-out; - -o-transition: all 0.2s ease-out; - transition: all 0.2s ease-out; +h1 { + color: #333; + font-weight: 300; } -/* -This is the parent `
    ` that contains the menu and the content area. -*/ -#layout { - position: relative; - padding-left: 0; +h2 { + color: #ccc; + font-weight: 300; } - #layout.active #menu { - left: 150px; - width: 150px; - } - #layout.active .menu-link { - left: 150px; - } -/* -The content `
    ` is where all your content goes. -*/ .content { - margin: 0 auto; - padding: 0 2em; + margin: 0px auto 50px; + padding: 0px 2em; max-width: 800px; - margin-bottom: 50px; line-height: 1.6em; } -.header { - margin: 0; - color: #333; - text-align: center; - padding: 2.5em 2em 0; - border-bottom: 1px solid #eee; - } - .header h1 { - margin: 0.2em 0; - font-size: 3em; - font-weight: 300; - } - .header h2 { - font-weight: 300; - color: #ccc; - padding: 0; - margin-top: 0; - } - .content-subhead { - margin: 50px 0 20px 0; + margin: 50px 0px 20px; font-weight: 300; color: #888; } - - -/* -The `#menu` `
    ` is the parent `
    ` that contains the `.pure-menu` that -appears on the left side of the page. -*/ - #menu { - margin-left: -150px; /* "#menu" width */ width: 150px; position: fixed; - top: 0; - left: 0; - bottom: 0; - z-index: 1000; /* so the menu or its navicon stays above all content */ - background: #191818; + top: 0px; + bottom: 0px; + z-index: 1000; + background: #191818 none repeat scroll 0% 0%; overflow-y: auto; - -webkit-overflow-scrolling: touch; + display: block; } - /* - All anchors inside the menu should be styled like this. - */ - #menu a { - color: #999; - border: none; - padding: 0.6em 0 0.6em 0.6em; - } - - /* - Remove all background/borders, since we are applying them to #menu. - */ - #menu .pure-menu, - #menu .pure-menu ul { - border: none; - background: transparent; - } - - /* - Add that light border to separate items into groups. - */ - #menu .pure-menu ul, - #menu .pure-menu .menu-item-divided { - border-top: 1px solid #333; - } - /* - Change color of the anchor links on hover/focus. - */ - #menu .pure-menu li a:hover, - #menu .pure-menu li a:focus { - background: #333; - } - - /* - This styles the selected menu item `
  • `. - */ - #menu .pure-menu-selected, - #menu .pure-menu-heading { - background: #1f8dd6; - } - /* - This styles a link within a selected menu item `
  • `. - */ - #menu .pure-menu-selected a { - color: #fff; - } - - /* - This styles the menu heading. - */ - #menu .pure-menu-heading { - font-size: 110%; - color: #fff; - margin: 0; - } - -/* -- Dynamic Button For Responsive Menu -------------------------------------*/ - -/* -The button to open/close the Menu is custom-made and not part of Pure. Here's -how it works: -*/ -/* -`.menu-link` represents the responsive menu toggle that shows/hides on -small screens. -*/ -.menu-link { - position: fixed; - display: block; /* show this only on small screens */ - top: 0; - left: 0; /* "#menu width" */ - background: #000; - background: rgba(0,0,0,0.7); - font-size: 10px; /* change this value to increase/decrease button size */ - z-index: 10; - width: 2em; - height: auto; - padding: 2.1em 1.6em; +.menu-heading { + background: #1f8dd6; + display: block; + padding: 10px; + color: white; + font-variant: small-caps; + font-size: 23px; + font-weight: bold; } - .menu-link:hover, - .menu-link:focus { - background: #000; - } - - .menu-link span { - position: relative; - display: block; - } - - .menu-link span, - .menu-link span:before, - .menu-link span:after { - background-color: #fff; - width: 100%; - height: 0.2em; - } - - .menu-link span:before, - .menu-link span:after { - position: absolute; - margin-top: -0.6em; - content: " "; - } - - .menu-link span:after { - margin-top: 0.6em; - } - - -/* -- Responsive Styles (Media Queries) ------------------------------------- */ - -/* -Hides the menu at `48em`, but modify this based on your app's needs. -*/ -@media (min-width: 48em) { - - .header, - .content { - padding-left: 2em; - padding-right: 2em; - } - - #layout { - padding-left: 150px; /* left col width "#menu" */ - left: 0; - } - #menu { - left: 150px; - } - - .menu-link { - position: fixed; - left: 150px; - display: none; - } +.menu-list { + list-style: none; + margin: 0; + padding: 0; +} - #layout.active .menu-link { - left: 150px; - } +.menu-item { + padding: 0; + margin: 0; + padding: 10px; } -@media (max-width: 48em) { - /* Only apply this when the window is small. Otherwise, the following - case results in extra padding on the left: - * Make the window small. - * Tap the menu to trigger the active state. - * Make the window large again. - */ - #layout.active { - position: relative; - left: 150px; - } +.menu-link { + text-decoration: none; + color: #ccc; + font-weight: bold; + font-size: 15px; } diff --git a/webui/index.html b/webui/index.html index 2c8392ac..240e6816 100644 --- a/webui/index.html +++ b/webui/index.html @@ -8,8 +8,10 @@ @@ -70,7 +77,7 @@ window.onload = function() {

    Not yet implemented :)

  • - -
    - - - - - -
    + -
    - -
    +
    + +
    From 1ec31125b0e8b95c38749c1e8d75fbafe441e44c Mon Sep 17 00:00:00 2001 From: EinMByte Date: Sun, 13 Sep 2015 16:54:55 +0200 Subject: [PATCH 11/31] Change WIN32 to _WIN32 in util.cpp. --- core/util/util.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/util/util.cpp b/core/util/util.cpp index 870a771d..650d32e6 100644 --- a/core/util/util.cpp +++ b/core/util/util.cpp @@ -18,7 +18,7 @@ #if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__OpenBSD__) #include #include -#elif defined(WIN32) +#elif defined(_WIN32) #include #include #include @@ -79,7 +79,7 @@ namespace config { strKey = strKey.substr(0, has_data); } -#ifdef WIN32 +#ifdef _WIN32 boost::to_lower(strKey); if(boost::algorithm::starts_with(strKey, "/")) strKey = "-" + strKey.substr(1); @@ -522,7 +522,7 @@ namespace net { return mtu; } -#elif defined(WIN32) +#elif defined(_WIN32) int GetMTUWindowsIpv4(sockaddr_in inputAddress, int fallback) { ULONG outBufLen = 0; From 17cd149e7f229d3ce6db7d6e0e04262987e548ab Mon Sep 17 00:00:00 2001 From: EinMByte Date: Thu, 17 Sep 2015 11:47:16 +0200 Subject: [PATCH 12/31] Several improvements to HTTPServer. --- client/HTTPServer.cpp | 32 +++++++---- client/HTTPServer.h | 1 - core/util/HTTP.cpp | 120 ++++++++++++++++++++++++++++++++++++++++-- core/util/HTTP.h | 23 ++++++++ tests/Utility.cpp | 37 +++++++++++++ webui/css/main.css | 6 ++- webui/help.html | 32 +++++++++++ webui/index.html | 38 +------------ webui/menu.html | 14 +++++ 9 files changed, 251 insertions(+), 52 deletions(-) create mode 100644 webui/help.html create mode 100644 webui/menu.html diff --git a/client/HTTPServer.cpp b/client/HTTPServer.cpp index 8aea3ae7..f4e5efff 100644 --- a/client/HTTPServer.cpp +++ b/client/HTTPServer.cpp @@ -63,7 +63,18 @@ void HTTPConnection::HandleReceive(const boost::system::error_code& e, std::size if(!e) { m_Buffer[nb_bytes] = 0; m_BufferLen = nb_bytes; - RunRequest(); + const std::string data = std::string(m_Buffer, m_Buffer + m_BufferLen); + if(!m_Request.hasData()) // New request + m_Request = i2p::util::http::Request(data); + else + m_Request.update(data); + + if(m_Request.isComplete()) { + RunRequest(); + m_Request.clear(); + } else { + Receive(); + } } else if(e != boost::asio::error::operation_aborted) Terminate(); } @@ -71,13 +82,13 @@ void HTTPConnection::HandleReceive(const boost::system::error_code& e, std::size void HTTPConnection::RunRequest() { try { - m_Request = i2p::util::http::Request(std::string(m_Buffer, m_Buffer + m_BufferLen)); if(m_Request.getMethod() == "GET") return HandleRequest(); if(m_Request.getHeader("Content-Type").find("application/json") != std::string::npos) return HandleI2PControlRequest(); } catch(...) { // Ignore the error for now, probably Content-Type doesn't exist + // Could also be invalid json data } // Unsupported method m_Reply = i2p::util::http::Response(502, ""); @@ -118,13 +129,15 @@ void HTTPConnection::HandleRequest() if(uri == "/") uri = "index.html"; - // Use cannonical to avoid .. or . in path - const std::string address = boost::filesystem::canonical( + // Use canonical to avoid .. or . in path + const boost::filesystem::path address = boost::filesystem::canonical( i2p::util::filesystem::GetWebuiDataDir() / uri, e - ).string(); + ); + + const std::string address_str = address.string(); - std::ifstream ifs(address); - if(e || !ifs || !isAllowed(address)) { + std::ifstream ifs(address_str); + if(e || !ifs || !isAllowed(address_str)) { m_Reply = i2p::util::http::Response(404, ""); return SendReply(); } @@ -136,12 +149,13 @@ void HTTPConnection::HandleRequest() ifs.read(&str[0], str.size()); ifs.close(); + str = i2p::util::http::preprocessContent(str, address.parent_path().string()); m_Reply = i2p::util::http::Response(200, str); // TODO: get rid of this hack, actually determine the MIME type - if(address.substr(address.find_last_of(".")) == ".css") + if(address_str.substr(address_str.find_last_of(".")) == ".css") m_Reply.setHeader("Content-Type", "text/css"); - else if(address.substr(address.find_last_of(".")) == ".js") + else if(address_str.substr(address_str.find_last_of(".")) == ".js") m_Reply.setHeader("Content-Type", "text/javascript"); else m_Reply.setHeader("Content-Type", "text/html"); diff --git a/client/HTTPServer.h b/client/HTTPServer.h index 9cc149fe..412f05fd 100644 --- a/client/HTTPServer.h +++ b/client/HTTPServer.h @@ -41,7 +41,6 @@ private: boost::asio::ip::tcp::socket* m_Socket; boost::asio::deadline_timer m_Timer; char m_Buffer[HTTP_CONNECTION_BUFFER_SIZE + 1]; - char m_StreamBuffer[HTTP_CONNECTION_BUFFER_SIZE + 1]; size_t m_BufferLen; util::http::Request m_Request; util::http::Response m_Reply; diff --git a/core/util/HTTP.cpp b/core/util/HTTP.cpp index 1f94c6c5..931e0b6a 100644 --- a/core/util/HTTP.cpp +++ b/core/util/HTTP.cpp @@ -1,6 +1,10 @@ #include "HTTP.h" #include -#include +#include +#include +#include +#include +#include "Log.h" namespace i2p { namespace util { @@ -19,20 +23,54 @@ void Request::parseHeaderLine(const std::string& line) headers[boost::trim_copy(line.substr(0, pos))] = boost::trim_copy(line.substr(pos + 1)); } +void Request::parseHeader(std::stringstream& ss) +{ + std::string line; + while(std::getline(ss, line) && !boost::trim_copy(line).empty()) + parseHeaderLine(line); + + has_header = boost::trim_copy(line).empty(); + if(!has_header) + header_part = line; + else + header_part = ""; +} + +void Request::setIsComplete() +{ + auto it = headers.find("Content-Length"); + if(it == headers.end()) { + // If Content-Length is not set, assume there is no more content + // TODO: Support chunked transfer, or explictly reject it + is_complete = true; + return; + } + const std::size_t length = std::stoi(it->second); + is_complete = content.size() >= length; +} + Request::Request(const std::string& data) { + if(!data.empty()) + has_data = true; + std::stringstream ss(data); + std::string line; std::getline(ss, line); + + // Assume the request line is always passed in one go parseRequestLine(line); - while(std::getline(ss, line) && !boost::trim_copy(line).empty()) - parseHeaderLine(line); + parseHeader(ss); - if(ss) { + if(has_header && ss) { const std::string current = ss.str(); content = current.substr(ss.tellg()); } + + if(has_header) + setIsComplete(); } std::string Request::getMethod() const @@ -65,6 +103,38 @@ std::string Request::getContent() const return content; } +bool Request::hasData() const +{ + return has_data; +} + +bool Request::isComplete() const +{ + return is_complete; +} + +void Request::clear() +{ + has_data = false; + has_header = false; + is_complete = false; +} + +void Request::update(const std::string& data) +{ + std::stringstream ss(header_part + data); + if(!has_header) + parseHeader(ss); + + if(has_header && ss) { + const std::string current = ss.str(); + content += current.substr(ss.tellg()); + } + + if(has_header) + setIsComplete(); +} + Response::Response(int status, const std::string& content) : status(status), content(content), headers() { @@ -115,6 +185,48 @@ void Response::setContentLength() setHeader("Content-Length", std::to_string(content.size())); } +std::string preprocessContent(const std::string& content, const std::string& path) +{ + const boost::filesystem::path directory(path); // Given path is assumed to be clean + + static const std::regex re( + "<\\!\\-\\-\\s*#include\\s+virtual\\s*\\=\\s*\"([^\"]*)\"\\s*\\-\\->" + ); + + boost::system::error_code e; + + std::string result; + + std::smatch match; + auto it = content.begin(); + while(std::regex_search(it, content.end(), match, re)) { + const auto last = it; + std::advance(it, match.position()); + result.append(last, it); + std::advance(it, match.length()); + + // Read the contents of the included file + std::ifstream ifs( + boost::filesystem::canonical(directory / std::string(match[1]), e).string() + ); + if(e || !ifs) + continue; + + std::string data; + ifs.seekg(0, ifs.end); + data.resize(ifs.tellg()); + ifs.seekg(0, ifs.beg); + ifs.read(&data[0], data.size()); + + result += data; + } + + // Append all of the remaining content + result.append(it, content.end()); + + return result; +} + } } } diff --git a/core/util/HTTP.h b/core/util/HTTP.h index a559938d..41e4d2af 100644 --- a/core/util/HTTP.h +++ b/core/util/HTTP.h @@ -3,6 +3,7 @@ #include #include +#include namespace i2p { namespace util { @@ -13,6 +14,10 @@ class Request { void parseRequestLine(const std::string& line); void parseHeaderLine(const std::string& line); + + void parseHeader(std::stringstream& ss); + + void setIsComplete(); public: Request() = default; @@ -33,13 +38,26 @@ public: std::string getContent() const; + bool hasData() const; + + bool isComplete() const; + + void clear(); + + void update(const std::string& data); + private: + std::string header_part; + std::string method; std::string uri; std::string host; std::string content; int port; std::map headers; + bool has_data; + bool has_header; + bool is_complete; }; class Response { @@ -69,6 +87,11 @@ private: std::map headers; }; +/** + * Handle server side includes. + */ +std::string preprocessContent(const std::string& content, const std::string& path); + } } } diff --git a/tests/Utility.cpp b/tests/Utility.cpp index 795fa517..c876d493 100644 --- a/tests/Utility.cpp +++ b/tests/Utility.cpp @@ -128,6 +128,43 @@ BOOST_AUTO_TEST_CASE(ParseHTTPRequestWithContent) BOOST_CHECK_EQUAL(req2.getContent(), "Random content.\r\nTest content."); } +BOOST_AUTO_TEST_CASE(ParseHTTPRequestWithPartialHeaders) +{ + Request req( + "GET /index.html HTTP/1.1\r\n" + "Host: local" + ); + BOOST_CHECK(req.hasData()); + BOOST_CHECK(!req.isComplete()); + BOOST_CHECK_EQUAL(req.getMethod(), "GET"); + req.update("host\r\n"); + BOOST_CHECK(req.isComplete()); + BOOST_CHECK_EQUAL(req.getHeader("Host"), "localhost"); + req.clear(); + BOOST_CHECK(!req.hasData()); +} + +BOOST_AUTO_TEST_CASE(ParseHTTPRequestHeadersFirst) +{ + Request req( + "GET /index.html HTTP/1.1\r\n" + "Content-Length: 5\r\n" + "Host: localhost\r\n\r\n" + ); + + BOOST_CHECK_EQUAL(req.getMethod(), "GET"); + BOOST_CHECK_EQUAL(req.getHeader("Content-Length"), "5"); + BOOST_CHECK_EQUAL(req.getHeader("Host"), "localhost"); + + BOOST_CHECK(!req.isComplete()); + req.update("ab"); + BOOST_CHECK(!req.isComplete()); + req.update("cde"); + BOOST_CHECK(req.isComplete()); + + BOOST_CHECK_EQUAL(req.getContent(), "abcde"); +} + BOOST_AUTO_TEST_CASE(HTTPResponseStatusMessage) { BOOST_CHECK_EQUAL(Response(0).getStatusMessage(), ""); diff --git a/webui/css/main.css b/webui/css/main.css index d7bcecf1..597bb9c2 100644 --- a/webui/css/main.css +++ b/webui/css/main.css @@ -1,5 +1,7 @@ -html { +body { font-family: sans-serif; + margin: 0px; + padding: 0px; } .header { @@ -41,6 +43,8 @@ h2 { background: #191818 none repeat scroll 0% 0%; overflow-y: auto; display: block; + margin: 0px; + padding: 0px; } .menu-heading { diff --git a/webui/help.html b/webui/help.html new file mode 100644 index 00000000..6949ae1b --- /dev/null +++ b/webui/help.html @@ -0,0 +1,32 @@ + + + +Purple I2P 0.10.0 Webconsole + + + + + + + +
    +

    I2P help

    +
    + +
    +

    Need help? Join us at IRC: #i2pd-dev at irc.freenode.net

    +

    + i2pd at GitHub +

    +

    I2P Project

    +
    + + + +
    + +
    + + diff --git a/webui/index.html b/webui/index.html index 240e6816..68eedd71 100644 --- a/webui/index.html +++ b/webui/index.html @@ -69,48 +69,12 @@ window.onload = function() {
    -
    -

    I2P configuration

    -
    - -
    -

    Not yet implemented :)

    -
    - -
    -
    -

    I2P help

    -
    - -
    -

    Need help? Join us at IRC: #i2pd-dev at irc.freenode.net

    -

    - i2pd at GitHub -

    -

    I2P Project

    -
    -
    - - +
    -
    diff --git a/webui/menu.html b/webui/menu.html new file mode 100644 index 00000000..b5c7d83b --- /dev/null +++ b/webui/menu.html @@ -0,0 +1,14 @@ + From 221e35022898db913859c2a04713f744e4ded105 Mon Sep 17 00:00:00 2001 From: EinMByte Date: Thu, 17 Sep 2015 11:59:04 +0200 Subject: [PATCH 13/31] Move MIME type detection to util/HTTP.cpp --- client/HTTPServer.cpp | 8 +------- core/util/HTTP.cpp | 13 +++++++++++++ core/util/HTTP.h | 5 +++++ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/client/HTTPServer.cpp b/client/HTTPServer.cpp index f4e5efff..1a73272c 100644 --- a/client/HTTPServer.cpp +++ b/client/HTTPServer.cpp @@ -152,13 +152,7 @@ void HTTPConnection::HandleRequest() str = i2p::util::http::preprocessContent(str, address.parent_path().string()); m_Reply = i2p::util::http::Response(200, str); - // TODO: get rid of this hack, actually determine the MIME type - if(address_str.substr(address_str.find_last_of(".")) == ".css") - m_Reply.setHeader("Content-Type", "text/css"); - else if(address_str.substr(address_str.find_last_of(".")) == ".js") - m_Reply.setHeader("Content-Type", "text/javascript"); - else - m_Reply.setHeader("Content-Type", "text/html"); + m_Reply.setHeader("Content-Type", i2p::util::http::getMimeType(address_str)); SendReply(); } diff --git a/core/util/HTTP.cpp b/core/util/HTTP.cpp index 931e0b6a..be8014e1 100644 --- a/core/util/HTTP.cpp +++ b/core/util/HTTP.cpp @@ -227,6 +227,19 @@ std::string preprocessContent(const std::string& content, const std::string& pat return result; } +std::string getMimeType(const std::string& filename) +{ + const std::string ext = filename.substr(filename.find_last_of(".")); + if(ext == ".css") + return "text/css"; + else if(ext == ".css") + return "text/javascript"; + else if(ext == ".html" || ext == ".htm") + return "text/html"; + else + return "application/octet-stream"; +} + } } } diff --git a/core/util/HTTP.h b/core/util/HTTP.h index 41e4d2af..3f6415dc 100644 --- a/core/util/HTTP.h +++ b/core/util/HTTP.h @@ -92,6 +92,11 @@ private: */ std::string preprocessContent(const std::string& content, const std::string& path); +/** + * @return the MIME type based on the extension of the given filename + */ +std::string getMimeType(const std::string& filename); + } } } From 30798f835efb67293c8ad04e0b7baaa01a466258 Mon Sep 17 00:00:00 2001 From: EinMByte Date: Thu, 17 Sep 2015 14:02:40 +0200 Subject: [PATCH 14/31] Fixes to the webui CSS --- webui/css/main.css | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/webui/css/main.css b/webui/css/main.css index 597bb9c2..8aa8b289 100644 --- a/webui/css/main.css +++ b/webui/css/main.css @@ -5,7 +5,7 @@ body { } .header { - margin: 0px; + margin: 0px 150px; text-align: center; padding: 2.5em 2em 0px; border-bottom: 1px solid #EEE; @@ -22,9 +22,8 @@ h2 { } .content { - margin: 0px auto 50px; + margin: 0px 150px auto; padding: 0px 2em; - max-width: 800px; line-height: 1.6em; } From 7e066f7f6914913534280ee98a6321c0c679b8d2 Mon Sep 17 00:00:00 2001 From: EinMByte Date: Thu, 17 Sep 2015 16:21:11 +0200 Subject: [PATCH 15/31] webui: reauthenticate when token expires --- webui/index.html | 4 ++-- webui/javascript/I2PControl.js | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/webui/index.html b/webui/index.html index 68eedd71..8517ce0f 100644 --- a/webui/index.html +++ b/webui/index.html @@ -9,8 +9,8 @@ + + + + +
    +

    i2pd router console

    +

    Network Database Information

    +
    + +
    +

    Active peers:

    +

    Known peers:

    +

    Floodfills:

    +
    +

    LeaseSets:

    +
    + + + + + + From f05419845bd9ef9d4e46ef0857a11449feba51e2 Mon Sep 17 00:00:00 2001 From: EinMByte Date: Thu, 17 Sep 2015 16:59:39 +0200 Subject: [PATCH 17/31] Move part of BUILD_NOTES.md to BUILDING.md --- doc/BUILDING.md | 14 +++++++++++++- doc/BUILD_NOTES.md | 13 ------------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/doc/BUILDING.md b/doc/BUILDING.md index 107f5939..a34046d8 100644 --- a/doc/BUILDING.md +++ b/doc/BUILDING.md @@ -15,7 +15,7 @@ The client should now reseed by itself. By default, the web console is located at http://localhost:7070/. -For a list of cmake options, see build/BUILD_NOTES.md +For a list of cmake options, see BUILD_NOTES.md Building Unit Tests =================== @@ -28,3 +28,15 @@ On Ubuntu/Debian based To build the tests, run $ cmake .. -DWITH_TESTS=ON + +CMake Options +============ +Available cmake options: + +* CMAKE_BUILD_TYPE -- build profile (Debug/Release) +* WITH_AESNI -- AES-NI support (ON/OFF) +* WITH_HARDENING -- enable hardening features (ON/OFF) (gcc only) +* WITH_TESTS -- build tests (ON/OFF) +* WITH_BENCHMARK -- build bechmarking code (ON/OFF) +* WITH_OPTIMIZE -- enable optimization flags (ON/OFF) (not for MSVC) +* I2PD_DATA_DIR -- directory where i2pd will store data diff --git a/doc/BUILD_NOTES.md b/doc/BUILD_NOTES.md index d4d289dd..e3130142 100644 --- a/doc/BUILD_NOTES.md +++ b/doc/BUILD_NOTES.md @@ -1,16 +1,3 @@ -Build notes -=========== - -Available cmake options: - -* CMAKE_BUILD_TYPE -- build profile (Debug/Release) -* WITH_AESNI -- AES-NI support (ON/OFF) -* WITH_HARDENING -- enable hardening features (ON/OFF) (gcc only) -* WITH_TESTS -- build tests (ON/OFF) -* WITH_BENCHMARK -- build bechmarking code (ON/OFF) -* WITH_OPTIMIZE -- enable optimization flags (ON/OFF) (not for MSVC) -* I2PD_DATA_DIR -- directory where i2pd will store data - Debian ------ From fbea1ea14272a8413324a9a876d060be1accfcf6 Mon Sep 17 00:00:00 2001 From: EinMByte Date: Thu, 17 Sep 2015 17:18:36 +0200 Subject: [PATCH 18/31] Remove obsolete includes and constants in HTTPServer --- client/HTTPServer.cpp | 29 +---------------------------- client/HTTPServer.h | 1 - 2 files changed, 1 insertion(+), 29 deletions(-) diff --git a/client/HTTPServer.cpp b/client/HTTPServer.cpp index 448695cd..fc045760 100644 --- a/client/HTTPServer.cpp +++ b/client/HTTPServer.cpp @@ -1,44 +1,17 @@ #include -#include -#include #include #include -#include "util/base64.h" #include "util/Log.h" #include "util/util.h" -#include "tunnel/Tunnel.h" -#include "tunnel/TransitTunnel.h" -#include "transport/Transports.h" -#include "NetworkDatabase.h" #include "util/I2PEndian.h" -#include "Streaming.h" -#include "Destination.h" -#include "RouterContext.h" -#include "ClientContext.h" #include "HTTPServer.h" -// For image and info -#include "version.h" - namespace i2p { namespace util { -const char HTTP_COMMAND_TUNNELS[] = "tunnels"; -const char HTTP_COMMAND_TRANSIT_TUNNELS[] = "transit_tunnels"; -const char HTTP_COMMAND_TRANSPORTS[] = "transports"; -const char HTTP_COMMAND_START_ACCEPTING_TUNNELS[] = "start_accepting_tunnels"; -const char HTTP_COMMAND_STOP_ACCEPTING_TUNNELS[] = "stop_accepting_tunnels"; -const char HTTP_COMMAND_LOCAL_DESTINATIONS[] = "local_destinations"; -const char HTTP_COMMAND_LOCAL_DESTINATION[] = "local_destination"; -const char HTTP_PARAM_BASE32_ADDRESS[] = "b32"; -const char HTTP_COMMAND_SAM_SESSIONS[] = "sam_sessions"; -const char HTTP_COMMAND_SAM_SESSION[] = "sam_session"; -const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; - HTTPConnection::HTTPConnection(boost::asio::ip::tcp::socket* socket, std::shared_ptr session) - : m_Socket(socket), m_Timer(socket->get_io_service()), - m_BufferLen(0), m_Session(session) + : m_Socket(socket), m_BufferLen(0), m_Session(session) { } diff --git a/client/HTTPServer.h b/client/HTTPServer.h index f8cd81d9..d47cd700 100644 --- a/client/HTTPServer.h +++ b/client/HTTPServer.h @@ -39,7 +39,6 @@ private: bool isAllowed(const std::string& address); private: boost::asio::ip::tcp::socket* m_Socket; - boost::asio::deadline_timer m_Timer; char m_Buffer[HTTP_CONNECTION_BUFFER_SIZE + 1]; size_t m_BufferLen; util::http::Request m_Request; From dbade8b56907bec9c5e51785022ec67883ee3f07 Mon Sep 17 00:00:00 2001 From: EinMByte Date: Thu, 17 Sep 2015 23:18:31 +0200 Subject: [PATCH 19/31] (Graceful) restart and reseed buttons in webui. --- webui/index.html | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/webui/index.html b/webui/index.html index 01fd8231..688551bd 100644 --- a/webui/index.html +++ b/webui/index.html @@ -44,6 +44,18 @@ function requestRouterInfo(session) { window.onload = function() { var session = new I2PControl.Session("itoopie"); session.start(function() { requestRouterInfo(session); }); + document.getElementById("shutdown").onclick = function() { + session.request("RouterManager", {"Shutdown" : ""}, function() {}); + this.disabled = true; + }; + document.getElementById("shutdown-graceful").onclick = function() { + session.request("RouterManager", {"ShutdownGraceful" : ""}, function() {}); + this.disabled = true; + }; + document.getElementById("reseed").onclick = function() { + session.request("RouterManager", {"Reseed" : ""}, function() {}); + this.disabled = true; + }; }; @@ -55,7 +67,8 @@ window.onload = function() {

    Version: , uptime:

    Network status:

    - + +

    From c741382fc91363053d43d6860c5005748d51f04a Mon Sep 17 00:00:00 2001 From: EinMByte Date: Fri, 18 Sep 2015 11:52:09 +0200 Subject: [PATCH 20/31] Added 404 page to the webui. --- client/HTTPServer.cpp | 63 ++++++++++++++++++++++++++++++++----------- client/HTTPServer.h | 9 ++++++- webui/404.html | 24 +++++++++++++++++ webui/help.html | 1 - webui/menu.html | 2 +- webui/netdb.html | 2 +- 6 files changed, 81 insertions(+), 20 deletions(-) create mode 100644 webui/404.html diff --git a/client/HTTPServer.cpp b/client/HTTPServer.cpp index fc045760..cff6256c 100644 --- a/client/HTTPServer.cpp +++ b/client/HTTPServer.cpp @@ -94,26 +94,41 @@ void HTTPConnection::HandleWriteReply(const boost::system::error_code& e) } } -void HTTPConnection::HandleRequest() +void HTTPConnection::Send404Reply() { - boost::system::error_code e; + try { + const std::string error_page = "404.html"; + m_Reply = i2p::util::http::Response(404, GetFileContents(error_page, true)); + m_Reply.setHeader("Content-Type", i2p::util::http::getMimeType(error_page)); + } catch(const std::runtime_error&) { + // Failed to load 404.html, assume the webui is incorrectly installed + m_Reply = i2p::util::http::Response(404, + "" + "Error: 404 - webui not installed" + "

    It looks like your webui installation is broken.

    " + "

    Run the following command to (re)install it:

    " + "
    ./i2pd --install /path/to/webui
    " + "

    The webui folder should come with the binaries.

    " + "" + ); + } + SendReply(); +} - std::string uri = m_Request.getUri(); - if(uri == "/") - uri = "index.html"; +std::string HTTPConnection::GetFileContents(const std::string& filename, bool preprocess) const +{ + boost::system::error_code e; - // Use canonical to avoid .. or . in path + // Use canonical to avoid .. or . in path const boost::filesystem::path address = boost::filesystem::canonical( - i2p::util::filesystem::GetWebuiDataDir() / uri, e + i2p::util::filesystem::GetWebuiDataDir() / filename, e ); const std::string address_str = address.string(); std::ifstream ifs(address_str); - if(e || !ifs || !isAllowed(address_str)) { - m_Reply = i2p::util::http::Response(404, ""); - return SendReply(); - } + if(e || !ifs || !isAllowed(address_str)) + throw std::runtime_error("Cannot load " + address_str + "."); std::string str; ifs.seekg(0, ifs.end); @@ -122,11 +137,27 @@ void HTTPConnection::HandleRequest() ifs.read(&str[0], str.size()); ifs.close(); - str = i2p::util::http::preprocessContent(str, address.parent_path().string()); - m_Reply = i2p::util::http::Response(200, str); + if(preprocess) + return i2p::util::http::preprocessContent(str, address.parent_path().string()); + else + return str; +} - m_Reply.setHeader("Content-Type", i2p::util::http::getMimeType(address_str)); - SendReply(); +void HTTPConnection::HandleRequest() +{ + + std::string uri = m_Request.getUri(); + if(uri == "/") + uri = "index.html"; + + try { + m_Reply = i2p::util::http::Response(200, GetFileContents(uri, true)); + m_Reply.setHeader("Content-Type", i2p::util::http::getMimeType(uri)); + SendReply(); + } catch(const std::runtime_error&) { + // Cannot open the file for some reason, send 404 + Send404Reply(); + } } void HTTPConnection::HandleI2PControlRequest() @@ -138,7 +169,7 @@ void HTTPConnection::HandleI2PControlRequest() SendReply(); } -bool HTTPConnection::isAllowed(const std::string& address) +bool HTTPConnection::isAllowed(const std::string& address) const { const std::size_t pos_dot = address.find_last_of('.'); const std::size_t pos_slash = address.find_last_of('/'); diff --git a/client/HTTPServer.h b/client/HTTPServer.h index d47cd700..a4209e2d 100644 --- a/client/HTTPServer.h +++ b/client/HTTPServer.h @@ -32,11 +32,18 @@ private: void HandleWriteReply(const boost::system::error_code& ecode); void SendReply(); + void Send404Reply(); + + /* + * @throw std::runtime_error when the file is not accessible + */ + std::string GetFileContents(const std::string& filename, bool preprocess) const; + void HandleRequest(); void HandleI2PControlRequest(); void ExtractParams(const std::string& str, std::map& params); - bool isAllowed(const std::string& address); + bool isAllowed(const std::string& address) const; private: boost::asio::ip::tcp::socket* m_Socket; char m_Buffer[HTTP_CONNECTION_BUFFER_SIZE + 1]; diff --git a/webui/404.html b/webui/404.html new file mode 100644 index 00000000..41174fe6 --- /dev/null +++ b/webui/404.html @@ -0,0 +1,24 @@ + + + +404 - Page not found + + + + + + + +
    +

    404 - Page not found

    +
    + +
    +

    The page you were looking for could not be found.

    +
    + + + + + + diff --git a/webui/help.html b/webui/help.html index d626ec16..09f519b2 100644 --- a/webui/help.html +++ b/webui/help.html @@ -5,7 +5,6 @@ - diff --git a/webui/menu.html b/webui/menu.html index 47c7e8b0..d73089e3 100644 --- a/webui/menu.html +++ b/webui/menu.html @@ -8,7 +8,7 @@ Network Database + diff --git a/webui/tunnels.html b/webui/tunnels.html new file mode 100644 index 00000000..0981116f --- /dev/null +++ b/webui/tunnels.html @@ -0,0 +1,66 @@ + + + +Purple I2P 0.10.0 Webconsole + + + + + + + + +
    +

    i2pd router console

    +

    Tunnel List

    +
    + +
    + + + + + + + + + +
    Tunnel IDStatusOverview
    +
    + + + + + + From 0c2830b9a52bd7e1da4ff84d3d13402a31e62f19 Mon Sep 17 00:00:00 2001 From: EinMByte Date: Fri, 18 Sep 2015 23:12:08 +0200 Subject: [PATCH 24/31] Fix webui tunnel status column. --- webui/tunnels.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webui/tunnels.html b/webui/tunnels.html index 0981116f..72d7c53c 100644 --- a/webui/tunnels.html +++ b/webui/tunnels.html @@ -22,7 +22,7 @@ function updateNetDbInfo(result, session) { var row = table.insertRow(table.rows.length); row.insertCell(0).appendChild(document.createTextNode(id)); - row.insertCell(1).appendChild(document.createTextNode(tunnel["status"] ? tunnel["status"] : "running")); + row.insertCell(1).appendChild(document.createTextNode(tunnel["state"] ? tunnel["state"] : "running")); row.insertCell(2).appendChild(document.createTextNode(tunnel["layout"])); } } From 633f71c145c513882965b5726802cfe5d29bd22e Mon Sep 17 00:00:00 2001 From: EinMByte Date: Mon, 21 Sep 2015 15:58:36 +0200 Subject: [PATCH 25/31] Display outbound tunnels in webui. --- webui/tunnels.html | 49 +++++++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/webui/tunnels.html b/webui/tunnels.html index 72d7c53c..53221f17 100644 --- a/webui/tunnels.html +++ b/webui/tunnels.html @@ -7,35 +7,45 @@ @@ -43,12 +53,23 @@ window.onload = function() {

    i2pd router console

    -

    Tunnel List

    +

    Tunnel Information

    - +

    Inbound Tunnels

    +
    + + + + + + + +
    Tunnel IDStatusOverview
    +

    Outbound Tunnels

    + From 99a4be498a5ddcfdce6ed2b67ac5ecdfcb40648c Mon Sep 17 00:00:00 2001 From: EinMByte Date: Mon, 21 Sep 2015 17:25:26 +0200 Subject: [PATCH 26/31] Properly escape backslash in CMakeLists (windows). --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 183c782f..8a186351 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -159,7 +159,7 @@ if(I2PD_DATA_PATH) # Using custom path, make sure the code knows about this add_definitions(-DI2PD_CUSTOM_DATA_PATH="${I2PD_DATA_PATH}") elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows") - set(I2PD_DATA_DIR "$ENV{APPDATA}\i2pd") + set(I2PD_DATA_DIR "$ENV{APPDATA}\\i2pd") elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") set(I2PD_DATA_DIR "$ENV{HOME}/Library/Application Support/i2pd") else() From 36c77080b652538f191b50f6177bd397d756492c Mon Sep 17 00:00:00 2001 From: EinMByte Date: Thu, 24 Sep 2015 22:47:44 +0200 Subject: [PATCH 27/31] Fixes needed for windows build (issue #270). --- client/DaemonWin32.cpp | 2 +- client/Win32Service.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/DaemonWin32.cpp b/client/DaemonWin32.cpp index 3f5d5b00..e260cbfa 100644 --- a/client/DaemonWin32.cpp +++ b/client/DaemonWin32.cpp @@ -4,7 +4,7 @@ #ifdef _WIN32 -#include "./Win32/Win32Service.h" +#include "Win32Service.h" namespace i2p { diff --git a/client/Win32Service.cpp b/client/Win32Service.cpp index c9189fac..90e909af 100644 --- a/client/Win32Service.cpp +++ b/client/Win32Service.cpp @@ -8,7 +8,7 @@ #include #include "Daemon.h" -#include "Log.h" +#include "util/Log.h" I2PService *I2PService::s_service = NULL; From 65252790e6ba401b6e8803e08adfcd18456f6c34 Mon Sep 17 00:00:00 2001 From: EinMByte Date: Sun, 27 Sep 2015 13:43:02 +0200 Subject: [PATCH 28/31] Fix #270. --- core/util/util.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/util/util.cpp b/core/util/util.cpp index b42d1962..abc14c2b 100644 --- a/core/util/util.cpp +++ b/core/util/util.cpp @@ -608,7 +608,7 @@ namespace net { LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; sockaddr_in* localInterfaceAddress = (sockaddr_in*) lpAddr; if(localInterfaceAddress->sin_addr.S_un.S_addr == inputAddress.sin_addr.S_un.S_addr) { - result = pAddresses->Mtu; + auto result = pAddresses->Mtu; FREE(pAddresses); return result; } @@ -670,7 +670,7 @@ namespace net { found_address = true; } } if (found_address) { - result = pAddresses->Mtu; + auto result = pAddresses->Mtu; FREE(pAddresses); pAddresses = nullptr; return result; From c9080f9f722434f53d77db98448d9e46082bce36 Mon Sep 17 00:00:00 2001 From: EinMByte Date: Sun, 27 Sep 2015 14:45:24 +0200 Subject: [PATCH 29/31] Changes to static build (related to #270). --- CMakeLists.txt | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a186351..0a014a58 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,17 +99,8 @@ endif() if(WITH_STATIC) set(Boost_USE_STATIC_LIBS ON) - set(Boost_USE_STATIC_RUNTIME ON) - if(WIN32) - # http://www.cmake.org/Wiki/CMake_FAQ#Dynamic_Replace - # Note that you might need to rebuild Crypto++ - foreach(flag_var CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE - CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) - if(${flag_var} MATCHES "/MD") - string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") - endif(${flag_var} MATCHES "/MD") - endforeach(flag_var) - else() + set(Boost_USE_STATIC_RUNTIME OFF) + if(NOT WIN32) set(CMAKE_FIND_LIBRARY_SUFFIXES .a) endif() @@ -126,10 +117,10 @@ elseif(NOT WIN32) # No need in -fPIC overhead for binary if not interested in library # HINT: revert c266cff CMakeLists.txt: compilation speed up set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") +else() # Not a static build + add_definitions(-DBOOST_ALL_DYN_LINK) endif() -add_definitions(-DBOOST_ALL_DYN_LINK) - find_package( Boost COMPONENTS system filesystem regex program_options date_time thread chrono REQUIRED From 73725774dc8ce7feaeb8dbeb4e55f6e73160df1b Mon Sep 17 00:00:00 2001 From: EinMByte Date: Sun, 27 Sep 2015 16:55:39 +0200 Subject: [PATCH 30/31] Minor bugfixes for windows. --- client/HTTPServer.cpp | 6 +++--- core/util/HTTP.cpp | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/client/HTTPServer.cpp b/client/HTTPServer.cpp index 6f0bf309..994f9b13 100644 --- a/client/HTTPServer.cpp +++ b/client/HTTPServer.cpp @@ -11,7 +11,7 @@ namespace util { HTTPConnection::HTTPConnection(boost::asio::ip::tcp::socket* socket, std::shared_ptr session) - : m_Socket(socket), m_BufferLen(0), m_Session(session) + : m_Socket(socket), m_BufferLen(0), m_Request(), m_Reply(), m_Session(session) { } @@ -128,7 +128,7 @@ std::string HTTPConnection::GetFileContents(const std::string& filename, bool pr const std::string address_str = address.string(); - std::ifstream ifs(address_str); + std::ifstream ifs(address_str, std::ios_base::in | std::ios_base::binary); if(e || !ifs || !isAllowed(address_str)) throw std::runtime_error("Cannot load " + address_str + "."); @@ -154,7 +154,7 @@ void HTTPConnection::HandleRequest() try { m_Reply = i2p::util::http::Response(200, GetFileContents(uri, true)); - m_Reply.setHeader("Content-Type", i2p::util::http::getMimeType(uri)); + m_Reply.setHeader("Content-Type", i2p::util::http::getMimeType(uri) + "; charset=UTF-8"); SendReply(); } catch(const std::runtime_error&) { // Cannot open the file for some reason, send 404 diff --git a/core/util/HTTP.cpp b/core/util/HTTP.cpp index 020d9776..ea8c9dff 100644 --- a/core/util/HTTP.cpp +++ b/core/util/HTTP.cpp @@ -207,7 +207,8 @@ std::string preprocessContent(const std::string& content, const std::string& pat // Read the contents of the included file std::ifstream ifs( - boost::filesystem::canonical(directory / std::string(match[1]), e).string() + boost::filesystem::canonical(directory / std::string(match[1]), e).string(), + std::ios_base::in | std::ios_base::binary ); if(e || !ifs) continue; From b9e25f2c96bd051cfdc9dc6dfbda1ed44b9ca027 Mon Sep 17 00:00:00 2001 From: EinMByte Date: Sun, 27 Sep 2015 17:10:23 +0200 Subject: [PATCH 31/31] Remove orignal certificate. --- .../router/orignal_at_mail.i2p.crt | 31 ------------------- 1 file changed, 31 deletions(-) delete mode 100644 contrib/certificates/router/orignal_at_mail.i2p.crt diff --git a/contrib/certificates/router/orignal_at_mail.i2p.crt b/contrib/certificates/router/orignal_at_mail.i2p.crt deleted file mode 100644 index c1229f3b..00000000 --- a/contrib/certificates/router/orignal_at_mail.i2p.crt +++ /dev/null @@ -1,31 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFVDCCAzwCCQC2r1XWYtqtAzANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQGEwJY -WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMRMwEQYDVQQKDApQdXJwbGUgSTJQ -MQ0wCwYDVQQLDARJMlBEMR8wHQYJKoZIhvcNAQkBFhBvcmlnbmFsQG1haWwuaTJw -MB4XDTE1MDIyMjEzNTgxOFoXDTI1MDIxOTEzNTgxOFowbDELMAkGA1UEBhMCWFgx -CzAJBgNVBAgMAlhYMQswCQYDVQQHDAJYWDETMBEGA1UECgwKUHVycGxlIEkyUDEN -MAsGA1UECwwESTJQRDEfMB0GCSqGSIb3DQEJARYQb3JpZ25hbEBtYWlsLmkycDCC -AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALp3D/gdvFjrMm+IE8tHZCWE -hQ6Pp0CCgCGDBC3WQFLqR98bqVPl4UwRG/MKY/LY7Woai06JNmGcpfw0LMoNnHxT -bvKtDRe/8kQdhdLHhgIkWKSbMvTAl7uUdV6FzsPgDR0x7scoFVWEhkF0wfmzGF2V -yr/WCBQejFPu69z03m5tRQ8Xjp2txWV45RawUmFu50bgbZvLCSLfTkIvxmfJzgPN -pJ3sPa/g7TBZl2uEiAu4uaEKvTuuzStOWCGgFaHYFVlTfFXTvmhFMqHfaidtzrlu -H35WGrmIWTDl6uGPC5QkSppvkj73rDj5aEyPzWMz5DN3YeECoVSchN+OJJCM6m7+ -rLFYXghVEp2h+T9O1GBRfcHlQ2E3CrWWvxhmK8dfteJmd501dyNX2paeuIg/aPFO -54/8m2r11uyF29hgY8VWLdXtqvwhKuK36PCzofEwDp9QQX8GRsEV4pZTrn4bDhGo -kb9BF7TZTqtL3uyiRmIyBXrNNiYlA1Xm4fyKRtxl0mrPaUXdgdnCt3KxOAJ8WM2B -7L/kk9U8C/nexHbMxIZfTap49XcUg5dxSO9kOBosIOcCUms8sAzBPDV2tWAByhYF -jI/Tutbd3F0+fvcmTcIFOlGbOxKgO2SfwXjv/44g/3LMK6IAMFB9UOc8KhnnJP0f -uAHvMXn1ahRs4pM1VizLAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAIOxdaXT+wfu -nv/+1hy5T4TlRMNNsuj79ROcy6Mp+JwMG50HjTc0qTlXh8C7nHybDJn4v7DA+Nyn -RxT0J5I+Gqn+Na9TaC9mLeX/lwe8/KomyhBWxjrsyWj1V6v/cLO924S2rtcfzMDm -l3SFh9YHM1KF/R9N1XYBwtMzr3bupWDnE1yycYp1F4sMLr5SMzMQ0svQpQEM2/y5 -kly8+eUzryhm+ag9x1686uEG5gxhQ1eHQoZEaClHUOsV+28+d5If7cqcYx9Hf5Tt -CiVjJQzdxBF+6GeiJtKxnLtevqlkbyIJt6Cm9/7YIy/ovRGF2AKSYN6oCwmZQ6i1 -8nRnFq5zE7O94m+GXconWZxy0wVqA6472HThMi7S+Tk/eLYen2ilGY+KCb9a0FH5 -5MOuWSoJZ8/HfW2VeQmL8EjhWm5F2ybg28wgXK4BOGR3jQi03Fsc+AFidnWxSKo0 -aiJoPgOsfyu8/fnCcAi07kSmjzUKIWskApgcpGQLNXHFK9mtg7+VA8esRnfLlKtP -tJf+nNAPY1sqHfGBzh7WWGWal5RGHF5nEm3ta3oiFF5sMKCJ6C87zVwFkEcRytGC -xOGmiG1O1RPrO5NG7rZUaQ4y1OKl2Y1H+nGONzZ3mvoAOvxEq6JtUnU2kZscpPlk -fpeOSDoGBYJGbIpzDreBDhxaZrwGq36k ------END CERTIFICATE-----
    Tunnel ID Status