From f93a18c9bdcd419a38301db889dd7fecd5e4e99c Mon Sep 17 00:00:00 2001 From: Yury Melnichek Date: Wed, 28 Nov 2012 17:50:24 +0100 Subject: [PATCH] [coding] Add Uri class. --- coding/coding.pro | 2 + coding/coding_tests/coding_tests.pro | 1 + coding/coding_tests/uri_test.cpp | 117 +++++++++++++++++++++++++++ coding/uri.cpp | 69 ++++++++++++++++ coding/uri.hpp | 36 +++++++++ 5 files changed, 225 insertions(+) create mode 100644 coding/coding_tests/uri_test.cpp create mode 100644 coding/uri.cpp create mode 100644 coding/uri.hpp diff --git a/coding/coding.pro b/coding/coding.pro index fac05657ed..dd977258f6 100644 --- a/coding/coding.pro +++ b/coding/coding.pro @@ -30,6 +30,7 @@ SOURCES += \ reader_writer_ops.cpp \ blob_indexer.cpp \ blob_storage.cpp \ + uri.cpp \ HEADERS += \ internal/xmlparser.h \ @@ -87,3 +88,4 @@ HEADERS += \ reader_wrapper.hpp \ blob_indexer.hpp \ blob_storage.hpp \ + uri.hpp \ diff --git a/coding/coding_tests/coding_tests.pro b/coding/coding_tests/coding_tests.pro index 2db011c1aa..1eb3c5edc7 100644 --- a/coding/coding_tests/coding_tests.pro +++ b/coding/coding_tests/coding_tests.pro @@ -39,6 +39,7 @@ SOURCES += ../../testing/testingmain.cpp \ trie_test.cpp \ reader_writer_ops_test.cpp \ blob_storage_test.cpp \ + uri_test.cpp \ HEADERS += \ reader_test.hpp \ diff --git a/coding/coding_tests/uri_test.cpp b/coding/coding_tests/uri_test.cpp new file mode 100644 index 0000000000..73b2073460 --- /dev/null +++ b/coding/coding_tests/uri_test.cpp @@ -0,0 +1,117 @@ +#include "../../testing/testing.hpp" +#include "../uri.hpp" +#include "../../base/macros.hpp" +#include "../../std/bind.hpp" +#include "../../std/queue.hpp" +#include "../../std/utility.hpp" + +using url_scheme::Uri; + +namespace +{ + +class TestUri +{ +public: + TestUri(string const & uri) { m_uri = uri; } + TestUri & Scheme(string const &scheme) { m_scheme = scheme; return *this; } + TestUri & Path(string const & path) { m_path = path; return *this; } + TestUri & KV(string const & key, string const & value) + { + m_keyValuePairs.push(make_pair(key, value)); + return *this; + } + + ~TestUri() + { + Uri uri(&m_uri[0], m_uri.size()); + TEST_EQUAL(uri.GetScheme(), m_scheme, ()); + TEST_EQUAL(uri.GetPath(), m_path, ()); + TEST(!m_scheme.empty() || !uri.IsValid(), ("Scheme is empty if and only if uri is invalid!")); + uri.ForEachKeyValue(bind(&TestUri::AddTestValue, this, _1, _2)); + } + +private: + + void AddTestValue(string const & key, string const & value) + { + TEST(!m_keyValuePairs.empty(), ("Check that key/value pair is expected")); + TEST_EQUAL(m_keyValuePairs.front().first, key, ()); + TEST_EQUAL(m_keyValuePairs.front().second, value, ()); + m_keyValuePairs.pop(); + } + + string m_uri, m_scheme, m_path; + queue > m_keyValuePairs; +}; + +} // unnamed namespace + +UNIT_TEST(UriValidScheme) +{ + char const uriS[] = "mapswithme://map?ll=10.3,12.3223&n=Hello%20World"; + Uri uri(uriS, ARRAY_SIZE(uriS) - 1); + TEST_EQUAL(uri.GetScheme(), "mapswithme", ()); +} + +UNIT_TEST(UriInvalidSchemeNoColon) +{ + TEST_EQUAL(Uri("mapswithme:").GetScheme(), "mapswithme", ()); +} + +UNIT_TEST(UriTestValidScheme2) +{ + TestUri("mapswithme://map?ll=10.3,12.3223&n=Hello%20World") + .Scheme("mapswithme") + .Path("map") + .KV("ll", "10.3,12.3223") + .KV("n", "Hello World"); +} + +UNIT_TEST(UriComprehensive) +{ + TestUri(""); + + TestUri("scheme:").Scheme("scheme"); + + TestUri("scheme:/").Scheme("scheme"); + + TestUri("scheme://").Scheme("scheme"); + + TestUri("sometext"); + + TestUri(":noscheme"); + + TestUri("://noscheme?"); + + TestUri("mwm://?").Scheme("mwm"); + + TestUri("http://path/to/something").Scheme("http").Path("path/to/something"); + + TestUri("http://path?").Scheme("http").Path("path"); + + TestUri("maps://path?&&key=&").Scheme("maps").Path("path").KV("key", ""); + + TestUri("mapswithme://map?ll=1.2,3.4&z=15").Scheme("mapswithme").Path("map") + .KV("ll", "1.2,3.4").KV("z", "15"); + + TestUri("nopathnovalues://?key1&key2=val2").Scheme("nopathnovalues").Path("") + .KV("key1", "").KV("key2", "val2"); + + TestUri("s://?key1&key2").Scheme("s").Path("").KV("key1", "").KV("key2", ""); + + TestUri("g://p?key1=val1&key2=").Scheme("g").Path("p").KV("key1", "val1").KV("key2", ""); + + TestUri("g://p?=val1&key2=").Scheme("g").Path("p").KV("", "val1").KV("key2", ""); + + TestUri("g://?k&key2").Scheme("g").KV("k", "").KV("key2", ""); + + TestUri("m:?%26Amp%26%3D%26Amp%26&name=%31%20%30").Scheme("m") + .KV("&Amp&=&Amp&", "").KV("name", "1 0"); + + TestUri("s://?key1=value1&key1=value2&key1=value3&key2&key2&key3=value1&key3&key3=value2") + .Scheme("s") + .KV("key1", "value1").KV("key1", "value2").KV("key1", "value3") + .KV("key2", "").KV("key2", "") + .KV("key3", "value1").KV("key3", "").KV("key3", "value2"); +} diff --git a/coding/uri.cpp b/coding/uri.cpp new file mode 100644 index 0000000000..9244219045 --- /dev/null +++ b/coding/uri.cpp @@ -0,0 +1,69 @@ +#include "uri.hpp" +#include "url_encode.hpp" +#include "../base/logging.hpp" +#include "../std/algorithm.hpp" + +using namespace url_scheme; + +void Uri::Init() +{ + if (!Parse()) + { + m_scheme.clear(); + m_path.clear(); + } +} + +bool Uri::Parse() +{ + // get url scheme + size_t pathStart = m_url.find(':'); + if (pathStart == string::npos || pathStart == 0) + return false; + m_scheme.assign(m_url, 0, pathStart); + + // skip slashes + while (++pathStart < m_url.size() && m_url[pathStart] == '/') {}; + + // get path + m_queryStart = m_url.find('?', pathStart); + m_path.assign(m_url, pathStart, m_queryStart - pathStart); + + // url without query + if (m_queryStart == string::npos) + m_queryStart = m_url.size(); + else + ++m_queryStart; + + return true; +} + +void Uri::ForEachKeyValue(CallbackT const & callback) const +{ + // parse query for keys and values + for (size_t start = m_queryStart; start < m_url.size(); ) + { + // TODO: Unoptimal search here, since it goes until the end of the string. + size_t const end = min(m_url.size(), m_url.find('&', start)); + + // Skip empty keys. + if (end - start > 0) + { + size_t const eq = m_url.find('=', start); + + string key, value; + if (eq < end) + { + key = UrlDecode(m_url.substr(start, eq - start)); + value = UrlDecode(m_url.substr(eq + 1, end - eq - 1)); + } + else + { + key = UrlDecode(m_url.substr(start, end - start)); + } + callback(key, value); + } + + start = end + 1; + } +} diff --git a/coding/uri.hpp b/coding/uri.hpp new file mode 100644 index 0000000000..305d34b566 --- /dev/null +++ b/coding/uri.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include "../base/base.hpp" +#include "../std/function.hpp" +#include "../std/string.hpp" + +namespace url_scheme +{ + +// Uri in format: 'scheme://path?key1=value1&key2&key3=&key4=value4' +class Uri +{ +public: + typedef function CallbackT; + + explicit Uri(string const & uri) : m_url(uri) { Init(); } + Uri(char const * uri, size_t size) : m_url(uri, uri + size) { Init(); } + + string GetScheme() const { return m_scheme; } + string GetPath() const { return m_path; } + bool IsValid() const { return !m_scheme.empty(); } + + void ForEachKeyValue(CallbackT const & callback) const; + +private: + void Init(); + bool Parse(); + + string m_url; + string m_scheme; + string m_path; + + size_t m_queryStart; +}; + +} // namespace url_scheme