Browse Source

Fix compiling of IpCondition related to IPv6 address. Fix #67.

FelisCatus 11 years ago
parent
commit
df9fbc4958
2 changed files with 118 additions and 10 deletions
  1. 37 8
      omega-pac/src/conditions.coffee
  2. 81 2
      omega-pac/test/conditions.coffee

+ 37 - 8
omega-pac/src/conditions.coffee

@@ -250,7 +250,7 @@ module.exports = exports =
         if parts.length > 1
           addr = @parseIp parts[0]
           prefixLen = parseInt(parts[1])
-          if addr and prefixLen
+          if addr and not isNaN(prefixLen)
             cache.ip =
               conditionType: 'IpCondition'
               ip: parts[0]
@@ -265,11 +265,7 @@ module.exports = exports =
         serverRegex = null
         if serverIp?
           if serverIp.regularExpressionString?
-            # TODO(felis): IPv6 regex is not fully supported by the ipv6
-            # module. Even simple addresses like ::1 will fail. Shall we
-            # implement that instead?
             regexStr = serverIp.regularExpressionString(true)
-            console.log(regexStr)
             serverRegex = '\\[' + regexStr + '\\]'
           else
             server = @normalizeIp serverIp
@@ -292,7 +288,7 @@ module.exports = exports =
       match: (condition, request, cache) ->
         cache = cache.analyzed
         return false if cache.scheme? and cache.scheme != request.scheme
-        return false if cache.ip? and @match cache.ip, request
+        return false if cache.ip? and not @match cache.ip, request
         if cache.host?
           if cache.host == '<local>'
             return request.host in @localHosts
@@ -381,7 +377,7 @@ module.exports = exports =
         mask = if cache.addr.v4
           new IP.v4.Address('255.255.255.255/' + cache.addr.subnetMask)
         else
-          new IP.v6.Address(@ipv6Max + cache.addr.subnetMask)
+          new IP.v6.Address(@ipv6Max + '/' + cache.addr.subnetMask)
         cache.mask = @normalizeIp mask.startAddress()
         cache
       match: (condition, request, cache) ->
@@ -392,7 +388,7 @@ module.exports = exports =
         return addr.isInSubnet cache.addr
       compile: (condition, cache) ->
         cache = cache.analyzed
