# Copyright (c) 2015 Sqreen. All Rights Reserved. # Please refer to our terms for more information: https://www.sqreen.io/terms.html require 'sqreen/rule_callback' module Sqreen module Rules # matcher behavior module Matcher attr_reader :min_size def self.prepare_re_pattern(value, options, case_sensitive) res = 0 res |= Regexp::MULTILINE if options.include?('multiline') res |= Regexp::IGNORECASE unless case_sensitive r = Regexp.compile(value, res) r.match("") r end ANYWHERE_OPT = 'anywhere'.freeze def prepare(patterns) @string = {} @regexp_patterns = [] if patterns.nil? msg = "no key 'values' in data (had #{@data.keys})" raise Sqreen::Exception, msg end @funs = { ANYWHERE_OPT => lambda { |value, str| str.include?(value) }, 'starts_with'.freeze => lambda { |value, str| str.start_with?(value) }, 'ends_with'.freeze => lambda { |value, str| str.end_with?(value) }, 'equals'.freeze => lambda { |value, str| str == value }, } sizes = [] patterns.each do |entry| next unless entry type = entry['type'] val = entry['value'] opts = entry['options'] opt = ANYWHERE_OPT opt = opts.first.freeze if opts && opts.first && opts.first != '' case_sensitive = entry['case_sensitive'] || false case type when 'string' if case_sensitive case_type = :cs else case_type = :ci val.downcase! end unless @funs.keys.include?(opt) Sqreen.log.debug { "Error: unknown string option '#{opt}' " } next end @string[opt] = { :ci => [], :cs => [] } unless @string.key?(opt) @string[opt][case_type] << val sizes << entry.fetch('min_length') { val.size } when 'regexp' pattern = Matcher.prepare_re_pattern(val, opt, case_sensitive) next unless pattern @regexp_patterns << pattern sizes << entry['min_length'] else raise Sqreen::Exception, "No such matcher type #{type}" end end @min_size = sizes.min unless sizes.any?(&:nil?) return unless [@regexp_patterns, @string].map(&:empty?).all? msg = "no key 'regexp' nor 'match' in data (had #{@data.keys})" raise Sqreen::Exception, msg end def match(str) return if str.nil? || str.empty? || !str.is_a?(String) str = enforce_encoding(str) unless str.ascii_only? istr = str.downcase unless @string.empty? @string.each do |type, cases| fun = @funs[type] if fun.nil? Sqreen.log.debug { "no matching function found for type #{type}" } end cases.each do |case_type, patterns| input_str = if case_type == :ci istr else str end patterns.each do |pat| return pat if fun.call(pat, input_str) end end end if defined?(Encoding) @regexp_patterns.each do |p| next unless Encoding.compatible?(p, str) return p if p.match(str) end else @regexp_patterns.each do |p| return p if p.match(str) end end nil end private def enforce_encoding(str) encoded8bit = str.encoding.name == 'ASCII-8BIT' return str if !encoded8bit && str.valid_encoding? str.chars.map do |v| if !v.valid_encoding? || (encoded8bit && !v.ascii_only?) '' else v end end.join end end # A configurable matcher rule class MatcherRuleCB < RuleCB def initialize(*args) super(*args) prepare(@data['values']) end include Matcher end end end