瀏覽代碼

Improve error handling for rule lists.

FelisCatus 10 年之前
父節點
當前提交
054d1c8b58
共有 4 個文件被更改,包括 82 次插入23 次删除
  1. 4 1
      omega-pac/src/profiles.coffee
  2. 49 22
      omega-pac/src/rule_list.coffee
  3. 18 0
      omega-pac/test/profiles.coffee
  4. 11 0
      omega-pac/test/rule_list.coffee

+ 4 - 1
omega-pac/src/profiles.coffee

@@ -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

+ 49 - 22
omega-pac/src/rule_list.coffee

@@ -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,23 +93,31 @@ 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
-          # TODO(catus): What if rule.profileName contains ' +' or new lines?
-          line += ' +' + rule.profileName
+        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
       if withResult
         # TODO(catus): Also special chars and sequences in defaultProfileName.
         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

+ 18 - 0
omega-pac/test/profiles.coffee

@@ -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'))

+ 11 - 0
omega-pac/test/rule_list.coffee

@@ -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'