-        new U2.AST_Call(
+        isInNetCall = new U2.AST_Call(
           expression: new U2.AST_SymbolRef name: 'isInNet'
           args: [
             new U2.AST_SymbolRef name: 'host'
@@ -400,6 +396,39 @@ module.exports = exports =
             new U2.AST_String value: cache.mask
           ]
         )
+        if cache.addr.v4 then isInNetCall else
+          isInNetExCall = new U2.AST_Call(
+            expression: new U2.AST_SymbolRef name: 'isInNetEx'
+            args: [
+              new U2.AST_SymbolRef name: 'host'
+              new U2.AST_String value: cache.addr.address
+            ]
+          )
+          alternative = if cache.addr.subnetMask > 0 then isInNetCall else
+            # ::/0 ==> Just detect whether address is IPv6 (containing colons).
+            new U2.AST_Binary(
+              left: new U2.AST_Call(
+                expression: new U2.AST_Dot(
+                  expression: new U2.AST_SymbolRef name: 'host'
+                  property: 'indexOf'
+                )
+                args: [new U2.AST_String value: ':']
+              )
+              operator: '>='
+              right: new U2.AST_Number value: 0
+            )
+          new U2.AST_Conditional(
+            condition: new U2.AST_Binary(
+              left: new U2.AST_UnaryPrefix(
+                operator: 'typeof'
+                expression: new U2.AST_SymbolRef name: 'isInNetEx'
+              )
+              operator: '==='
+              right: new U2.AST_String value: 'function'
+            )
+            consequent: isInNetExCall
+            alternative: alternative
+          )
     'HostLevelsCondition':
       tag: (condition) -> condition.minValue + '~' + condition.maxValue
       analyze: (condition) -> '.'.charCodeAt 0

+ 81 - 2
omega-pac/test/conditions.coffee

@@ -175,16 +175,95 @@ describe 'Conditions', ->
         pattern: 'http://127.0.0.1:8080'
       testCond(cond, 'http://127.0.0.1:8080/', 'match')
       testCond(cond, 'http://127.0.0.2:8080/', not 'match')
-    # TODO(felis): Not yet supported. See the code for BypassCondition.
     it 'should correctly support IPv6 canonicalization', ->
       cond =
         conditionType: 'BypassCondition'
         pattern: 'http://[0:0::1]:8080'
       result = Conditions.analyze(cond)
-      console.log(result.analyzed)
       testCond(cond, 'http://[::1]:8080/', 'match')
       testCond(cond, 'http://[1::1]:8080/', not 'match')
 
+    it 'should parse IPv4 CIDR notation', ->
+      cond =
+        conditionType: 'BypassCondition'
+        pattern: '192.168.0.0/16'
+      result = Conditions.analyze(cond).analyzed
+      should.exist(result.ip)
+      result.ip.should.eql({
+        conditionType: 'IpCondition'
+        ip: '192.168.0.0'
+        prefixLength: 16
+      })
+
+    it 'should parse IPv6 CIDR notation', ->
+      cond =
+        conditionType: 'BypassCondition'
+        pattern: 'fefe:13::abc/33'
+      result = Conditions.analyze(cond).analyzed
+      should.exist(result.ip)
+      result.ip.should.eql({
+        conditionType: 'IpCondition'
+        ip: 'fefe:13::abc'
+        prefixLength: 33
+      })
+
+    it 'should parse IPv6 CIDR notation with zero prefixLength', ->
+      cond =
+        conditionType: 'BypassCondition'
+        pattern: '::/0'
+      result = Conditions.analyze(cond).analyzed
+      should.exist(result.ip)
+      result.ip.should.eql({
+        conditionType: 'IpCondition'
+        ip: '::'
+        prefixLength: 0
+      })
+
+  describe 'IpCondition', ->
+    # IpCondition requires isInNetEx or isInNet function provided by the PAC
+    # runner, which is not available in the unit test. So We can't use testCond
+    # here.
+    it 'should support IPv4 subnet', ->
+      cond =
+        conditionType: "IpCondition"
+        ip: '192.168.1.1'
+        prefixLength: 16
+      request = Conditions.requestFromUrl('http://192.168.4.4/')
+      Conditions.match(cond, request).should.be.true
+      compiled = Conditions.compile(cond).print_to_string()
+      compiled.should.equal('isInNet(host,"192.168.1.1","255.255.0.0")')
+    it 'should support IPv6 subnet', ->
+      cond =
+        conditionType: "IpCondition"
+        ip: 'fefe:13::abc'
+        prefixLength: 33
+
+      request = Conditions.requestFromUrl('http://[fefe:13::def]/')
+      Conditions.match(cond, request).should.be.true
+
+      compiled = Conditions.compile(cond).print_to_string()
+      compiled_args = compiled.substr(compiled.lastIndexOf('('))
+      compiled_args.should.eql('(host,"fefe:13::abc","ffff:ffff:8000::")')
+    it 'should support IPv6 subnet with zero prefixLength', ->
+      cond =
+        conditionType: "IpCondition"
+        ip: '::'
+        prefixLength: 0
+
+      request = Conditions.requestFromUrl('http://[fefe:13::def]/')
+      Conditions.match(cond, request).should.be.true
+
+      compiled = Conditions.compile(cond).print_to_string()
+      compiled.indexOf('indexOf(').should.be.above(0)
+    it 'should not match domain name to IP subnet', ->
+      cond =
+        conditionType: "IpCondition"
+        ip: '::'
+        prefixLength: 0
+
+      request = Conditions.requestFromUrl('http://www.example.com/')
+      Conditions.match(cond, request).should.be.false
+
   describe 'KeywordCondition', ->
     cond =
       conditionType: 'KeywordCondition'