Bug 504014 - Enforce RFC 3986 syntax for IPv6 literals
authorMichal Novotny <michal.novotny@gmail.com>
Thu, 23 Jun 2011 17:04:23 +0200
changeset 71866 8b5646a07963004d5fb5ae969b7dbdf0087488ce
parent 71865 3e54c496db92d1199306da1131ba105cfdcdb510
child 71867 17f2489e16604e9b21e31734f6b534f05ba2b84f
push id209
push userbzbarsky@mozilla.com
push dateTue, 05 Jul 2011 17:42:16 +0000
treeherdermozilla-aurora@cc6e30cce8af [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs504014
milestone7.0a1
Bug 504014 - Enforce RFC 3986 syntax for IPv6 literals
netwerk/base/src/nsURLHelper.cpp
netwerk/base/src/nsURLHelper.h
netwerk/base/src/nsURLParsers.cpp
netwerk/test/unit/test_bug504014.js
netwerk/test/unit/xpcshell.ini
--- a/netwerk/base/src/nsURLHelper.cpp
+++ b/netwerk/base/src/nsURLHelper.cpp
@@ -32,32 +32,36 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
+#include "mozilla/RangedPtr.h"
+
 #include "nsURLHelper.h"
 #include "nsReadableUtils.h"
 #include "nsIServiceManager.h"
 #include "nsIIOService.h"
 #include "nsILocalFile.h"
 #include "nsIURLParser.h"
 #include "nsIURI.h"
 #include "nsMemory.h"
 #include "nsEscape.h"
 #include "nsCOMPtr.h"
 #include "nsCRT.h"
 #include "nsNetCID.h"
 #include "netCore.h"
 #include "prprf.h"
 #include "prnetdb.h"
 
+using namespace mozilla;
+
 //----------------------------------------------------------------------------
 // Init/Shutdown
 //----------------------------------------------------------------------------
 
 static PRBool gInitialized = PR_FALSE;
 static nsIURLParser *gNoAuthURLParser = nsnull;
 static nsIURLParser *gAuthURLParser = nsnull;
 static nsIURLParser *gStdURLParser = nsnull;
@@ -985,8 +989,99 @@ net_IsValidHostName(const nsCSubstring &
                              "ABCDEFGHIJKLMNOPQRSTUVWXYZ$+_") == end)
         return PR_TRUE;
 
     // Might be a valid IPv6 link-local address containing a percent sign
     nsCAutoString strhost(host);
     PRNetAddr addr;
     return PR_StringToNetAddr(strhost.get(), &addr) == PR_SUCCESS;
 }
+
+PRBool
+net_IsValidIPv4Addr(const char *addr, PRInt32 addrLen)
+{
+    RangedPtr<const char> p(addr, addrLen);
+
+    PRInt32 octet = -1;   // means no digit yet
+    PRInt32 dotCount = 0; // number of dots in the address
+
+    for (; 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)
+{
+    RangedPtr<const char> p(addr, addrLen);
+
+    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 (; 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.get() - 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);
+}
--- a/netwerk/base/src/nsURLHelper.h
+++ b/netwerk/base/src/nsURLHelper.h
@@ -243,9 +243,19 @@ inline char *net_RFindCharNotInSet(const
 }
 
 /**
  * This function returns true if the given hostname does not include any
  * restricted characters.  Otherwise, false is returned.
  */
 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__
--- a/netwerk/base/src/nsURLParsers.cpp
+++ b/netwerk/base/src/nsURLParsers.cpp
@@ -636,16 +636,23 @@ nsAuthURLParser::ParseServerInfo(const c
         }
     }
     else {
         // serverinfo = <hostname>
         SET_RESULT(hostname, 0, 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;
 }
 
 void
 nsAuthURLParser::ParseAfterScheme(const char *spec, PRInt32 specLen,
                                   PRUint32 *authPos, PRInt32 *authLen,
                                   PRUint32 *pathPos, PRInt32 *pathLen)
 {
new file mode 100644
--- /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);
+    }
+  }
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -40,16 +40,17 @@ tail =
 [test_bug468426.js]
 [test_bug468594.js]
 [test_bug470716.js]
 [test_bug479413.js]
 [test_bug479485.js]
 [test_bug482601.js]
 [test_bug484684.js]
 [test_bug490095.js]
+[test_bug504014.js]
 [test_bug510359.js]
 [test_bug515583.js]
 [test_bug528292.js]
 [test_bug540566.js]
 [test_bug543805.js]
 [test_bug553970.js]
 [test_bug561276.js]
 [test_bug580508.js]