From 7c4450fdb117a57f135647260c9336a3735ea728 Mon Sep 17 00:00:00 2001 From: Seva <seva@binarytrails.net> Date: Tue, 27 Aug 2019 23:05:36 -0400 Subject: [PATCH] http: add url with tests, handle prefix --- CMakeLists.txt | 2 + include/opendht/http.h | 14 ++++ src/http.cpp | 38 +++++++++ tests/Makefile.am | 4 +- tests/httptester.cpp | 183 +++++++++++++++++++++++++++++++++++++++++ tests/httptester.h | 79 ++++++++++++++++++ 6 files changed, 318 insertions(+), 2 deletions(-) create mode 100644 tests/httptester.cpp create mode 100644 tests/httptester.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ce860b8..4f3008b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -362,6 +362,8 @@ if (OPENDHT_TESTS) ) if (OPENDHT_PROXY_SERVER AND OPENDHT_PROXY_CLIENT) list (APPEND test_FILES + tests/httptester.h + tests/httptester.cpp tests/dhtproxytester.h tests/dhtproxytester.cpp ) diff --git a/include/opendht/http.h b/include/opendht/http.h index 71a9d78b..cc1f675b 100644 --- a/include/opendht/http.h +++ b/include/opendht/http.h @@ -57,6 +57,19 @@ using ConnectHandlerCb = std::function<void(const asio::error_code& ec, using ssl_socket_t = restinio::impl::tls_socket_t; using socket_t = asio::ip::tcp::socket; +class OPENDHT_PUBLIC Url +{ +public: + Url(){}; + Url(const std::string& url); + std::string url; + std::string protocol {"http"}; + std::string host; + std::string service {"80"}; + std::string target {"/"}; + std::string query; +}; + class OPENDHT_PUBLIC Connection { public: @@ -126,6 +139,7 @@ public: using ResolverCb = std::function<void(const asio::error_code& ec, std::vector<asio::ip::tcp::endpoint> endpoints)>; + Resolver(asio::io_context& ctx, const std::string& url, std::shared_ptr<dht::Logger> logger = {}); Resolver(asio::io_context& ctx, const std::string& host, const std::string& service = "80", std::shared_ptr<dht::Logger> logger = {}); diff --git a/src/http.cpp b/src/http.cpp index 11f076b1..c1935a1d 100644 --- a/src/http.cpp +++ b/src/http.cpp @@ -34,6 +34,36 @@ constexpr char HTTP_HEADER_CONTENT_TYPE_JSON[] = "application/json"; constexpr char HTTP_HEADER_DELIM[] = "\r\n\r\n"; constexpr char JSON_VALUE_DELIM[] = "\n"; +Url::Url(const std::string& url): url(url) +{ + size_t addr_begin = 0; + // protocol + const size_t proto_end = url.find("://"); + if (proto_end != std::string::npos){ + addr_begin = proto_end + 3; + if (url.substr(0, proto_end) == "https") + protocol = "https"; + } + // host and service + size_t addr_size = url.substr(addr_begin).find("/"); + if (addr_size == std::string::npos) + addr_size = url.size() - addr_begin; + auto host_service = splitPort(url.substr(addr_begin, addr_size)); + host = host_service.first; + if (!host_service.second.empty()) + service = host_service.second; + // target, query + size_t query_begin = url.find("?"); + auto addr_end = addr_begin + addr_size; + if (addr_end < url.size()){ + if (query_begin == std::string::npos) + target = url.substr(addr_end); + else + target = url.substr(addr_end, query_begin - addr_end); + } + query = url.substr(query_begin + 1); +} + // connection unsigned int Connection::ids_ = 1; @@ -269,6 +299,14 @@ Connection::timeout(const std::chrono::seconds timeout, HandlerCb cb) // Resolver +Resolver::Resolver(asio::io_context& ctx, const std::string& url, std::shared_ptr<dht::Logger> logger) + : resolver_(ctx), logger_(logger) +{ + dht::http::Url http_url(url); + service_ = http_url.service; + resolve(http_url.host, http_url.service); +} + Resolver::Resolver(asio::io_context& ctx, const std::string& host, const std::string& service, std::shared_ptr<dht::Logger> logger) : resolver_(ctx), logger_(logger) diff --git a/tests/Makefile.am b/tests/Makefile.am index 252d7565..3515a15f 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -3,7 +3,7 @@ bin_PROGRAMS = opendht_unit_tests AM_CPPFLAGS = -I../include -nobase_include_HEADERS = infohashtester.h valuetester.h cryptotester.h dhtrunnertester.h dhtproxytester.h -opendht_unit_tests_SOURCES = tests_runner.cpp cryptotester.cpp infohashtester.cpp valuetester.cpp dhtrunnertester.cpp dhtproxytester.cpp +nobase_include_HEADERS = infohashtester.h valuetester.h cryptotester.h dhtrunnertester.h httptester.h dhtproxytester.h +opendht_unit_tests_SOURCES = tests_runner.cpp cryptotester.cpp infohashtester.cpp valuetester.cpp dhtrunnertester.cpp httptester.cpp dhtproxytester.cpp opendht_unit_tests_LDFLAGS = -lopendht -lcppunit -L@top_builddir@/src/.libs @GnuTLS_LIBS@ endif diff --git a/tests/httptester.cpp b/tests/httptester.cpp new file mode 100644 index 00000000..efc759c7 --- /dev/null +++ b/tests/httptester.cpp @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2019 Savoir-faire Linux Inc. + * + * Author: Vsevolod Ivanov <vsevolod.ivanov@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "httptester.h" + +// std +#include <iostream> +#include <string> + +#include <chrono> +#include <condition_variable> + + +namespace test { +CPPUNIT_TEST_SUITE_REGISTRATION(HttpTester); + +void +HttpTester::setUp() { + logger = dht::log::getStdLogger(); +} + +void +HttpTester::tearDown() { +} + +void +HttpTester::test_parse_url() { + // Arrange + std::string url = "http://google.com/"; + // Act + dht::http::Url parsed (url); + // Assert + CPPUNIT_ASSERT(parsed.url == url); + CPPUNIT_ASSERT(parsed.protocol == "http"); + CPPUNIT_ASSERT(parsed.host == "google.com"); + CPPUNIT_ASSERT(parsed.service == "80"); + CPPUNIT_ASSERT(parsed.target == "/"); +} + +void +HttpTester::test_parse_url_no_prefix_no_target() { + // Arrange + std::string url = "google.com"; + // Act + dht::http::Url parsed (url); + // Assert + CPPUNIT_ASSERT(parsed.url == url); + CPPUNIT_ASSERT(parsed.protocol == "http"); + CPPUNIT_ASSERT(parsed.host == "google.com"); + CPPUNIT_ASSERT(parsed.service == "80"); + CPPUNIT_ASSERT(parsed.target == "/"); +} + +void +HttpTester::test_parse_url_target() { + // Arrange + std::string url = "https://www.google.com:666/going/under"; + // Act + dht::http::Url parsed (url); + // Assert + CPPUNIT_ASSERT(parsed.url == url); + CPPUNIT_ASSERT(parsed.protocol == "https"); + CPPUNIT_ASSERT(parsed.host == "www.google.com"); + CPPUNIT_ASSERT(parsed.service == "666"); + CPPUNIT_ASSERT(parsed.target == "/going/under"); +} + +void +HttpTester::test_parse_url_query() { + // Arrange + std::string url = "http://google.com/?key=1"; + // Act + dht::http::Url parsed (url); + // Assert + CPPUNIT_ASSERT(parsed.url == url); + CPPUNIT_ASSERT(parsed.protocol == "http"); + CPPUNIT_ASSERT(parsed.host == "google.com"); + CPPUNIT_ASSERT(parsed.service == "80"); + CPPUNIT_ASSERT(parsed.target == "/"); + CPPUNIT_ASSERT(parsed.query == "key=1"); +} + +void +HttpTester::test_parse_url_ipv4() { + // Arrange + std::string url = "http://172.217.13.132/"; + // Act + dht::http::Url parsed (url); + // Assert + CPPUNIT_ASSERT(parsed.url == url); + CPPUNIT_ASSERT(parsed.protocol == "http"); + CPPUNIT_ASSERT(parsed.host == "172.217.13.132"); + CPPUNIT_ASSERT(parsed.service == "80"); + CPPUNIT_ASSERT(parsed.target == "/"); +} + +void +HttpTester::test_parse_url_no_prefix_no_target_ipv4() { + // Arrange + std::string url = "172.217.13.132"; + // Act + dht::http::Url parsed (url); + // Assert + CPPUNIT_ASSERT(parsed.url == url); + CPPUNIT_ASSERT(parsed.protocol == "http"); + CPPUNIT_ASSERT(parsed.host == "172.217.13.132"); + CPPUNIT_ASSERT(parsed.service == "80"); + CPPUNIT_ASSERT(parsed.target == "/"); +} + +void +HttpTester::test_parse_url_target_ipv4() { + // Arrange + std::string url = "https://172.217.13.132:666/going/under"; + // Act + dht::http::Url parsed (url); + // Assert + CPPUNIT_ASSERT(parsed.url == url); + CPPUNIT_ASSERT(parsed.protocol == "https"); + CPPUNIT_ASSERT(parsed.host == "172.217.13.132"); + CPPUNIT_ASSERT(parsed.service == "666"); + CPPUNIT_ASSERT(parsed.target == "/going/under"); +} + +void +HttpTester::test_parse_url_ipv6() { + // Arrange + std::string url = "http://[2607:f8b0:4006:804::2004]/"; + // Act + dht::http::Url parsed (url); + // Assert + CPPUNIT_ASSERT(parsed.url == url); + CPPUNIT_ASSERT(parsed.protocol == "http"); + CPPUNIT_ASSERT(parsed.host == "2607:f8b0:4006:804::2004"); + CPPUNIT_ASSERT(parsed.service == "80"); + CPPUNIT_ASSERT(parsed.target == "/"); +} + +void +HttpTester::test_parse_url_no_prefix_no_target_ipv6() { + // Arrange + std::string url = "2607:f8b0:4006:804::2004"; + // Act + dht::http::Url parsed (url); + // Assert + CPPUNIT_ASSERT(parsed.url == url); + CPPUNIT_ASSERT(parsed.protocol == "http"); + CPPUNIT_ASSERT(parsed.host == "2607:f8b0:4006:804::2004"); + CPPUNIT_ASSERT(parsed.service == "80"); + CPPUNIT_ASSERT(parsed.target == "/"); +} + +void +HttpTester::test_parse_url_target_ipv6() { + // Arrange + std::string url = "https://[2607:f8b0:4006:804::2004]:666/going/under"; + // Act + dht::http::Url parsed (url); + // Assert + CPPUNIT_ASSERT(parsed.url == url); + CPPUNIT_ASSERT(parsed.protocol == "https"); + CPPUNIT_ASSERT(parsed.host == "2607:f8b0:4006:804::2004"); + CPPUNIT_ASSERT(parsed.service == "666"); + CPPUNIT_ASSERT(parsed.target == "/going/under"); +} + +} // namespace test diff --git a/tests/httptester.h b/tests/httptester.h new file mode 100644 index 00000000..cf77258e --- /dev/null +++ b/tests/httptester.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2019 Savoir-faire Linux Inc. + * + * Author: Vsevolod Ivanov <vsevolod.ivanov@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +// cppunit +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +#include <opendht/log.h> +#include <opendht/http.h> + +namespace test { + +class HttpTester : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(HttpTester); + CPPUNIT_TEST(test_parse_url); + CPPUNIT_TEST(test_parse_url_no_prefix_no_target); + CPPUNIT_TEST(test_parse_url_target); + CPPUNIT_TEST(test_parse_url_query); + CPPUNIT_TEST(test_parse_url_ipv4); + CPPUNIT_TEST(test_parse_url_no_prefix_no_target_ipv4); + CPPUNIT_TEST(test_parse_url_target_ipv4); + CPPUNIT_TEST(test_parse_url_ipv6); + CPPUNIT_TEST(test_parse_url_no_prefix_no_target_ipv6); + CPPUNIT_TEST(test_parse_url_target_ipv6); + CPPUNIT_TEST_SUITE_END(); + + public: + /** + * Method automatically called before each test by CppUnit + * Init nodes + */ + void setUp(); + /** + * Method automatically called after each test CppUnit + */ + void tearDown(); + /** + * Test parse urls + */ + void test_parse_url(); + void test_parse_url_no_prefix_no_target(); + void test_parse_url_target(); + void test_parse_url_query(); + /** + * Test parse urls (ipv4) + */ + void test_parse_url_ipv4(); + void test_parse_url_no_prefix_no_target_ipv4(); + void test_parse_url_target_ipv4(); + /** + * Test parse urls (ipv6) + */ + void test_parse_url_ipv6(); + void test_parse_url_no_prefix_no_target_ipv6(); + void test_parse_url_target_ipv6(); + + private: + std::shared_ptr<dht::Logger> logger {}; +}; + +} // namespace test -- GitLab