diff options
Diffstat (limited to 'lib/remote/url.cpp')
-rw-r--r-- | lib/remote/url.cpp | 363 |
1 files changed, 363 insertions, 0 deletions
diff --git a/lib/remote/url.cpp b/lib/remote/url.cpp new file mode 100644 index 0000000..e87628e --- /dev/null +++ b/lib/remote/url.cpp @@ -0,0 +1,363 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/array.hpp" +#include "base/utility.hpp" +#include "base/objectlock.hpp" +#include "remote/url.hpp" +#include "remote/url-characters.hpp" +#include <boost/tokenizer.hpp> + +using namespace icinga; + +Url::Url(const String& base_url) +{ + String url = base_url; + + if (url.GetLength() == 0) + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid URL Empty URL.")); + + size_t pHelper = String::NPos; + if (url[0] != '/') + pHelper = url.Find(":"); + + if (pHelper != String::NPos) { + if (!ParseScheme(url.SubStr(0, pHelper))) + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid URL Scheme.")); + url = url.SubStr(pHelper + 1); + } + + if (*url.Begin() != '/') + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid URL: '/' expected after scheme.")); + + if (url.GetLength() == 1) { + return; + } + + if (*(url.Begin() + 1) == '/') { + pHelper = url.Find("/", 2); + + if (pHelper == String::NPos) + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid URL: Missing '/' after authority.")); + + if (!ParseAuthority(url.SubStr(0, pHelper))) + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid URL Authority")); + + url = url.SubStr(pHelper); + } + + if (*url.Begin() == '/') { + pHelper = url.FindFirstOf("#?"); + if (!ParsePath(url.SubStr(1, pHelper - 1))) + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid URL Path")); + + if (pHelper != String::NPos) + url = url.SubStr(pHelper); + } else + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid URL: Missing path.")); + + if (*url.Begin() == '?') { + pHelper = url.Find("#"); + if (!ParseQuery(url.SubStr(1, pHelper - 1))) + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid URL Query")); + + if (pHelper != String::NPos) + url = url.SubStr(pHelper); + } + + if (*url.Begin() == '#') { + if (!ParseFragment(url.SubStr(1))) + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid URL Fragment")); + } +} + +String Url::GetScheme() const +{ + return m_Scheme; +} + +String Url::GetAuthority() const +{ + if (m_Host.IsEmpty()) + return ""; + + String auth; + if (!m_Username.IsEmpty()) { + auth = m_Username; + if (!m_Password.IsEmpty()) + auth += ":" + m_Password; + auth += "@"; + } + + auth += m_Host; + + if (!m_Port.IsEmpty()) + auth += ":" + m_Port; + + return auth; +} + +String Url::GetUsername() const +{ + return m_Username; +} + +String Url::GetPassword() const +{ + return m_Password; +} + +String Url::GetHost() const +{ + return m_Host; +} + +String Url::GetPort() const +{ + return m_Port; +} + +const std::vector<String>& Url::GetPath() const +{ + return m_Path; +} + +const std::vector<std::pair<String, String>>& Url::GetQuery() const +{ + return m_Query; +} + +String Url::GetFragment() const +{ + return m_Fragment; +} + +void Url::SetScheme(const String& scheme) +{ + m_Scheme = scheme; +} + +void Url::SetUsername(const String& username) +{ + m_Username = username; +} + +void Url::SetPassword(const String& password) +{ + m_Password = password; +} + +void Url::SetHost(const String& host) +{ + m_Host = host; +} + +void Url::SetPort(const String& port) +{ + m_Port = port; +} + +void Url::SetPath(const std::vector<String>& path) +{ + m_Path = path; +} + +void Url::SetQuery(const std::vector<std::pair<String, String>>& query) +{ + m_Query = query; +} + +void Url::SetArrayFormatUseBrackets(bool useBrackets) +{ + m_ArrayFormatUseBrackets = useBrackets; +} + +void Url::AddQueryElement(const String& name, const String& value) +{ + m_Query.emplace_back(name, value); +} + +void Url::SetFragment(const String& fragment) { + m_Fragment = fragment; +} + +String Url::Format(bool onlyPathAndQuery, bool printCredentials) const +{ + String url; + + if (!onlyPathAndQuery) { + if (!m_Scheme.IsEmpty()) + url += m_Scheme + ":"; + + if (printCredentials && !GetAuthority().IsEmpty()) + url += "//" + GetAuthority(); + else if (!GetHost().IsEmpty()) + url += "//" + GetHost() + (!GetPort().IsEmpty() ? ":" + GetPort() : ""); + } + + if (m_Path.empty()) + url += "/"; + else { + for (const String& segment : m_Path) { + url += "/"; + url += Utility::EscapeString(segment, ACPATHSEGMENT_ENCODE, false); + } + } + + String param; + if (!m_Query.empty()) { + typedef std::pair<String, std::vector<String> > kv_pair; + + for (const auto& kv : m_Query) { + String key = Utility::EscapeString(kv.first, ACQUERY_ENCODE, false); + if (param.IsEmpty()) + param = "?"; + else + param += "&"; + + param += key; + param += kv.second.IsEmpty() ? + String() : "=" + Utility::EscapeString(kv.second, ACQUERY_ENCODE, false); + } + } + + url += param; + + if (!m_Fragment.IsEmpty()) + url += "#" + Utility::EscapeString(m_Fragment, ACFRAGMENT_ENCODE, false); + + return url; +} + +bool Url::ParseScheme(const String& scheme) +{ + m_Scheme = scheme; + + if (scheme.FindFirstOf(ALPHA) != 0) + return false; + + return (ValidateToken(scheme, ACSCHEME)); +} + +bool Url::ParseAuthority(const String& authority) +{ + String auth = authority.SubStr(2); + size_t pos = auth.Find("@"); + if (pos != String::NPos && pos != 0) { + if (!Url::ParseUserinfo(auth.SubStr(0, pos))) + return false; + auth = auth.SubStr(pos+1); + } + + pos = auth.Find(":"); + if (pos != String::NPos) { + if (pos == 0 || pos == auth.GetLength() - 1 || !Url::ParsePort(auth.SubStr(pos+1))) + return false; + } + + m_Host = auth.SubStr(0, pos); + return ValidateToken(m_Host, ACHOST); +} + +bool Url::ParseUserinfo(const String& userinfo) +{ + size_t pos = userinfo.Find(":"); + m_Username = userinfo.SubStr(0, pos); + if (!ValidateToken(m_Username, ACUSERINFO)) + return false; + m_Username = Utility::UnescapeString(m_Username); + if (pos != String::NPos && pos != userinfo.GetLength() - 1) { + m_Password = userinfo.SubStr(pos+1); + if (!ValidateToken(m_Username, ACUSERINFO)) + return false; + m_Password = Utility::UnescapeString(m_Password); + } else + m_Password = ""; + + return true; +} + +bool Url::ParsePort(const String& port) +{ + m_Port = Utility::UnescapeString(port); + if (!ValidateToken(m_Port, ACPORT)) + return false; + return true; +} + +bool Url::ParsePath(const String& path) +{ + const std::string& pathStr = path; + boost::char_separator<char> sep("/"); + boost::tokenizer<boost::char_separator<char> > tokens(pathStr, sep); + + for (const String& token : tokens) { + if (token.IsEmpty()) + continue; + + if (!ValidateToken(token, ACPATHSEGMENT)) + return false; + + m_Path.emplace_back(Utility::UnescapeString(token)); + } + + return true; +} + +bool Url::ParseQuery(const String& query) +{ + /* Tokenizer does not like String AT ALL */ + const std::string& queryStr = query; + boost::char_separator<char> sep("&"); + boost::tokenizer<boost::char_separator<char> > tokens(queryStr, sep); + + for (const String& token : tokens) { + size_t pHelper = token.Find("="); + + if (pHelper == 0) + // /?foo=bar&=bar == invalid + return false; + + String key = token.SubStr(0, pHelper); + String value = Empty; + + if (pHelper != String::NPos && pHelper != token.GetLength() - 1) + value = token.SubStr(pHelper+1); + + if (!ValidateToken(value, ACQUERY)) + return false; + + value = Utility::UnescapeString(value); + + pHelper = key.Find("[]"); + + if (pHelper == 0 || (pHelper != String::NPos && pHelper != key.GetLength()-2)) + return false; + + key = key.SubStr(0, pHelper); + + if (!ValidateToken(key, ACQUERY)) + return false; + + m_Query.emplace_back(Utility::UnescapeString(key), std::move(value)); + } + + return true; +} + +bool Url::ParseFragment(const String& fragment) +{ + m_Fragment = Utility::UnescapeString(fragment); + + return ValidateToken(fragment, ACFRAGMENT); +} + +bool Url::ValidateToken(const String& token, const String& symbols) +{ + for (const char ch : token) { + if (symbols.FindFirstOf(ch) == String::NPos) + return false; + } + + return true; +} + |