diff options
author | Richard Walters <rwalters@digitalstirling.com> | 2018-07-04 16:09:49 -0700 |
---|---|---|
committer | Richard Walters <rwalters@digitalstirling.com> | 2018-07-04 16:09:49 -0700 |
commit | fe976143fe4c505beeca78e842602716c97b2018 (patch) | |
tree | 6ef0c0f75d462b62a93ed866918f9fd5dcf5d979 | |
parent | 253ee21a8acebf6fa39e19b55904076d57a48eec (diff) |
Validate IPv6 addresses
* Add ValidateIpv6Address.
* Add ValidateIpv4Address (since an IPv6 address is
allowed to contain an IPv4 address for compatibility)
* Add ValidateOctet (used by ValidateIpv4Address).
-rw-r--r-- | src/Uri.cpp | 213 | ||||
-rw-r--r-- | test/src/UriTests.cpp | 45 |
2 files changed, 258 insertions, 0 deletions
diff --git a/src/Uri.cpp b/src/Uri.cpp index 559e769..777aca2 100644 --- a/src/Uri.cpp +++ b/src/Uri.cpp @@ -167,6 +167,216 @@ namespace { } /** + * This function checks to make sure the given string + * is a valid rendering of an octet as a decimal number. + * + * @param[in] octetString + * This is the octet string to validate. + * + * @return + * An indication of whether or not the given astring + * is a valid rendering of an octet as a + * decimal number is returned. + */ + bool ValidateOctet(const std::string& octetString) { + int octet = 0; + for (auto c: octetString) { + if (DIGIT.Contains(c)) { + octet *= 10; + octet += (int)(c - '0'); + } else { + return false; + } + } + return (octet <= 255); + } + + /** + * This function checks to make sure the given address + * is a valid IPv6 address according to the rules in + * RFC 3986 (https://tools.ietf.org/html/rfc3986). + * + * @param[in] address + * This is the IPv6 address to validate. + * + * @return + * An indication of whether or not the given address + * is a valid IPv6 address is returned. + */ + bool ValidateIpv4Adress(const std::string& address) { + size_t numGroups = 0; + size_t state = 0; + std::string octetBuffer; + for (auto c: address) { + switch (state) { + case 0: { // not in an octet yet + if (DIGIT.Contains(c)) { + octetBuffer.push_back(c); + state = 1; + } else { + return false; + } + } break; + + case 1: { // expect a digit or dot + if (c == '.') { + if (numGroups++ >= 4) { + return false; + } + if (!ValidateOctet(octetBuffer)) { + return false; + } + octetBuffer.clear(); + state = 0; + } else if (DIGIT.Contains(c)) { + octetBuffer.push_back(c); + } else { + return false; + } + } break; + } + } + if (!octetBuffer.empty()) { + ++numGroups; + if (!ValidateOctet(octetBuffer)) { + return false; + } + } + return (numGroups == 4); + } + + /** + * This function checks to make sure the given address + * is a valid IPv6 address according to the rules in + * RFC 3986 (https://tools.ietf.org/html/rfc3986). + * + * @param[in] address + * This is the IPv6 address to validate. + * + * @return + * An indication of whether or not the given address + * is a valid IPv6 address is returned. + */ + bool ValidateIpv6Address(const std::string& address) { + size_t numGroups = 0; + size_t numDigits = 0; + bool doubleColonEncountered = false; + size_t state = 0; + size_t potentialIpv4AddressStart = 0; + size_t position = 0; + bool ipv4AddressEncountered = false; + for (auto c: address) { + switch (state) { + case 0: { // not in a group yet + if (c == ':') { + state = 1; + } else if (DIGIT.Contains(c)) { + potentialIpv4AddressStart = position; + ++numDigits = 1; + state = 4; + } else if (HEXDIG.Contains(c)) { + ++numDigits = 1; + state = 3; + } else { + return false; + } + } break; + + case 1: { // not in a group yet, encountered one colon + if (c == ':') { + if (doubleColonEncountered) { + return false; + } else { + doubleColonEncountered = true; + state = 2; + } + } else { + return false; + } + } break; + + case 2: { // expect a hex digit + if (DIGIT.Contains(c)) { + potentialIpv4AddressStart = position; + if (++numDigits > 4) { + return false; + } + state = 4; + } else if (HEXDIG.Contains(c)) { + if (++numDigits > 4) { + return false; + } + state = 3; + } else { + return false; + } + } break; + + case 3: { // expect either a hex digit or colon + if (c == ':') { + numDigits = 0; + ++numGroups; + state = 2; + } else if (HEXDIG.Contains(c)) { + if (++numDigits > 4) { + return false; + } + } else { + return false; + } + } break; + + case 4: { // expect either a hex digit, dot, or colon + if (c == ':') { + numDigits = 0; + ++numGroups; + } else if (c == '.') { + ipv4AddressEncountered = true; + break; + } else if (DIGIT.Contains(c)) { + if (++numDigits > 4) { + return false; + } + } else if (HEXDIG.Contains(c)) { + if (++numDigits > 4) { + return false; + } + state = 3; + } else { + return false; + } + } break; + } + if (ipv4AddressEncountered) { + break; + } + ++position; + } + if (state == 4) { + // count trailing group + ++numGroups; + } + if ( + (position == address.length()) + && (state == 1) + ) { // trailing single colon + return false; + } + if (ipv4AddressEncountered) { + if (!ValidateIpv4Adress(address.substr(potentialIpv4AddressStart))) { + return false; + } + numGroups += 2; + } + if (doubleColonEncountered) { + // A double colon matches one or more groups (of 0). + return (numGroups <= 7); + } else { + return (numGroups == 8); + } + } + + /** * This function takes a given "stillPassing" strategy * and invokes it on the sequence of characters in the given * string, to check if the string passes or not. @@ -490,6 +700,9 @@ namespace Uri { // before attempting to code it host.push_back(c); if (c == ']') { + if (!ValidateIpv6Address(host)) { + return false; + } hostParsingState = HostParsingState::GARBAGE_CHECK; } } break; diff --git a/test/src/UriTests.cpp b/test/src/UriTests.cpp index b17d492..7f4a1a0 100644 --- a/test/src/UriTests.cpp +++ b/test/src/UriTests.cpp @@ -710,3 +710,48 @@ TEST(UriTests, EmptyPathInUriWithAuthorityIsEquivalentToSlashOnlyPath) { ASSERT_TRUE(uri2.ParseFromString("//example.com/")); ASSERT_EQ(uri1, uri2); } + +TEST(UriTests, IPv6Address) { + struct TestVector { + std::string uriString; + std::string expectedHost; + bool isValid; + }; + const std::vector< TestVector > testVectors{ + // valid + {"http://[::1]/", "::1", true}, + {"http://[::ffff:1.2.3.4]/", "::ffff:1.2.3.4", true}, + {"http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]/", "2001:db8:85a3:8d3:1319:8a2e:370:7348", true}, + + // invalid + {"http://[::ffff:1.2.x.4]/", "", false}, + {"http://[::ffff:1.2.3.4.8]/", "", false}, + {"http://[::ffff:1.2.3]/", "", false}, + {"http://[::ffff:1.2.3.]/", "", false}, + {"http://[::ffff:1.2.3.256]/", "", false}, + {"http://[::fxff:1.2.3.4]/", "", false}, + {"http://[::ffff:1.2.3.-4]/", "", false}, + {"http://[::ffff:1.2.3. 4]/", "", false}, + {"http://[::ffff:1.2.3.4 ]/", "", false}, + {"http://[::ffff:1.2.3.4/", "", false}, + {"http://::ffff:1.2.3.4]/", "", false}, + {"http://::ffff:a.2.3.4]/", "", false}, + {"http://::ffff:1.a.3.4]/", "", false}, + {"http://[2001:db8:85a3:8d3:1319:8a2e:370:7348:0000]/", "", false}, + {"http://[2001:db8:85a3::8a2e:0:]/", "", false}, + {"http://[2001:db8:85a3::8a2e::]/", "", false}, + {"http://[]/", "", false}, + {"http://[:]/", "", false}, + {"http://[v]/", "", false}, + }; + size_t index = 0; + for (const auto& testVector : testVectors) { + Uri::Uri uri; + const bool parseResult = uri.ParseFromString(testVector.uriString); + ASSERT_EQ(testVector.isValid, parseResult) << index; + if (parseResult) { + ASSERT_EQ(testVector.expectedHost, uri.GetHost()); + } + ++index; + } +} |