From 0afbaaaa41502db5733584e371a2c11d0f96a186 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Wed, 5 Jul 2017 13:44:41 +0200 Subject: [PATCH 1/2] Many improvements and fixes to HTTP::URL class --- lib/http_parser.cpp | 114 ++++++++++++++++++++++++++++++++++++++++++-- lib/http_parser.h | 5 ++ 2 files changed, 114 insertions(+), 5 deletions(-) diff --git a/lib/http_parser.cpp b/lib/http_parser.cpp index 87c4106e..188bdf77 100644 --- a/lib/http_parser.cpp +++ b/lib/http_parser.cpp @@ -27,14 +27,46 @@ HTTP::URL::URL(const std::string & url){ } //proto_sep now points to the start of the host, guaranteed //continue by finding the path, if any - size_t first_slash = url.find('/', proto_sep); + size_t first_slash = url.find_first_of("/?#", proto_sep); if (first_slash != std::string::npos){ - path = url.substr(first_slash+1); + if (url[first_slash] == '/'){ + path = url.substr(first_slash+1); + }else{ + path = url.substr(first_slash); + } + size_t hmark = path.find('#'); + if (hmark != std::string::npos){ + frag = path.substr(hmark+1); + path.erase(hmark); + } size_t qmark = path.find('?'); if (qmark != std::string::npos){ args = path.substr(qmark+1); path.erase(qmark); } + if (path.size()){ + size_t dots = path.find("/./"); + while (dots != std::string::npos){ + path.erase(dots, 2); + dots = path.find("/./"); + } + dots = path.find("/../"); + while (dots != std::string::npos){ + size_t prevslash = path.rfind('/', dots-1); + if (prevslash == std::string::npos){ + path.erase(0, dots+4); + }else{ + path.erase(prevslash+1, dots-prevslash+3); + } + dots = path.find("/../"); + } + if (path.substr(0, 2) == "./"){ + path.erase(0, 2); + } + if (path.substr(0, 3) == "../"){ + path.erase(0, 3); + } + } } //host and port are now definitely between proto_sep and first_slash //we check for [ at the start because we may have an IPv6 address as host @@ -60,8 +92,8 @@ HTTP::URL::URL(const std::string & url){ //"normal" host - first find port, if any size_t colon = url.rfind(':', first_slash); if (colon == std::string::npos || colon < proto_sep){ - //no port. Assume 80 - port = "80"; + //no port. Assume default + port = ""; host = url.substr(proto_sep, first_slash-proto_sep); }else{ //we have a port number, read it @@ -83,10 +115,82 @@ HTTP::URL::URL(const std::string & url){ ///Returns the port in numeric format uint32_t HTTP::URL::getPort() const{ - if (!port.size()){return 80;} + if (!port.size()){return getDefaultPort();} return atoi(port.c_str()); } +///Returns the default port for the protocol in numeric format +uint32_t HTTP::URL::getDefaultPort() const{ + if (protocol == "https"){return 443;} + if (protocol == "rtmp"){return 1935;} + if (protocol == "dtsc"){return 4200;} + return 80; +} + +///Returns the full URL in string format +std::string HTTP::URL::getUrl() const{ + std::string ret; + if (protocol.size()){ + ret = protocol + "://" + host; + }else{ + ret = "//" + host; + } + if (port.size() && getPort() != getDefaultPort()){ret += ":" + port;} + ret += "/"; + if (path.size()){ret += path;} + if (args.size()){ret += "?" + args;} + if (frag.size()){ret += "#" + frag;} + return ret; +} + +///Returns the URL in string format without args and frag +std::string HTTP::URL::getBareUrl() const{ + std::string ret; + if (protocol.size()){ + ret = protocol + "://" + host; + }else{ + ret = "//" + host; + } + if (port.size() && getPort() != getDefaultPort()){ret += ":" + port;} + ret += "/"; + if (path.size()){ret += path;} + return ret; +} + +///Returns a URL object for the given link, resolved relative to the current URL object. +HTTP::URL HTTP::URL::link(const std::string &l){ + //Full link + if (l.find("://") < l.find('/') && l.find('/' != std::string::npos)){ + DONTEVEN_MSG("Full link: %s", l.c_str()); + return URL(l); + } + //Absolute link + if (l[0] == '/'){ + DONTEVEN_MSG("Absolute link: %s", l.c_str()); + if (l.size() > 1 && l[1] == '/'){ + //Same-protocol full link + return URL(protocol+":"+l); + }else{ + //Same-domain/port absolute link + URL tmp = *this; + tmp.args.clear(); + tmp.path = l.substr(1); + //Abuse the fact that we don't check for arguments in getUrl() + return URL(tmp.getUrl()); + } + } + //Relative link + std::string tmpUrl = getBareUrl(); + size_t slashPos = tmpUrl.rfind('/'); + if (slashPos == std::string::npos){ + tmpUrl += "/"; + }else{ + tmpUrl.erase(slashPos+1); + } + DONTEVEN_MSG("Relative link: %s+%s", tmpUrl.c_str(), l.c_str()); + return URL(tmpUrl+l); +} + /// This constructor creates an empty HTTP::Parser, ready for use for either reading or writing. /// All this constructor does is call HTTP::Parser::Clean(). HTTP::Parser::Parser() { diff --git a/lib/http_parser.h b/lib/http_parser.h index 181ea5f5..692febd8 100644 --- a/lib/http_parser.h +++ b/lib/http_parser.h @@ -74,11 +74,16 @@ namespace HTTP { public: URL(const std::string & url); uint32_t getPort() const; + uint32_t getDefaultPort() const; + std::string getUrl() const; + std::string getBareUrl() const; std::string host;///< Hostname or IP address of URL std::string protocol;/// Date: Tue, 27 Jun 2017 23:14:39 +0200 Subject: [PATCH 2/2] Improved isAddress() function for sockets with masking support, added several socket convenience functions. --- lib/socket.cpp | 207 ++++++++++++++++++++++++++++++++----------------- lib/socket.h | 9 ++- 2 files changed, 146 insertions(+), 70 deletions(-) diff --git a/lib/socket.cpp b/lib/socket.cpp index 55c378a0..d12c3edb 100644 --- a/lib/socket.cpp +++ b/lib/socket.cpp @@ -33,6 +33,107 @@ static const char* addrFam(int f){ } } +static std::string getIPv6BinAddr(const struct sockaddr_in6 & remoteaddr){ + char tmpBuffer[17] = "\000\000\000\000\000\000\000\000\000\000\377\377\000\000\000\000"; + switch (remoteaddr.sin6_family){ + case AF_INET: + memcpy(tmpBuffer + 12, &(reinterpret_cast(&remoteaddr)->sin_addr.s_addr), 4); + break; + case AF_INET6: + memcpy(tmpBuffer, &(remoteaddr.sin6_addr.s6_addr), 16); + break; + default: + return ""; + break; + } + return std::string(tmpBuffer, 16); +} + +/// Helper function that matches two binary-format IPv6 addresses with prefix bits of prefix. +bool Socket::matchIPv6Addr(const std::string &A, const std::string &B, uint8_t prefix){ + if (!prefix){prefix = 128;} + if (Util::Config::printDebugLevel >= DLVL_MEDIUM){ + std::string Astr, Bstr; + Socket::hostBytesToStr(A.data(), 16, Astr); + Socket::hostBytesToStr(B.data(), 16, Bstr); + MEDIUM_MSG("Matching: %s to %s with %u prefix", Astr.c_str(), Bstr.c_str(), prefix); + } + if (memcmp(A.data(), B.data(), prefix / 8)){return false;} + if ((prefix % 8) && ((A.data()[prefix / 8] & (0xFF << (8 - (prefix % 8)))) != + (B.data()[prefix / 8] & (0xFF << (8 - (prefix % 8)))))){ + return false; + } + return true; +} + +/// Attempts to match the given address with optional subnet to the given binary-form IPv6 address. +/// Returns true if match could be made, false otherwise. +bool Socket::isBinAddress(const std::string &binAddr, std::string addr){ + //Check if we need to do prefix matching + uint8_t prefixLen = 0; + if (addr.find('/') != std::string::npos){ + prefixLen = atoi(addr.c_str() + addr.find('/') + 1); + addr.erase(addr.find('/'), std::string::npos); + } + //Loops over all IPs for the given address and matches them in IPv6 form. + struct addrinfo *result, *rp, hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED; + hints.ai_protocol = 0; + hints.ai_canonname = NULL; + hints.ai_addr = NULL; + hints.ai_next = NULL; + int s = getaddrinfo(addr.c_str(), 0, &hints, &result); + if (s != 0){return false;} + + for (rp = result; rp != NULL; rp = rp->ai_next){ + std::string tBinAddr = getIPv6BinAddr(*((sockaddr_in6 *)rp->ai_addr)); + if (rp->ai_family == AF_INET){ + if (matchIPv6Addr(tBinAddr, binAddr, prefixLen ? prefixLen + 96 : 0)){return true;} + }else{ + if (matchIPv6Addr(tBinAddr, binAddr, prefixLen)){return true;} + } + } + freeaddrinfo(result); + return false; +} + +/// Converts the given address with optional subnet to binary IPv6 form. +/// Returns 16 bytes of address, followed by 1 byte of subnet bits, zero or more times. +std::string Socket::getBinForms(std::string addr){ + //Check if we need to do prefix matching + uint8_t prefixLen = 128; + if (addr.find('/') != std::string::npos){ + prefixLen = atoi(addr.c_str() + addr.find('/') + 1); + addr.erase(addr.find('/'), std::string::npos); + } + //Loops over all IPs for the given address and converts to IPv6 binary form. + struct addrinfo *result, *rp, hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED; + hints.ai_protocol = 0; + hints.ai_canonname = NULL; + hints.ai_addr = NULL; + hints.ai_next = NULL; + int s = getaddrinfo(addr.c_str(), 0, &hints, &result); + if (s != 0){return "";} + std::string ret; + for (rp = result; rp != NULL; rp = rp->ai_next){ + ret += getIPv6BinAddr(*((sockaddr_in6 *)rp->ai_addr)); + if (rp->ai_family == AF_INET){ + ret += (char)(prefixLen<=32 ? prefixLen + 96 : prefixLen); + }else{ + ret += (char)prefixLen; + } + } + freeaddrinfo(result); + return ret; +} + /// Checks bytes (length len) containing a binary-encoded IPv4 or IPv6 IP address, and writes it in human-readable notation to target. /// Writes "unknown" if it cannot decode to a sensible value. void Socket::hostBytesToStr(const char *bytes, size_t len, std::string &target){ @@ -624,40 +725,31 @@ std::string Socket::Connection::getHost() const{ return remotehost; } -/// Gets hostname for connection, if available. +/// Gets binary IPv6 address for connection, if available. /// Guaranteed to be either empty or 16 bytes long. std::string Socket::Connection::getBinHost(){ - if (remotehost.size()){ - struct addrinfo *result, *rp, hints; - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_ADDRCONFIG; - hints.ai_protocol = 0; - hints.ai_canonname = NULL; - hints.ai_addr = NULL; - hints.ai_next = NULL; - int s = getaddrinfo(remotehost.c_str(), 0, &hints, &result); - if (s != 0){ - DEBUG_MSG(DLVL_FAIL, "Could not resolve '%s'! Error: %s", remotehost.c_str(), gai_strerror(s)); - return ""; - } - char tmpBuffer[17] = "\000\000\000\000\000\000\000\000\000\000\377\377\000\000\000\000"; - for (rp = result; rp != NULL; rp = rp->ai_next){ - if (rp->ai_family == AF_INET){memcpy(tmpBuffer + 12, &((sockaddr_in *)rp->ai_addr)->sin_addr.s_addr, 4);} - if (rp->ai_family == AF_INET6){memcpy(tmpBuffer, ((sockaddr_in6 *)rp->ai_addr)->sin6_addr.s6_addr, 16);} - } - freeaddrinfo(result); - return std::string(tmpBuffer, 16); - }else{ - return ""; - } + return getIPv6BinAddr(remoteaddr); } /// Sets hostname for connection manually. /// Overwrites the detected host, thus possibily making it incorrect. void Socket::Connection::setHost(std::string host){ remotehost = host; + struct addrinfo *result, *rp, hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED; + hints.ai_protocol = 0; + hints.ai_canonname = NULL; + hints.ai_addr = NULL; + hints.ai_next = NULL; + int s = getaddrinfo(host.c_str(), 0, &hints, &result); + if (s != 0){return;} + if (result){ + remoteaddr = *((sockaddr_in6 *)result->ai_addr); + } + freeaddrinfo(result); } /// Returns true if these sockets are the same socket. @@ -680,35 +772,10 @@ Socket::Connection::operator bool() const{ /// Returns true if the given address can be matched with the remote host. /// Can no longer return true after any socket error have occurred. -bool Socket::Connection::isAddress(std::string addr){ - struct addrinfo *result, *rp, hints; - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED; - hints.ai_protocol = 0; - hints.ai_canonname = NULL; - hints.ai_addr = NULL; - hints.ai_next = NULL; - int s = getaddrinfo(addr.c_str(), 0, &hints, &result); - if (s != 0){return false;} - - char newaddr[INET6_ADDRSTRLEN]; - newaddr[0] = 0; - for (rp = result; rp != NULL; rp = rp->ai_next){ - if (rp->ai_family == AF_INET && inet_ntop(rp->ai_family, &(((sockaddr_in *)rp->ai_addr)->sin_addr), newaddr, INET6_ADDRSTRLEN)){ - INSANE_MSG("Comparing '%s' to '%s'", remotehost.c_str(), newaddr); - if (remotehost == newaddr){return true;} - INSANE_MSG("Comparing '%s' to '::ffff:%s'", remotehost.c_str(), newaddr); - if (remotehost == std::string("::ffff:") + newaddr){return true;} - } - if (rp->ai_family == AF_INET6 && inet_ntop(rp->ai_family, &(((sockaddr_in6 *)rp->ai_addr)->sin6_addr), newaddr, INET6_ADDRSTRLEN)){ - INSANE_MSG("Comparing '%s' to '%s'", remotehost.c_str(), newaddr); - if (remotehost == newaddr){return true;} - } - } - freeaddrinfo(result); - return false; +bool Socket::Connection::isAddress(const std::string &addr){ + //Retrieve current socket binary address + std::string myBinAddr = getBinHost(); + return isBinAddress(myBinAddr, addr); } bool Socket::Connection::isLocal(){ @@ -719,23 +786,23 @@ bool Socket::Connection::isLocal(){ getifaddrs(&ifAddrStruct); - for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) { - if (!ifa->ifa_addr) { + for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next){ + if (!ifa->ifa_addr){ continue; } - if (ifa->ifa_addr->sa_family == AF_INET) { // check it is IP4 + if (ifa->ifa_addr->sa_family == AF_INET){// check it is IP4 tmpAddrPtr=&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr; inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN); INSANE_MSG("Comparing '%s' to '%s'", remotehost.c_str(), addressBuffer); if (remotehost == addressBuffer){return true;} INSANE_MSG("Comparing '%s' to '::ffff:%s'", remotehost.c_str(), addressBuffer); if (remotehost == std::string("::ffff:") + addressBuffer){return true;} - } else if (ifa->ifa_addr->sa_family == AF_INET6) { // check it is IP6 + }else if (ifa->ifa_addr->sa_family == AF_INET6){// check it is IP6 tmpAddrPtr=&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr; inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN); INSANE_MSG("Comparing '%s' to '%s'", remotehost.c_str(), addressBuffer); if (remotehost == addressBuffer){return true;} - } + } } if (ifAddrStruct!=NULL) freeifaddrs(ifAddrStruct); return false; @@ -907,10 +974,10 @@ Socket::Server::Server(std::string address, bool nonblock){ /// \returns A Socket::Connection, which may or may not be connected, depending on settings and circumstances. Socket::Connection Socket::Server::accept(bool nonblock){ if (sock < 0){return Socket::Connection(-1);} - struct sockaddr_in6 addrinfo; - socklen_t len = sizeof(addrinfo); + struct sockaddr_in6 tmpaddr; + socklen_t len = sizeof(tmpaddr); static char addrconv[INET6_ADDRSTRLEN]; - int r = ::accept(sock, (sockaddr *)&addrinfo, &len); + int r = ::accept(sock, (sockaddr *)&tmpaddr, &len); // set the socket to be nonblocking, if requested. // we could do this through accept4 with a flag, but that call is non-standard... if ((r >= 0) && nonblock){ @@ -919,21 +986,22 @@ Socket::Connection Socket::Server::accept(bool nonblock){ fcntl(r, F_SETFL, flags); } Socket::Connection tmp(r); + tmp.remoteaddr = tmpaddr; if (r < 0){ if ((errno != EWOULDBLOCK) && (errno != EAGAIN) && (errno != EINTR)){ DEBUG_MSG(DLVL_FAIL, "Error during accept - closing server socket %d.", sock); close(); } }else{ - if (addrinfo.sin6_family == AF_INET6){ - tmp.remotehost = inet_ntop(AF_INET6, &(addrinfo.sin6_addr), addrconv, INET6_ADDRSTRLEN); + if (tmpaddr.sin6_family == AF_INET6){ + tmp.remotehost = inet_ntop(AF_INET6, &(tmpaddr.sin6_addr), addrconv, INET6_ADDRSTRLEN); DEBUG_MSG(DLVL_HIGH, "IPv6 addr [%s]", tmp.remotehost.c_str()); } - if (addrinfo.sin6_family == AF_INET){ - tmp.remotehost = inet_ntop(AF_INET, &(((sockaddr_in *)&addrinfo)->sin_addr), addrconv, INET6_ADDRSTRLEN); + if (tmpaddr.sin6_family == AF_INET){ + tmp.remotehost = inet_ntop(AF_INET, &(((sockaddr_in *)&tmpaddr)->sin_addr), addrconv, INET6_ADDRSTRLEN); DEBUG_MSG(DLVL_HIGH, "IPv4 addr [%s]", tmp.remotehost.c_str()); } - if (addrinfo.sin6_family == AF_UNIX){ + if (tmpaddr.sin6_family == AF_UNIX){ DEBUG_MSG(DLVL_HIGH, "Unix connection"); tmp.remotehost = "UNIX_SOCKET"; } @@ -1251,7 +1319,7 @@ uint16_t Socket::UDPConnection::bind(int port, std::string iface, const std::str } if (multicast){ const int optval = 1; - if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) { + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0){ WARN_MSG("Could not set multicast UDP socket re-use!"); } } @@ -1408,3 +1476,4 @@ bool Socket::UDPConnection::Receive(){ int Socket::UDPConnection::getSock(){ return sock; } + diff --git a/lib/socket.h b/lib/socket.h index 0975c283..2d9e78f4 100644 --- a/lib/socket.h +++ b/lib/socket.h @@ -25,6 +25,9 @@ namespace Buffer{ namespace Socket{ void hostBytesToStr(const char *bytes, size_t len, std::string &target); + bool isBinAddress(const std::string &binAddr, std::string matchTo); + bool matchIPv6Addr(const std::string &A, const std::string &B, uint8_t prefix); + std::string getBinForms(std::string addr); /// A buffer made out of std::string objects that can be efficiently read from and written to. class Buffer{ @@ -49,12 +52,16 @@ namespace Socket{ }; // Buffer + class Server; + /// This class is for easy communicating through sockets, either TCP or Unix. class Connection{ + friend Server; private: int sock; ///< Internally saved socket number. int pipes[2]; ///< Internally saved file descriptors for pipe socket simulation. std::string remotehost; ///< Stores remote host address. + struct sockaddr_in6 remoteaddr;///< Stores remote host address. uint64_t up; uint64_t down; long long int conntime; @@ -84,7 +91,7 @@ namespace Socket{ int getPureSocket(); ///< Returns non-piped internal socket number. std::string getError(); ///< Returns a string describing the last error that occured. bool connected() const; ///< Returns the connected-state for this socket. - bool isAddress(std::string addr); + bool isAddress(const std::string &addr); bool isLocal(); ///< Returns true if remote address is a local address. // buffered i/o methods bool spool(); ///< Updates the downbufferinternal variables.