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