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

This commit is contained in:
FelisCatus 2015-01-15 15:22:51 +08:00
parent 045d852739
commit df9fbc4958
2 changed files with 118 additions and 10 deletions

View File

@ -250,7 +250,7 @@ module.exports = exports =
if parts.length > 1 if parts.length > 1
addr = @parseIp parts[0] addr = @parseIp parts[0]
prefixLen = parseInt(parts[1]) prefixLen = parseInt(parts[1])
if addr and prefixLen if addr and not isNaN(prefixLen)
cache.ip = cache.ip =
conditionType: 'IpCondition' conditionType: 'IpCondition'
ip: parts[0] ip: parts[0]
@ -265,11 +265,7 @@ module.exports = exports =
serverRegex = null serverRegex = null
if serverIp? if serverIp?
if serverIp.regularExpressionString? 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) regexStr = serverIp.regularExpressionString(true)
console.log(regexStr)
serverRegex = '\\[' + regexStr + '\\]' serverRegex = '\\[' + regexStr + '\\]'
else else
server = @normalizeIp serverIp server = @normalizeIp serverIp
@ -292,7 +288,7 @@ module.exports = exports =
match: (condition, request, cache) -> match: (condition, request, cache) ->
cache = cache.analyzed cache = cache.analyzed
return false if cache.scheme? and cache.scheme != request.scheme 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?
if cache.host == '<local>' if cache.host == '<local>'
return request.host in @localHosts return request.host in @localHosts
@ -381,7 +377,7 @@ module.exports = exports =
mask = if cache.addr.v4 mask = if cache.addr.v4
new IP.v4.Address('255.255.255.255/' + cache.addr.subnetMask) new IP.v4.Address('255.255.255.255/' + cache.addr.subnetMask)
else else
new IP.v6.Address(@ipv6Max + cache.addr.subnetMask) new IP.v6.Address(@ipv6Max + '/' + cache.addr.subnetMask)
cache.mask = @normalizeIp mask.startAddress() cache.mask = @normalizeIp mask.startAddress()
cache cache
match: (condition, request, cache) -> match: (condition, request, cache) ->
@ -392,7 +388,7 @@ module.exports = exports =
return addr.isInSubnet cache.addr return addr.isInSubnet cache.addr
compile: (condition, cache) -> compile: (condition, cache) ->
cache = cache.analyzed cache = cache.analyzed
new U2.AST_Call( isInNetCall = new U2.AST_Call(
expression: new U2.AST_SymbolRef name: 'isInNet' expression: new U2.AST_SymbolRef name: 'isInNet'
args: [ args: [
new U2.AST_SymbolRef name: 'host' new U2.AST_SymbolRef name: 'host'
@ -400,6 +396,39 @@ module.exports = exports =
new U2.AST_String value: cache.mask 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': 'HostLevelsCondition':
tag: (condition) -> condition.minValue + '~' + condition.maxValue tag: (condition) -> condition.minValue + '~' + condition.maxValue
analyze: (condition) -> '.'.charCodeAt 0 analyze: (condition) -> '.'.charCodeAt 0

View File

@ -175,16 +175,95 @@ describe 'Conditions', ->
pattern: 'http://127.0.0.1:8080' pattern: 'http://127.0.0.1:8080'
testCond(cond, 'http://127.0.0.1:8080/', 'match') testCond(cond, 'http://127.0.0.1:8080/', 'match')
testCond(cond, 'http://127.0.0.2:8080/', not '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', -> it 'should correctly support IPv6 canonicalization', ->
cond = cond =
conditionType: 'BypassCondition' conditionType: 'BypassCondition'
pattern: 'http://[0:0::1]:8080' pattern: 'http://[0:0::1]:8080'
result = Conditions.analyze(cond) result = Conditions.analyze(cond)
console.log(result.analyzed)
testCond(cond, 'http://[::1]:8080/', 'match') testCond(cond, 'http://[::1]:8080/', 'match')
testCond(cond, 'http://[1::1]:8080/', not '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', -> describe 'KeywordCondition', ->
cond = cond =
conditionType: 'KeywordCondition' conditionType: 'KeywordCondition'