Improve error handling for rule lists.

This commit is contained in:
FelisCatus 2015-02-09 13:53:11 +08:00
parent 674f501ada
commit 054d1c8b58
4 changed files with 82 additions and 23 deletions

View File

@ -389,6 +389,8 @@ module.exports = exports =
profile.matchProfileName ?= 'direct'
profile.ruleList ?= ''
directReferenceSet: (profile) ->
refs = RuleList[profile.format]?.directReferenceSet?(profile)
return refs if refs
refs = {}
for name in [profile.matchProfileName, profile.defaultProfileName]
refs[exports.nameAsKey(name)] = name
@ -407,7 +409,7 @@ module.exports = exports =
formatHandler = RuleList[format]
if not formatHandler
throw new Error "Unsupported rule list format #{format}!"
ruleList = profile.ruleList
ruleList = profile.ruleList?.trim() || ''
if formatHandler.preprocess?
ruleList = formatHandler.preprocess(ruleList)
return formatHandler.parse(ruleList, profile.matchProfileName,
@ -418,6 +420,7 @@ module.exports = exports =
exports.compile(profile, 'SwitchProfile')
updateUrl: (profile) -> profile.sourceUrl
update: (profile, data) ->
data = data.trim()
original = profile.format ? exports.formatByType[profile.profileType]
profile.profileType = 'RuleListProfile'
format = original

View File

@ -14,12 +14,10 @@ module.exports = exports =
return true
return
preprocess: (text) ->
text = text.trim()
if strStartsWith(text, exports['AutoProxy'].magicPrefix)
text = new Buffer(text, 'base64').toString('utf8')
return text
parse: (text, matchProfileName, defaultProfileName) ->
text = text.trim()
normal_rules = []
exclusive_rules = []
for line in text.split(/\n|\r/)
@ -55,21 +53,36 @@ module.exports = exports =
'Switchy':
omegaPrefix: '[SwitchyOmega Conditions'
specialLineStart: "[;#@!"
detect: (text) ->
text = text.trim()
if strStartsWith(text, exports['Switchy'].omegaPrefix)
return true
return
parse: (text, matchProfileName, defaultProfileName) ->
text = text.trim()
switchy = exports['Switchy']
parser = 'parseOmega'
if not strStartsWith(text, switchy.omegaPrefix)
if text[0] == '#' or text.indexOf('\n#') >= 0
parser = 'parseLegacy'
parser = switchy.getParser(text)
return switchy[parser](text, matchProfileName, defaultProfileName)
directReferenceSet: ({ruleList, matchProfileName, defaultProfileName}) ->
text = ruleList.trim()
switchy = exports['Switchy']
parser = switchy.getParser(text)
return unless parser == 'parseOmega'
return unless /(^|\n)@with\s+results?(\r|\n)/i.test(text)
refs = {}
for line in text.split(/\n|\r/)
line = line.trim()
if switchy.specialLineStart.indexOf(line[0]) < 0
iSpace = line.lastIndexOf(' +')
if iSpace < 0
profile = defaultProfileName || 'direct'
else
profile = line.substr(iSpace + 2).trim()
refs['+' + profile] = profile
refs
# For the omega rule list format, please see the following wiki page:
# https://github.com/FelisCatus/SwitchyOmega/wiki/SwitchyOmega-conditions-format
compose: ({rules, defaultProfileName}, {withResult, useExclusive} = {}) ->
@ -80,15 +93,15 @@ module.exports = exports =
ruleList += '@with result' + eol + eol
else
ruleList += eol
specialLineStart = exports['Switchy'].specialLineStart + '+'
for rule in rules
line = Conditions.str(rule.condition)
if line[0] == '#' or line[0] == '+'
# Escape leading # to avoid being detected as legacy format.
# Reserve leading + for condition results.
line = ': ' + line
if useExclusive and rule.profileName == defaultProfileName
line = '!' + line
else if withResult
else
if specialLineStart.indexOf(line[0]) >= 0
line = ': ' + line
if withResult
# TODO(catus): What if rule.profileName contains ' +' or new lines?
line += ' +' + rule.profileName
ruleList += line + eol
@ -97,6 +110,14 @@ module.exports = exports =
ruleList += '* +' + defaultProfileName + eol
return ruleList
getParser: (text) ->
switchy = exports['Switchy']
parser = 'parseOmega'
if not strStartsWith(text, switchy.omegaPrefix)
if text[0] == '#' or text.indexOf('\n#') >= 0
parser = 'parseLegacy'
return parser
conditionFromLegacyWildcard: (pattern) ->
if pattern[0] == '@'
pattern = pattern.substring(1)
@ -151,10 +172,12 @@ module.exports = exports =
# Exclusive rules have higher priority, so they come first.
return exclusive_rules.concat normal_rules
parseOmega: (text, matchProfileName, defaultProfileName) ->
parseOmega: (text, matchProfileName, defaultProfileName, args = {}) ->
{strict} = args
rules = []
rulesWithDefaultProfile = []
withResult = false
exclusiveProfile = null
for line in text.split(/\n|\r/)
line = line.trim()
continue if line.length == 0
@ -183,16 +206,18 @@ module.exports = exports =
else if withResult
iSpace = line.lastIndexOf(' +')
if iSpace < 0
throw new Error("Missing result profile name: " + line)
throw new Error("Missing result profile name: " + line) if strict
continue
profile = line.substr(iSpace + 2).trim()
line = line.substr(0, iSpace).trim()
defaultProfileName = profile if line == '*'
exclusiveProfile = profile if line == '*'
else
profile = matchProfileName
cond = Conditions.fromStr(line)
if not cond
throw new Error("Invalid rule: " + line)
throw new Error("Invalid rule: " + line) if strict
continue
rule = {condition: cond, profileName: profile, source: source ? line}
rules.push(rule)
@ -200,8 +225,10 @@ module.exports = exports =
rulesWithDefaultProfile.push(rule)
if withResult
if not defaultProfileName
throw new Error("Missing default rule with catch-all '*' condition!")
if not exclusiveProfile
if strict
throw new Error("Missing default rule with catch-all '*' condition")
exclusiveProfile = defaultProfileName || 'direct'
for rule in rulesWithDefaultProfile
rule.profileName = defaultProfileName
rule.profileName = exclusiveProfile
return rules

View File

@ -209,6 +209,24 @@ describe 'Profiles', ->
'+example': 'example'
'+default': 'default'
)
it 'should calulate referenced profiles for rule list with results', ->
set = Profiles.directReferenceSet({
profileType: 'RuleListProfile'
format: 'Switchy'
matchProfileName: 'ignored'
defaultProfileName: 'alsoIgnored'
ruleList: '''
[SwitchyOmega Conditions]
@with result
!*.example.org
*.example.com +ABC
* +DEF
'''
})
set.should.eql(
'+ABC': 'ABC'
'+DEF': 'DEF'
)
it 'should match requests based on the rule list', ->
testProfile(profile, 'http://localhost/example.com',
ruleListResult('example', 'example.com'))

View File

@ -291,6 +291,17 @@ describe 'RuleList', ->
result = parse(list, 'match', 'notmatch')
result.should.have.length(1)
result[0].should.eql(rule)
it 'should compose and parse conditions starting with special chars', ->
rule =
source: ': ;abc'
condition:
conditionType: 'HostWildcardCondition',
pattern: ';abc'
profileName: 'match'
list = compose({rules: [rule], defaultProfileName: 'notmatch'})
result = parse(list, 'match', 'notmatch')
result.should.have.length(1)
result[0].should.eql(rule)
it 'should parse multiple conditions', ->
rules = [{
source: '*.example.com'