diff --git a/omega-pac/src/profiles.coffee b/omega-pac/src/profiles.coffee index 49db982..c74da02 100644 --- a/omega-pac/src/profiles.coffee +++ b/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 diff --git a/omega-pac/src/rule_list.coffee b/omega-pac/src/rule_list.coffee index 6f3988f..77a3e6d 100644 --- a/omega-pac/src/rule_list.coffee +++ b/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 diff --git a/omega-pac/test/profiles.coffee b/omega-pac/test/profiles.coffee index 4e43114..59c2c2a 100644 --- a/omega-pac/test/profiles.coffee +++ b/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')) diff --git a/omega-pac/test/rule_list.coffee b/omega-pac/test/rule_list.coffee index 30875b5..79f4e49 100644 --- a/omega-pac/test/rule_list.coffee +++ b/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'