// Copyright (C) 2015 Jérôme Leclercq // This file is part of the "Nazara Engine - Utility module" // For conditions of distribution and use, see copyright notice in Config.hpp #include #include #include #include namespace Nz { namespace Detail { bool ParseDecimal(const char* str, unsigned int* number, const char** endOfRead) { const char* ptr = str; unsigned int val = 0; while (*ptr >= '0' && *ptr <= '9') { val *= 10; val += *ptr - '0'; ++ptr; } if (str == ptr) return false; if (number) *number = val; if (endOfRead) *endOfRead = ptr; return true; } bool ParseHexadecimal(const char* str, unsigned int* number, const char** endOfRead) { const char* ptr = str; unsigned int val = 0; while ((*ptr >= '0' && *ptr <= '9') || ((*ptr & 0x5F) >= 'A' && (*ptr & 0x5F) <= 'F')) { val *= 16; val += (*ptr > '9') ? ((*ptr & 0x5F) - 'A' + 10) : *ptr - '0'; ++ptr; } if (str == ptr) return false; if (number) *number = val; if (endOfRead) *endOfRead = ptr; return true; } } // From http://rosettacode.org/wiki/Parse_an_IP_Address // Parse a textual IPv4 or IPv6 address, optionally with port, into a binary // array (for the address, in host order), and an optionally provided port. // Also, indicate which of those forms (4 or 6) was parsed. bool ParseIPAddress(const char* addressPtr, UInt8 result[16], UInt16* port, bool* isIPv6, const char** endOfRead) { NazaraAssert(addressPtr, "Invalid address string"); NazaraAssert(result, "Invalid result pointer"); //find first colon, dot, and open bracket const char* colonPtr = std::strchr(addressPtr, ':'); const char* dotPtr = std::strchr(addressPtr, '.'); const char* openBracketPtr = std::strchr(addressPtr, '['); // we'll consider this to (probably) be IPv6 if we find an open // bracket, or an absence of dots, or if there is a colon, and it // precedes any dots that may or may not be there bool detectedIPv6 = openBracketPtr || !dotPtr || (colonPtr && (!dotPtr || colonPtr < dotPtr)); // OK, now do a little further sanity check our initial guess... if (detectedIPv6) { // if open bracket, then must have close bracket that follows somewhere const char* closeBracketPtr = std::strchr(addressPtr, ']'); if (openBracketPtr && (!closeBracketPtr || closeBracketPtr < openBracketPtr)) return false; } else // probably ipv4 { // dots must exist, and precede any colons if (!dotPtr || (colonPtr && colonPtr < dotPtr)) return false; } // OK, there should be no correctly formed strings which are miscategorized, // and now any format errors will be found out as we continue parsing // according to plan. if (!detectedIPv6) //try to parse as IPv4 { // 4 dotted quad decimal; optional port if there is a colon // since there are just 4, and because the last one can be terminated // differently, I'm just going to unroll any potential loop. UInt8* resultPtr = result; for (unsigned int i = 0; i < 4; ++i) { unsigned int value; if (!Detail::ParseDecimal(addressPtr, &value, &addressPtr) || value > 255) //must be in range and followed by dot and nonempty return false; if (i != 3) { if (*addressPtr != '.') return false; addressPtr++; } *resultPtr++ = static_cast(value); } } else // try to parse as IPv6 { UInt8* resultPtr; UInt8* zeroLoc; // up to 8 16-bit hex quantities, separated by colons, with at most one // empty quantity, acting as a stretchy run of zeros. optional port // if there are brackets followed by colon and decimal port number. // A further form allows an ipv4 dotted quad instead of the last two // 16-bit quantities, but only if in the ipv4 space ::ffff:x:x . if (openBracketPtr) // start past the open bracket, if it exists addressPtr = openBracketPtr + 1; resultPtr = result; zeroLoc = nullptr; // if we find a 'zero compression' location bool mappedIPv4 = false; unsigned int i; for (i = 0; i < 8; ++i) // we've got up to 8 of these, so we will use a loop { const char* savedPtr = addressPtr; unsigned int value; // get value; these are hex if (!Detail::ParseHexadecimal(addressPtr, &value, &addressPtr)) // if empty, we are zero compressing; note the loc { if (zeroLoc) //there can be only one! { // unless it's a terminal empty field, then this is OK, it just means we're done with the host part if (resultPtr == zeroLoc) { --i; break; } return false; // otherwise, it's a format error } if (*addressPtr != ':') // empty field can only be via : return false; if (i == 0 && *++addressPtr != ':') // leading zero compression requires an extra peek, and adjustment return false; zeroLoc = resultPtr; ++addressPtr; } else { if ('.' == *addressPtr) // special case of ipv4 convenience notation { addressPtr = savedPtr; // who knows how to parse ipv4? we do! UInt8 ipv4[16]; bool ipv6; if (!ParseIPAddress(addressPtr, ipv4, nullptr, &ipv6, &addressPtr) || ipv6) // must parse and must be ipv4 return false; // transfer addrlocal into the present location for (unsigned int j = 0; j < 4; ++j) *(resultPtr++) = ipv4[j]; ++i; // pretend like we took another short, since the ipv4 effectively is two shorts mappedIPv4 = true; // remember how we got here for further validation later break; // totally done with address } if (value > 65535) // must be 16 bit quantity return false; *(resultPtr++) = value >> 8; *(resultPtr++) = value & 0xFF; if (*addressPtr == ':') // typical case inside; carry on ++addressPtr; else // some other terminating character; done with this parsing parts break; } } // handle any zero compression we found if (zeroLoc) { std::ptrdiff_t nHead = (int) (zeroLoc - result); // how much before zero compression std::ptrdiff_t nTail = i * 2 - nHead; // how much after zero compression std::ptrdiff_t nZeros = 16 - nTail - nHead; // how much zeros std::memmove(&result[16 - nTail], zeroLoc, nTail); // scootch stuff down std::memset(zeroLoc, 0, nZeros); // clear the compressed zeros } // validation of ipv4 subspace ::ffff:x.x if (mappedIPv4) { static const UInt8 abyPfx[] = {0,0, 0,0, 0,0, 0,0, 0,0, 0xFF,0xFF}; if (std::memcmp(result, abyPfx, sizeof(abyPfx)) != 0) return false; } // close bracket if (openBracketPtr) { if (*addressPtr != ']') return false; ++addressPtr; } } // if asked to read the port if (port) { if (*addressPtr == ':') // have port part { ++addressPtr; // past the colon unsigned int portValue; if (!Detail::ParseDecimal(addressPtr, &portValue, nullptr) || portValue > 65535) return false; if (port) *port = static_cast(portValue); } else // finished just with IP address *port = 0; // indicate we have no port part } if (isIPv6) *isIPv6 = detectedIPv6; if (endOfRead) *endOfRead = addressPtr; return true; } }