class Recog::Fingerprint

A fingerprint that can be {#match matched} against a particular kind of fingerprintable data, e.g. an HTTP `Server` header

Attributes

name[R]

A human readable name describing this fingerprint @return (see parse_description)

params[R]

Collection of indexes for capture groups created by {#match}

@return (see parse_params)

regex[R]

Regular expression pulled from the {DB} xml file.

@see create_regexp @return [Regexp] the Regexp to try when calling {#match}

tests[R]

Collection of example strings that should {#match} our {#regex}

@return (see parse_examples)

Public Class Methods

new(xml) click to toggle source

@param xml [Nokogiri::XML::Element]

# File lib/recog/fingerprint.rb, line 30
def initialize(xml)
  @name   = parse_description(xml)
  @regex  = create_regexp(xml)
  @params = {}
  @tests = []

  parse_examples(xml)
  parse_params(xml)
end

Public Instance Methods

match(match_string) click to toggle source

Attempt to match the given string.

@param match_string [String] @return [Hash,nil] Keys will be host, service, and os attributes

# File lib/recog/fingerprint.rb, line 44
def match(match_string)
  # match_string.force_encoding('BINARY') if match_string
  match_data = @regex.match(match_string)
  return if match_data.nil?

  result = { 'matched' => @name }
  @params.each_pair do |k,v|
    pos = v[0]
    if pos == 0
      # A match offset of 0 means this param has a hardcoded value
      result[k] = v[1]
    else
      # A match offset other than 0 means the value should come from
      # the corresponding match result index
      result[k] = match_data[ pos ]
    end
  end
  return result
end
verify_params() { |:fail, "'| ... } click to toggle source

Ensure all the {#params} are valid

@yieldparam status [Symbol] One of `:warn`, `:fail`, or `:success` to

indicate whether a param is valid

@yieldparam message [String] A human-readable string explaining the

%x`status`
# File lib/recog/fingerprint.rb, line 70
def verify_params(&block)
  return if params.empty?
  params.each do |param_name, pos_value|
    pos, value = pos_value
    next unless pos != 0 && !value.to_s.empty?
    yield :fail, "'#{@name}'s #{param_name} is a non-zero pos but specifies a value of '#{value}'"
  end
end
verify_tests() { |:warn, "'| ... } click to toggle source

Ensure all the {#tests} actually match the fingerprint and return the expected capture groups.

@yieldparam status [Symbol] One of `:warn`, `:fail`, or `:success` to

indicate whether a test worked

@yieldparam message [String] A human-readable string explaining the

%x`status`
# File lib/recog/fingerprint.rb, line 86
def verify_tests(&block)
  if tests.size == 0
    yield :warn, "'#{@name}' has no test cases"
  end

  tests.each do |test|
    result = match(test.content)
    if result.nil?
      yield :fail, "'#{@name}' failed to match #{test.content.inspect} with #{@regex}'"
      next
    end

    message = test
    status = :success
    # Ensure that all the attributes as provided by the example were parsed
    # out correctly and match the capture group values we expect.
    test.attributes.each do |k, v|
      if !result.has_key?(k) || result[k] != v
        message = "'#{@name}' failed to find expected capture group #{k} '#{v}'"
        status = :fail
        break
      end
    end
    yield status, message
  end
end

Private Instance Methods

create_regexp(xml) click to toggle source

@param xml [Nokogiri::XML::Element] @return [Regexp]

# File lib/recog/fingerprint.rb, line 117
def create_regexp(xml)
  pattern = xml['pattern']
  flags   = xml['flags'].to_s.split(',')
  RegexpFactory.build(pattern, flags)
end
parse_description(xml) click to toggle source

@param xml [Nokogiri::XML::Element] @return [String] Contents of the source XML's `description` tag

# File lib/recog/fingerprint.rb, line 125
def parse_description(xml)
  element = xml.xpath('description')
  element.empty? ? '' : element.first.content.to_s.gsub(/\s+/, ' ').strip
end
parse_examples(xml) click to toggle source

@param xml [Nokogiri::XML::Element] @return [void]

# File lib/recog/fingerprint.rb, line 132
def parse_examples(xml)
  elements = xml.xpath('example')

  elements.each do |elem|
    # convert nokogiri Attributes into a hash of name => value
    attrs = elem.attributes.values.reduce({}) { |a,e| a.merge(e.name => e.value) }
    @tests << Test.new(elem.content, attrs)
  end

  nil
end
parse_params(xml) click to toggle source

@param xml [Nokogiri::XML::Element] @return [Hash<String,Array>] Keys are things like `“os.name”`, values are a two

element Array. The first element is an index for the capture group that returns
that thing. If the index is 0, the second element is a static value for
that thing; otherwise it is undefined.
# File lib/recog/fingerprint.rb, line 149
def parse_params(xml)
  @params = {}.tap do |h|
    xml.xpath('param').each do |param|
      name  = param['name']
      pos   = param['pos'].to_i
      value = param['value'].to_s
      h[name] = [pos, value]
    end
  end

  nil
end