/* * Copyright (c) 2010-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #include <Swiften/Base/URL.h> #include <algorithm> #include <iostream> namespace Swift { int URL::getPortOrDefaultPort(const URL& url) { if (url.getPort()) { return *url.getPort(); } else if (url.getScheme() == "http") { return 80; } else if (url.getScheme() == "https") { return 443; } else { std::cerr << "Unknown scheme: " + url.getScheme() << std::endl; return 80; } } URL URL::fromString(const std::string& urlString) { size_t colonIndex = urlString.find(':'); if (colonIndex == std::string::npos) { return URL(); } std::string scheme = urlString.substr(0, colonIndex); // Authority if (urlString.size() > colonIndex + 2 && urlString[colonIndex+1] == '/' && urlString[colonIndex+2] == '/') { size_t authorityIndex = colonIndex + 3; size_t slashIndex = urlString.find('/', authorityIndex); std::string authority; std::string path; if (slashIndex == std::string::npos) { authority = urlString.substr(authorityIndex); path = ""; } else { authority = urlString.substr(authorityIndex, slashIndex - authorityIndex); path = unescape(urlString.substr(slashIndex)); } size_t atIndex = authority.find('@'); std::string userInfo; std::string hostAndPort; if (atIndex != std::string::npos) { userInfo = authority.substr(0, atIndex); hostAndPort = authority.substr(atIndex + 1); } else { userInfo = ""; hostAndPort = authority; } std::string host; boost::optional<int> port; if (hostAndPort[0] == '[') { // handle IPv6 address literals size_t addressEndIndex = hostAndPort.find(']'); if (addressEndIndex != std::string::npos) { host = hostAndPort.substr(1, addressEndIndex - 1); colonIndex = hostAndPort.find(':', addressEndIndex); if (colonIndex != std::string::npos) { try { port = boost::lexical_cast<int>(hostAndPort.substr(colonIndex + 1)); } catch (const boost::bad_lexical_cast&) { return URL(); } } } else { return URL(); } } else { colonIndex = hostAndPort.find(':'); if (colonIndex != std::string::npos) { host = unescape(hostAndPort.substr(0, colonIndex)); try { port = boost::lexical_cast<int>(hostAndPort.substr(colonIndex + 1)); } catch (const boost::bad_lexical_cast&) { return URL(); } } else { host = unescape(hostAndPort); } } if (port) { return URL(scheme, host, *port, path); } else { return URL(scheme, host, path); } } else { // We don't support URLs without authorities yet return URL(); } } // FIXME: Escape non-ascii characters std::string URL::toString() const { if (empty) { return ""; } std::string result = scheme + "://"; if (!user.empty()) { result += user; if (!password.empty()) { result += ":" + password; } result += "@"; } if (host.find(':') != std::string::npos) { result += "[" + host + "]"; } else { result += host; } if (port) { result += ":"; result += boost::lexical_cast<std::string>(*port); } result += path; return result; } // Disabling this code for now, since GCC4.5+boost1.42 (on ubuntu) seems to // result in a bug. Replacing it with naive code. #if 0 // Should be in anonymous namespace, but older GCCs complain if we do that struct PercentEncodedCharacterFinder { template<typename Iterator> boost::iterator_range<Iterator> operator()(Iterator begin, Iterator end) { boost::iterator_range<Iterator> r = boost::first_finder("%")(begin, end); if (r.end() == end) { return r; } else { if (r.end() + 1 == end || r.end() + 2 == end) { throw std::runtime_error("Incomplete escape character"); } else { r.advance_end(2); return r; } } } }; struct PercentUnencodeFormatter { template<typename FindResult> std::string operator()(const FindResult& match) const { std::stringstream s; s << std::hex << std::string(match.begin() + 1, match.end()); unsigned int value; s >> value; if (s.fail() || s.bad()) { throw std::runtime_error("Invalid escape character"); } unsigned char charValue = static_cast<unsigned char>(value); return std::string(reinterpret_cast<const char*>(&charValue), 1); } }; std::string unescape(const std::string& s) { try { return boost::find_format_all_copy(s, PercentEncodedCharacterFinder(), PercentUnencodeFormatter()); } catch (const std::exception&) { return ""; } } #endif std::string URL::unescape(const std::string& str) { std::string result; for (size_t i = 0; i < str.size(); ++i) { if (str[i] == '%') { if (i + 3 < str.size()) { std::stringstream s; s << std::hex << str.substr(i+1, 2); unsigned int value; s >> value; if (s.fail() || s.bad()) { return ""; } unsigned char charValue = static_cast<unsigned char>(value); result += std::string(reinterpret_cast<const char*>(&charValue), 1); i += 2; } else { return ""; } } else { result += str[i]; } } return result; } }