From: Michal Novotny <michal.novotny@gmail.com>
Date: Tue, 10 Jan 2012 23:12:41 +0100
Subject: Bug 504014 - Enforce RFC 3986 syntax for IPv6 literals a=dveditz

---
 netwerk/base/src/nsURLHelper.cpp    |   91 +++++++++++++++++++++++++++++++++++
 netwerk/base/src/nsURLHelper.h      |   10 ++++
 netwerk/base/src/nsURLParsers.cpp   |    7 +++
 netwerk/test/unit/test_bug504014.js |   73 ++++++++++++++++++++++++++++
 4 files changed, 181 insertions(+), 0 deletions(-)
 create mode 100644 netwerk/test/unit/test_bug504014.js

diff --git a/netwerk/base/src/nsURLHelper.cpp b/netwerk/base/src/nsURLHelper.cpp
index e4a4574..8ea4b56 100644
--- a/netwerk/base/src/nsURLHelper.cpp
+++ b/netwerk/base/src/nsURLHelper.cpp
@@ -991,3 +991,94 @@ net_IsValidHostName(const nsCSubstring &host)
     PRNetAddr addr;
     return PR_StringToNetAddr(strhost.get(), &addr) == PR_SUCCESS;
 }
+
+PRBool
+net_IsValidIPv4Addr(const char *addr, PRInt32 addrLen)
+{
+    const char *p;
+
+    PRInt32 octet = -1;   // means no digit yet
+    PRInt32 dotCount = 0; // number of dots in the address
+
+    for (p = addr; addrLen; ++p, --addrLen) {
+        if (*p == '.') {
+            dotCount++;
+            if (octet == -1) {
+                // invalid octet
+                return PR_FALSE;
+            }
+            octet = -1;
+        } else if (*p >= '0' && *p <='9') {
+            if (octet == 0) {
+                // leading 0 is not allowed
+                return PR_FALSE;
+            } else if (octet == -1) {
+                octet = *p - '0';
+            } else {
+                octet *= 10;
+                octet += *p - '0';
+                if (octet > 255)
+                    return PR_FALSE;
+            }
+        } else {
+            // invalid character
+            return PR_FALSE;
+        }
+    }
+
+    return (dotCount == 3 && octet != -1);
+}
+
+PRBool
+net_IsValidIPv6Addr(const char *addr, PRInt32 addrLen)
+{
+    const char *p;
+
+    PRInt32 digits = 0; // number of digits in current block
+    PRInt32 colons = 0; // number of colons in a row during parsing
+    PRInt32 blocks = 0; // number of hexadecimal blocks
+    PRBool haveZeros = PR_FALSE; // true if double colon is present in the address
+
+    for (p = addr; addrLen; ++p, --addrLen) {
+        if (*p == ':') {
+            if (colons == 0) {
+                if (digits != 0) {
+                    digits = 0;
+                    blocks++;
+                }
+            } else if (colons == 1) {
+                if (haveZeros)
+                    return PR_FALSE; // only one occurrence is allowed
+                haveZeros = PR_TRUE;
+            } else {
+                // too many colons in a row
+                return PR_FALSE;
+            }
+            colons++;
+        } else if ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f') ||
+                   (*p >= 'A' && *p <= 'F')) {
+            if (colons == 1 && blocks == 0) // starts with a single colon
+                return PR_FALSE;
+            if (digits == 4) // too many digits
+                return PR_FALSE;
+            colons = 0;
+            digits++;
+        } else if (*p == '.') {
+            // check valid IPv4 from the beginning of the last block
+            if (!net_IsValidIPv4Addr(p - digits, addrLen + digits))
+                return PR_FALSE;
+            return (haveZeros && blocks < 6) || (!haveZeros && blocks == 6);
+        } else {
+            // invalid character
+            return PR_FALSE;
+        }
+    }
+
+    if (colons == 1) // ends with a single colon
+        return PR_FALSE;
+
+    if (digits) // there is a block at the end
+        blocks++;
+
+    return (haveZeros && blocks < 8) || (!haveZeros && blocks == 8);
+}
diff --git a/netwerk/base/src/nsURLHelper.h b/netwerk/base/src/nsURLHelper.h
index 7bdbff8..3578da9 100644
--- a/netwerk/base/src/nsURLHelper.h
+++ b/netwerk/base/src/nsURLHelper.h
@@ -244,4 +244,14 @@ inline char *net_RFindCharNotInSet(const char *str, const char *set)
  */
 NS_HIDDEN_(PRBool) net_IsValidHostName(const nsCSubstring &host);
 
