diff --git a/libretroshare/src/libretroshare.pro b/libretroshare/src/libretroshare.pro index c2604fa4a..5ec795d3d 100644 --- a/libretroshare/src/libretroshare.pro +++ b/libretroshare/src/libretroshare.pro @@ -550,7 +550,8 @@ HEADERS += util/folderiterator.h \ util/rstime.h \ util/stacktrace.h \ util/rsdeprecate.h \ - util/cxx11retrocompat.h + util/cxx11retrocompat.h \ + util/rsurl.h SOURCES += ft/ftchunkmap.cc \ ft/ftcontroller.cc \ @@ -695,7 +696,8 @@ SOURCES += util/folderiterator.cc \ util/rsrandom.cc \ util/rstickevent.cc \ util/rsrecogn.cc \ - util/rstime.cc + util/rstime.cc \ + util/rsurl.cc upnp_miniupnpc { diff --git a/libretroshare/src/util/rsnet.h b/libretroshare/src/util/rsnet.h index 1d028e991..95887e61a 100644 --- a/libretroshare/src/util/rsnet.h +++ b/libretroshare/src/util/rsnet.h @@ -133,6 +133,7 @@ bool sockaddr_storage_sameip(const struct sockaddr_storage &addr, const struct s // string, std::string sockaddr_storage_tostring(const struct sockaddr_storage &addr); +bool sockaddr_storage_fromString(const std::string& str, sockaddr_storage &addr); std::string sockaddr_storage_familytostring(const struct sockaddr_storage &addr); std::string sockaddr_storage_iptostring(const struct sockaddr_storage &addr); std::string sockaddr_storage_porttostring(const struct sockaddr_storage &addr); diff --git a/libretroshare/src/util/rsnet_ss.cc b/libretroshare/src/util/rsnet_ss.cc index 581fd06e9..65f2f3ee7 100644 --- a/libretroshare/src/util/rsnet_ss.cc +++ b/libretroshare/src/util/rsnet_ss.cc @@ -24,6 +24,8 @@ * */ +#include "util/rsurl.h" + #include #include #include @@ -491,27 +493,32 @@ bool sockaddr_storage_sameip(const struct sockaddr_storage &addr, const struct s std::string sockaddr_storage_tostring(const struct sockaddr_storage &addr) { - std::string output; - output += sockaddr_storage_familytostring(addr); + RsUrl url; switch(addr.ss_family) { case AF_INET: - output += "="; - output += sockaddr_storage_iptostring(addr); - output += ":"; - output += sockaddr_storage_porttostring(addr); + url.setScheme("ipv4"); break; case AF_INET6: - output += "=["; - output += sockaddr_storage_iptostring(addr); - output += "]:"; - output += sockaddr_storage_porttostring(addr); + url.setScheme("ipv6"); break; default: - break; + return "INVALID_IP"; } - return output; + + url.setHost(sockaddr_storage_iptostring(addr)) + .setPort(sockaddr_storage_port(addr)); + + return url.toString(); +} + +bool sockaddr_storage_fromString(const std::string& str, sockaddr_storage &addr) +{ + RsUrl url(str); + bool valid = sockaddr_storage_inet_pton(addr, url.host()); + if(url.hasPort()) sockaddr_storage_setport(addr, url.port()); + return valid; } void sockaddr_storage_dump(const sockaddr_storage & addr, std::string * outputString) diff --git a/libretroshare/src/util/rsurl.cc b/libretroshare/src/util/rsurl.cc new file mode 100644 index 000000000..31922abbf --- /dev/null +++ b/libretroshare/src/util/rsurl.cc @@ -0,0 +1,258 @@ +/* + * RetroShare + * Copyright (C) 2018 Gioacchino Mazzurco + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +//#include "util/rsurl.h" +#include "rsurl.h" + +#include +#include +#include +#include +#include + +using namespace std; + +RsUrl::RsUrl() : mPort(0), mHasPort(false) {} + +RsUrl::RsUrl(const std::string& urlStr) : mPort(0), mHasPort(false) +{ fromString(urlStr); } + +RsUrl& RsUrl::fromString(const std::string& urlStr) +{ + size_t urlSize = urlStr.size(); + + size_t schemeEndI = urlStr.find(schemeSeparator); + if(schemeEndI == string::npos) + { + mScheme = urlStr; + return *this; + } + + mScheme = urlStr.substr(0, schemeEndI); + + size_t hostBeginI = schemeEndI + 3; + if(hostBeginI >= urlSize) return *this; + + bool hasSquareBr = (urlStr[hostBeginI] == ipv6WrapOpen[0]); + size_t hostEndI; + if(hasSquareBr) + { + if(++hostBeginI >= urlSize) return *this; + hostEndI = urlStr.find(ipv6WrapClose, hostBeginI); + mHost = urlStr.substr(hostBeginI, hostEndI - hostBeginI - 1); + } + else + { + hostEndI = urlStr.find(pathSeparator, hostBeginI); + hostEndI = min(hostEndI, urlStr.find(portSeparator, hostBeginI)); + hostEndI = min(hostEndI, urlStr.find(querySeparator, hostBeginI)); + hostEndI = min(hostEndI, urlStr.find(fragmentSeparator, hostBeginI)); + + mHost = urlStr.substr(hostBeginI, hostEndI - hostBeginI); + if(hostEndI == string::npos) return *this; + } + + mHasPort = (sscanf(&urlStr[hostEndI], ":%hu", &mPort) == 1); + + size_t pathBeginI = urlStr.find(pathSeparator, hostEndI); + size_t pathEndI = string::npos; + if(pathBeginI != string::npos) + { + pathEndI = urlStr.find(querySeparator, pathBeginI); + pathEndI = min(pathEndI, urlStr.find(fragmentSeparator, pathBeginI)); + mPath = UrlDecode(urlStr.substr(pathBeginI, pathEndI - pathBeginI)); + if(pathEndI == string::npos) return *this; + } + + size_t queryBeginI = urlStr.find(querySeparator, schemeEndI); + size_t queryEndI = urlStr.find(fragmentSeparator, schemeEndI); + if(queryBeginI != string::npos) + { + string qStr = urlStr.substr(queryBeginI+1, queryEndI-queryBeginI-1); + + size_t kPos = 0; + size_t assPos = qStr.find(queryAssign); + do + { + size_t vEndPos = qStr.find(queryFieldSep, assPos); + mQuery.insert( std::make_pair( qStr.substr(kPos, assPos-kPos), + UrlDecode(qStr.substr(assPos+1, vEndPos-assPos-1)) + ) ); + kPos = vEndPos+1; + assPos = qStr.find(queryAssign, vEndPos); + } + while(assPos != string::npos); + + if(queryEndI == string::npos) return *this; + } + + size_t fragmentBeginI = urlStr.find(fragmentSeparator, schemeEndI); + if(fragmentBeginI != string::npos) + mFragment = UrlDecode(urlStr.substr(++fragmentBeginI)); +} + +std::string RsUrl::toString() const +{ + std::string urlStr(mScheme); + urlStr += schemeSeparator; + + if(!mHost.empty()) + { + if(mHost.find(ipv6Separator) != string::npos && + mHost[0] != ipv6WrapOpen[0] ) + urlStr += ipv6WrapOpen + mHost + ipv6WrapClose; + else urlStr += mHost; + } + + if(mHasPort) urlStr += portSeparator + std::to_string(mPort); + + urlStr += UrlEncode(mPath, pathSeparator); + + bool hasQuery = !mQuery.empty(); + if(hasQuery) urlStr += querySeparator; + for(auto&& kv : mQuery) + { + urlStr += kv.first; + urlStr += queryAssign; + urlStr += UrlEncode(kv.second); + urlStr += queryFieldSep; + } + if(hasQuery) urlStr.pop_back(); + + if(!mFragment.empty()) urlStr += fragmentSeparator + UrlEncode(mFragment); + + return urlStr; +} + +const std::string& RsUrl::scheme() const { return mScheme; } +RsUrl& RsUrl::setScheme(const std::string& scheme) +{ + mScheme = scheme; + return *this; +} + +const std::string& RsUrl::host() const { return mHost; } +RsUrl& RsUrl::setHost(const std::string& host) +{ + mHost = host; + return *this; +} + +bool RsUrl::hasPort() const { return mHasPort; } + +uint16_t RsUrl::port(uint16_t def) const +{ + if(mHasPort) return mPort; + return def; +} + +RsUrl& RsUrl::setPort(uint16_t port) +{ + mPort = port; + return *this; +} + +const std::string& RsUrl::path() const { return mPath; } +RsUrl& RsUrl::setPath(const std::string& path) +{ + mPath = path; + return *this; +} + +const std::map& RsUrl::query() const +{ return mQuery; } +RsUrl& RsUrl::setQuery(const std::map& query) +{ + mQuery = query; + return *this; +} +RsUrl& RsUrl::setQueryKV(const std::string& key, const std::string& value) +{ + mQuery.insert(std::make_pair(key, value)); + return *this; +} +RsUrl& RsUrl::delQueryK(const std::string& key) +{ + mQuery.erase(key); + return *this; +} + +const std::string& RsUrl::fragment() const { return mFragment; } +RsUrl& RsUrl::setFragment(const std::string& fragment) +{ + mFragment = fragment; + return *this; +} + +/*static*/ std::string RsUrl::UrlEncode( const std::string& str, + const std::string& ignore ) +{ + ostringstream escaped; + escaped.fill('0'); + escaped << hex; + + for (string::value_type c : str) + { + // Keep alphanumeric and other accepted characters intact + if ( isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~' + || ignore.find(c) != string::npos ) + { + escaped << c; + continue; + } + + // Any other characters are percent-encoded + escaped << uppercase; + escaped << '%' << setw(2) << int((unsigned char) c); + escaped << nouppercase; + } + + return escaped.str(); +} + +/*static*/ std::string RsUrl::UrlDecode(const std::string& str) +{ + ostringstream decoded; + + size_t len = str.size(); + size_t boundary = len-2; // % Encoded char must be at least 2 hex char + for (size_t i = 0; i < len; ++i) + { + + if(str[i] == '%' && i < boundary) + { + decoded << static_cast(stoi(str.substr(++i, 2), 0, 16)); + ++i; + } + else decoded << str[i]; + } + + return decoded.str(); +} + +/*static*/ const std::string RsUrl::schemeSeparator("://"); +/*static*/ const std::string RsUrl::ipv6WrapOpen("["); +/*static*/ const std::string RsUrl::ipv6Separator(":"); +/*static*/ const std::string RsUrl::ipv6WrapClose("]"); +/*static*/ const std::string RsUrl::portSeparator(":"); +/*static*/ const std::string RsUrl::pathSeparator("/"); +/*static*/ const std::string RsUrl::querySeparator("?"); +/*static*/ const std::string RsUrl::queryAssign("="); +/*static*/ const std::string RsUrl::queryFieldSep("&"); +/*static*/ const std::string RsUrl::fragmentSeparator("#"); + diff --git a/libretroshare/src/util/rsurl.h b/libretroshare/src/util/rsurl.h new file mode 100644 index 000000000..fc9d2992b --- /dev/null +++ b/libretroshare/src/util/rsurl.h @@ -0,0 +1,101 @@ +#pragma once +/* + * RetroShare + * Copyright (C) 2018 Gioacchino Mazzurco + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include +#include + +/** + * Very simplistic and minimal URL helper class for RetroShare, after looking + * for a small and self-contained C/C++ URL parsing and manipulation library, + * haven't found nothing satisfactory except for implementation like QUrl that + * rely on bigger library. + * ATM this implementation is not standard compliant and doesn't aim to be. + * Improvements to this are welcome. + * + * Anyway this should support most common URLs of the form + * scheme://host[:port][/path][?query][#fragment] + */ +struct RsUrl +{ + RsUrl(); + RsUrl(const std::string& urlStr); + + RsUrl& fromString(const std::string& urlStr); + std::string toString() const; + + const std::string& scheme() const; + RsUrl& setScheme(const std::string& scheme); + + const std::string& host() const; + RsUrl& setHost(const std::string& host); + + bool hasPort() const; + uint16_t port(uint16_t def = 0) const; + RsUrl& setPort(uint16_t port); + + const std::string& path() const; + RsUrl& setPath(const std::string& path); + + const std::map& query() const; + RsUrl& setQuery(const std::map& query); + RsUrl& setQueryKV(const std::string& key, const std::string& value); + RsUrl& delQueryK(const std::string& key); + + const std::string& fragment() const; + RsUrl& setFragment(const std::string& fragment); + + static std::string UrlEncode(const std::string& str, + const std::string& ignoreChars = ""); + static std::string UrlDecode(const std::string& str); + + inline bool operator<(const RsUrl& rhs) + { return toString() < rhs.toString(); } + inline bool operator>(const RsUrl& rhs) + { return toString() > rhs.toString(); } + inline bool operator<=(const RsUrl& rhs) + { return toString() <= rhs.toString(); } + inline bool operator>=(const RsUrl& rhs) + { return toString() >= rhs.toString(); } + inline bool operator==(const RsUrl& rhs) + { return toString() == rhs.toString(); } + inline bool operator!=(const RsUrl& rhs) + { return toString() != rhs.toString(); } + + static const std::string schemeSeparator; + static const std::string ipv6WrapOpen; + static const std::string ipv6Separator; + static const std::string ipv6WrapClose; + static const std::string portSeparator; + static const std::string pathSeparator; + static const std::string querySeparator; + static const std::string queryAssign; + static const std::string queryFieldSep; + static const std::string fragmentSeparator; + +private: + + std::string mScheme; + std::string mHost; + uint16_t mPort; + bool mHasPort; + std::string mPath; + std::map mQuery; + std::string mFragment; +}; +