summaryrefslogtreecommitdiffstats
path: root/src/lib/http/url.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/http/url.cc')
-rw-r--r--src/lib/http/url.cc223
1 files changed, 223 insertions, 0 deletions
diff --git a/src/lib/http/url.cc b/src/lib/http/url.cc
new file mode 100644
index 0000000..427b11a
--- /dev/null
+++ b/src/lib/http/url.cc
@@ -0,0 +1,223 @@
+// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <http/url.h>
+#include <boost/lexical_cast.hpp>
+#include <sstream>
+
+#include <iostream>
+
+namespace isc {
+namespace http {
+
+Url::Url(const std::string& url)
+ : url_(url), valid_(false), error_message_(), scheme_(Url::HTTPS),
+ hostname_(), port_(0), path_() {
+ parse();
+}
+
+bool
+Url::operator<(const Url& url) const {
+ return (url_ < url.rawUrl());
+}
+
+Url::Scheme
+Url::getScheme() const {
+ checkValid();
+ return (scheme_);
+}
+
+std::string
+Url::getHostname() const {
+ checkValid();
+ return (hostname_);
+}
+
+std::string
+Url::getStrippedHostname() const {
+ std::string hostname = getHostname();
+ if ((hostname.length() >= 2) && (hostname.at(0) == '[')) {
+ return (hostname.substr(1, hostname.length() - 2));
+ }
+
+ return (hostname);
+}
+
+unsigned
+Url::getPort() const {
+ checkValid();
+ return (port_);
+}
+
+std::string
+Url::getPath() const {
+ checkValid();
+ return (path_);
+}
+
+std::string
+Url::toText() const {
+ std::ostringstream s;
+ s << (getScheme() == HTTP ? "http" : "https");
+ s << "://" << getHostname();
+
+ if (getPort() != 0) {
+ s << ":" << getPort();
+ }
+
+ s << getPath();
+
+ return (s.str());
+}
+
+void
+Url::checkValid() const {
+ if (!isValid()) {
+ isc_throw(InvalidOperation, "invalid URL " << url_ << ": " << error_message_);
+ }
+}
+
+void
+Url::parse() {
+ valid_ = false;
+ error_message_.clear();
+ scheme_ = Url::HTTPS;
+ hostname_.clear();
+ port_ = 0;
+ path_.clear();
+
+ std::ostringstream error;
+
+ // Retrieve scheme
+ size_t offset = url_.find(":");
+ if ((offset == 0) || (offset == std::string::npos)) {
+ error << "url " << url_ << " lacks http or https scheme";
+ error_message_ = error.str();
+ return;
+ }
+
+ // Validate scheme.
+ std::string scheme = url_.substr(0, offset);
+ if (scheme == "http") {
+ scheme_ = Url::HTTP;
+
+ } else if (scheme == "https") {
+ scheme_ = Url::HTTPS;
+
+ } else {
+ error << "invalid scheme " << scheme << " in " << url_;
+ error_message_ = error.str();
+ return;
+ }
+
+ // Colon and two slashes should follow the scheme
+ if (url_.substr(offset, 3) != "://") {
+ error << "expected :// after scheme in " << url_;
+ error_message_ = error.str();
+ return;
+ }
+
+ // Move forward to hostname.
+ offset += 3;
+ if (offset >= url_.length()) {
+ error << "hostname missing in " << url_;
+ error_message_ = error.str();
+ return;
+ }
+
+ size_t offset2 = 0;
+
+ // IPv6 address is specified within [ ].
+ if (url_.at(offset) == '[') {
+ offset2 = url_.find(']', offset);
+ if (offset2 == std::string::npos) {
+ error << "expected ] after IPv6 address in " << url_;
+ error_message_ = error.str();
+ return;
+
+ } else if (offset2 == offset + 1) {
+ error << "expected IPv6 address within [] in " << url_;
+ error_message_ = error.str();
+ return;
+ }
+
+ // Move one character beyond the ].
+ ++offset2;
+
+ } else {
+ // There is a normal hostname or IPv4 address. It is terminated
+ // by the colon (for port number), a slash (if no port number) or
+ // goes up to the end of the URL.
+ offset2 = url_.find(":", offset);
+ if (offset2 == std::string::npos) {
+ offset2 = url_.find("/", offset);
+ if (offset2 == std::string::npos) {
+ // No port number and no slash.
+ offset2 = url_.length();
+ }
+ }
+ }
+
+ // Extract the hostname.
+ hostname_ = url_.substr(offset, offset2 - offset);
+
+ // If there is no port number and no path, simply return and mark the
+ // URL as valid.
+ if (offset2 == url_.length()) {
+ valid_ = true;
+ return;
+ }
+
+ // If there is a port number, we need to read it and convert to
+ // numeric value.
+ if (url_.at(offset2) == ':') {
+ if (offset2 == url_.length() - 1) {
+ error << "expected port number after : in " << url_;
+ error_message_ = error.str();
+ return;
+ }
+ // Move to the port number.
+ ++offset2;
+
+ // Port number may be terminated by a slash or by the end of URL.
+ size_t slash_offset = url_.find('/', offset2);
+ std::string port_str;
+ if (slash_offset == std::string::npos) {
+ port_str = url_.substr(offset2);
+ } else {
+ port_str = url_.substr(offset2, slash_offset - offset2);
+ }
+
+ try {
+ // Try to convert the port number to numeric value.
+ port_ = boost::lexical_cast<unsigned>(port_str);
+
+ } catch (...) {
+ error << "invalid port number " << port_str << " in " << url_;
+ error_message_ = error.str();
+ return;
+ }
+
+ // Go to the end of the port section.
+ offset2 = slash_offset;
+ }
+
+ // If there is anything left in the URL, we consider it a path.
+ if (offset2 != std::string::npos) {
+ path_ = url_.substr(offset2);
+ if (path_.empty()) {
+ path_ = "/";
+ }
+ }
+
+ valid_ = true;
+}
+
+} // end of namespace isc::http
+} // end of namespace isc