aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Walters <rwalters@digitalstirling.com>2018-07-04 16:09:49 -0700
committerRichard Walters <rwalters@digitalstirling.com>2018-07-04 16:09:49 -0700
commitfe976143fe4c505beeca78e842602716c97b2018 (patch)
tree6ef0c0f75d462b62a93ed866918f9fd5dcf5d979
parent253ee21a8acebf6fa39e19b55904076d57a48eec (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.cpp213
-rw-r--r--test/src/UriTests.cpp45
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;
+ }
+}