+/**
+ * Checks whether the IPv4 address is valid according to RFC 3986 section 3.2.2.
+ */
+NS_HIDDEN_(PRBool) net_IsValidIPv4Addr(const char *addr, PRInt32 addrLen);
+
+/**
+ * Checks whether the IPv6 address is valid according to RFC 3986 section 3.2.2.
+ */
+NS_HIDDEN_(PRBool) net_IsValidIPv6Addr(const char *addr, PRInt32 addrLen);
+
 #endif // !nsURLHelper_h__
diff --git a/netwerk/base/src/nsURLParsers.cpp b/netwerk/base/src/nsURLParsers.cpp
index f83c100..da60fef 100644
--- a/netwerk/base/src/nsURLParsers.cpp
+++ b/netwerk/base/src/nsURLParsers.cpp
@@ -631,6 +631,13 @@ nsAuthURLParser::ParseServerInfo(const char *serverinfo, PRInt32 serverinfoLen,
         if (port)
            *port = -1;
     }
+
+    // In case of IPv6 address check its validity
+    if (*hostnameLen > 1 && *(serverinfo + *hostnamePos) == '[' &&
+        *(serverinfo + *hostnamePos + *hostnameLen - 1) == ']' &&
+        !net_IsValidIPv6Addr(serverinfo + *hostnamePos + 1, *hostnameLen - 2))
+            return NS_ERROR_MALFORMED_URI;
+
     return NS_OK;
 }
 
diff --git a/netwerk/test/unit/test_bug504014.js b/netwerk/test/unit/test_bug504014.js
new file mode 100644
index 0000000..527a02a
--- /dev/null
+++ b/netwerk/test/unit/test_bug504014.js
@@ -0,0 +1,73 @@
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+var valid_URIs = [ "http://[::]/",
+                   "http://[::1]/",
+                   "http://[1::]/",
+                   "http://[::]/",
+                   "http://[::1]/",
+                   "http://[1::]/",
+                   "http://[1:2:3:4:5:6:7::]/",
+                   "http://[::1:2:3:4:5:6:7]/",
+                   "http://[1:2:a:B:c:D:e:F]/",
+                   "http://[1::8]/",
+                   "http://[1:2::8]/",
+                   "http://[0000:0123:4567:89AB:CDEF:abcd:ef00:0000]/",
+                   "http://[::192.168.1.1]/",
+                   "http://[1::0.0.0.0]/",
+                   "http://[1:2::255.255.255.255]/",
+                   "http://[1:2:3::255.255.255.255]/",
+                   "http://[1:2:3:4::255.255.255.255]/",
+                   "http://[1:2:3:4:5::255.255.255.255]/",
+                   "http://[1:2:3:4:5:6:255.255.255.255]/"];
+
+var invalid_URIs = [ "http://[1]/",
+                     "http://[192.168.1.1]/",
+                     "http://[:::]/",
+                     "http://[:::1]/",
+                     "http://[1:::]/",
+                     "http://[::1::]/",
+                     "http://[1:2:3:4:5:6:7:]/",
+                     "http://[:2:3:4:5:6:7:8]/",
+                     "http://[1:2:3:4:5:6:7:8:]/",
+                     "http://[:1:2:3:4:5:6:7:8]/",
+                     "http://[1:2:3:4:5:6:7:8::]/",
+                     "http://[::1:2:3:4:5:6:7:8]/",
+                     "http://[1:2:3:4:5:6:7]/",
+                     "http://[1:2:3:4:5:6:7:8:9]/",
+                     "http://[00001:2:3:4:5:6:7:8]/",
+                     "http://[0001:2:3:4:5:6:7:89abc]/",
+                     "http://[A:b:C:d:E:f:G:h]/",
+                     "http://[::192.168.1]/",
+                     "http://[::192.168.1.]/",
+                     "http://[::.168.1.1]/",
+                     "http://[::192..1.1]/",
+                     "http://[::0192.168.1.1]/",
+                     "http://[::256.255.255.255]/",
+                     "http://[::1x.255.255.255]/",
+                     "http://[::192.4294967464.1.1]/",
+                     "http://[1:2:3:4:5:6::255.255.255.255]/",
+                     "http://[1:2:3:4:5:6:7:255.255.255.255]/"];
+
+function run_test() {
+  var ios = Cc["@mozilla.org/network/io-service;1"].
+    getService(Ci.nsIIOService);
+
+  for (var i=0 ; i<valid_URIs.length ; i++) {
+    try {
+      var uri = ios.newURI(valid_URIs[i], null, null);
+    } catch (e) {
+      do_throw("cannot create URI:" + valid_URIs[i]);
+    }
+  }
+
+  for (var i=0 ; i<invalid_URIs.length ; i++) {
+    try {
+      var uri = ios.newURI(invalid_URIs[i], null, null);
+      do_throw("should throw: " + invalid_URIs[i]);
+    } catch (e) {
+      do_check_eq(e.result, Cr.NS_ERROR_MALFORMED_URI);
+    }
+  }
+}
