chai = require 'chai' should = chai.should() describe 'Conditions', -> Conditions = require '../src/conditions' U2 = require 'uglify-js' testCond = (condition, request, should_match) -> o_request = request should_match = !!should_match if typeof request == 'string' request = Conditions.requestFromUrl(request) matchResult = Conditions.match(condition, request) condExpr = Conditions.compile(condition, request) testFunc = new U2.AST_Function( argnames: [ new U2.AST_SymbolFunarg name: 'url' new U2.AST_SymbolFunarg name: 'host' new U2.AST_SymbolFunarg name: 'scheme' ] body: [ new U2.AST_Return value: condExpr ] ) testFunc = eval '(' + testFunc.print_to_string() + ')' compileResult = testFunc(request.url, request.host, request.scheme) friendlyError = (compiled) -> # Try to give friendly assert messages instead of something like # "expect true to be false". printCond = JSON.stringify(condition) printCompiled = if compiled then 'COMPILED ' else '' printMatch = if should_match then 'to match' else 'not to match' msg = ("expect #{printCompiled}condition #{printCond} " + "#{printMatch} request #{o_request}") chai.assert(false, msg) if matchResult != should_match friendlyError() if compileResult != should_match friendlyError('compiled') return matchResult describe 'TrueCondition', -> it 'should always return true', -> testCond({conditionType: 'TrueCondition'}, {}, 'match') describe 'FalseCondition', -> it 'should always return false', -> testCond({conditionType: 'FalseCondition'}, {}, not 'match') describe 'UrlRegexCondition', -> cond = conditionType: 'UrlRegexCondition' pattern: 'example\\.com' it 'should match requests based on regex pattern', -> testCond(cond, 'http://www.example.com/', 'match') it 'should not match requests not matching the pattern', -> testCond(cond, 'http://www.example.net/', not 'match') it 'should support regex meta chars', -> con = conditionType: 'UrlRegexCondition' pattern: 'exam.*\\.com' testCond(con, 'http://www.example.com/', 'match') it 'should fallback to not match if pattern is invalid', -> con = conditionType: 'UrlRegexCondition' pattern: ')Invalid(' testCond(con, 'http://www.example.com/', not 'match') describe 'UrlWildcardCondition', -> cond = conditionType: 'UrlWildcardCondition' pattern: '*example.com*' it 'should match requests based on wildcard pattern', -> testCond(cond, 'http://www.example.com/', 'match') it 'should not match requests not matching the pattern', -> testCond(cond, 'http://www.example.net/', not 'match') it 'should support wildcard question marks', -> cond = conditionType: 'UrlWildcardCondition' pattern: '*exam???.com*' testCond(cond, 'http://www.example.com/', 'match') it 'should not support regex meta chars', -> cond = conditionType: 'UrlWildcardCondition' pattern: '.*example.com.*' testCond(cond, 'http://example.com/', not 'match') it 'should support multiple patterns in one condition', -> cond = conditionType: 'UrlWildcardCondition' pattern: '*.example.com/*|*.example.net/*' testCond(cond, 'http://a.example.com/abc', 'match') testCond(cond, 'http://b.example.net/def', 'match') testCond(cond, 'http://c.example.org/ghi', not 'match') describe 'HostRegexCondition', -> cond = conditionType: 'HostRegexCondition' pattern: '.*\\.example\\.com' it 'should match requests based on regex pattern', -> testCond(cond, 'http://www.example.com/', 'match') it 'should not match requests not matching the pattern', -> testCond(cond, 'http://example.com/', not 'match') it 'should not match URL parts other than the host', -> testCond(cond, 'http://example.net/www.example.com') .should.be.false describe 'HostWildcardCondition', -> cond = conditionType: 'HostWildcardCondition' pattern: '*.example.com' it 'should match requests based on wildcard pattern', -> testCond(cond, 'http://www.example.com/', 'match') it 'should also match hostname without the optional level', -> # https://github.com/FelisCatus/SwitchyOmega/wiki/Host-wildcard-condition testCond(cond, 'http://example.com/', 'match') it 'should process patterns like *.*example.com correctly', -> # https://github.com/FelisCatus/SwitchyOmega/issues/158 con = conditionType: 'HostWildcardCondition' pattern: '*.*example.com' testCond(con, 'http://example.com/', 'match') testCond(con, 'http://www.example.com/', 'match') testCond(con, 'http://www.some-example.com/', 'match') testCond(con, 'http://xample.com/', not 'match') it 'should allow override of the magical behavior', -> con = conditionType: 'HostWildcardCondition' pattern: '**.example.com' testCond(con, 'http://www.example.com/', 'match') testCond(con, 'http://example.com/', not 'match') it 'should not match URL parts other than the host', -> testCond(cond, 'http://example.net/www.example.com') .should.be.false it 'should support multiple patterns in one condition', -> cond = conditionType: 'HostWildcardCondition' pattern: '*.example.com|*.example.net' testCond(cond, 'http://a.example.com/abc', 'match') testCond(cond, 'http://example.net/def', 'match') testCond(cond, 'http://c.example.org/ghi', not 'match') describe 'BypassCondition', -> # See https://developer.chrome.com/extensions/proxy#bypass_list it 'should correctly support patterns containing hosts', -> cond = conditionType: 'BypassCondition' pattern: '.example.com' testCond(cond, 'http://www.example.com/', 'match') testCond(cond, 'http://example.com/', not 'match') cond.pattern = '*.example.com' testCond(cond, 'http://www.example.com/', 'match') testCond(cond, 'http://example.com/', not 'match') cond.pattern = 'example.com' testCond(cond, 'http://example.com/', 'match') testCond(cond, 'http://www.example.com/', not 'match') cond.pattern = '*example.com' testCond(cond, 'http://example.com/', 'match') testCond(cond, 'http://www.example.com/', 'match') testCond(cond, 'http://anotherexample.com/', 'match') it 'should match the scheme specified in the pattern', -> cond = conditionType: 'BypassCondition' pattern: 'http://example.com' testCond(cond, 'http://example.com/', 'match') testCond(cond, 'https://example.com/', not 'match') it 'should match the port specified in the pattern', -> cond = conditionType: 'BypassCondition' pattern: 'http://example.com:8080' testCond(cond, 'http://example.com:8080/', 'match') testCond(cond, 'http://example.com:888/', not 'match') it 'should correctly support patterns using IPv4 literals', -> cond = conditionType: 'BypassCondition' 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') it 'should correctly support IPv6 canonicalization', -> cond = conditionType: 'BypassCondition' pattern: 'http://[0:0::1]:8080' result = Conditions.analyze(cond) 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' pattern: 'example.com' it 'should match requests based on substring', -> testCond(cond, 'http://www.example.com/', 'match') testCond(cond, 'http://www.example.net/', not 'match') it 'should not match HTTPS requests', -> testCond(cond, 'https://example.com/', not 'match') testCond(cond, 'https://example.net/', not 'match') describe '#typeFromAbbr', -> it 'should get condition types by abbrs', -> Conditions.typeFromAbbr('True').should.equal('TrueCondition') Conditions.typeFromAbbr('HR').should.equal('HostRegexCondition') describe '#str and #fromStr', -> it 'should encode & decode TrueCondition correctly', -> condition = conditionType: 'TrueCondition' result = Conditions.str(condition) result.should.equal('True:') cond = Conditions.fromStr(result) cond.should.eql(condition) it 'should encode & decode conditions with pattern correctly', -> condition = conditionType: 'UrlWildcardCondition' pattern: '*://*.example.com/*' result = Conditions.str(condition) result.should.equal('UrlWildcard: ' + condition.pattern) cond = Conditions.fromStr(result) cond.should.eql(condition) it 'should encode & decode False while preserving pattern', -> condition = conditionType: 'FalseCondition' pattern: 'a b c' result = Conditions.str(condition) result.should.equal('Disabled: a b c') cond = Conditions.fromStr(result) cond.should.eql(condition) it 'should encode & decode FalseCondition without any pattern', -> condition = conditionType: 'FalseCondition' result = Conditions.str(condition) result.should.equal('Disabled:') cond = Conditions.fromStr(result) cond.should.eql(condition) it 'should encode & decode HostWildcardCondition using shorthand syntax', -> condition = conditionType: 'HostWildcardCondition' pattern: '*.example.com' result = Conditions.str(condition) result.should.equal(condition.pattern) cond = Conditions.fromStr(result) cond.should.eql(condition) it 'should encode & decode HostWildcardCondition ending with colon', -> condition = conditionType: 'HostWildcardCondition' pattern: 'bogus:' result = Conditions.str(condition) result.should.equal('HostWildcard: ' + condition.pattern) cond = Conditions.fromStr(result) cond.should.eql(condition) it 'should encode & decode IpCondition correctly', -> condition = conditionType: 'IpCondition' ip: '127.0.0.1' prefixLength: 16 result = Conditions.str(condition) result.should.equal('Ip: 127.0.0.1/16') cond = Conditions.fromStr(result) cond.should.eql(condition) it 'should parse conditions with extra spaces correctly', -> Conditions.fromStr('url: *abcde* ').should.eql({ conditionType: 'UrlWildcardCondition' pattern: '*abcde*' }) it 'should parse abbreviated condition types correctly', -> Conditions.fromStr('url: *://*.example.com/*').should.eql({ conditionType: 'UrlWildcardCondition' pattern: '*://*.example.com/*' }) it 'should parse escaped HostWildcardCondition starting with colon', -> Conditions.fromStr(': :bogus:').should.eql({ conditionType: 'HostWildcardCondition' pattern: ':bogus:' })