259 lines
7.2 KiB
C++
259 lines
7.2 KiB
C++
// 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 <Nazara/Network/Algorithm.hpp>
|
|
#include <Nazara/Core/Error.hpp>
|
|
#include <cstring>
|
|
#include <Nazara/Network/Debug.hpp>
|
|
|
|
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<UInt8>(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<UInt16>(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;
|
|
}
|
|
}
|