class Arcana::Pattern

Attributes

flags[R]
type[R]
value[R]

Public Class Methods

new(type, value) click to toggle source
# File lib/arcana.rb, line 133
def initialize(type, value)
  type, *@flags = type.split("/")
  @type, *@type_ops = type.split(/(?=[&%+-])/)
  @value = value
end

Public Instance Methods

match?(input) click to toggle source
# File lib/arcana.rb, line 139
def match?(input)
  return true if @value == "x"

  return if !input
  return if input.eof?
  flags = @flags.dup

  case @type
  when "string", "ustring"
    flags.delete("b") # force on binary files
    flags.delete("t") # force on text files

    flags.delete("w") # FIXME: blanks
    flags.delete("W") # FIXME: blanks
    flags.delete("c") # FIXME: case insensitive
    flags.delete("C") # FIXME: case insensitive

    if @value.start_with?("!")
      test_string = parse_string(@value[1..])
      input.read(test_string.length) != test_string
    elsif @value.start_with?("=")
      test_string = parse_string(@value[1..])
      input.read(test_string.length) == test_string
    else
      test_string = parse_string(@value)
      input.read(test_string.length) == test_string
    end
  when "byte"
    match_packed_integer?(input, "c", 1)
  when "ubyte"
    match_packed_integer?(input, "C", 1)
  when "short"
    match_packed_integer?(input, "s", 2)
  when "ushort"
    match_packed_integer?(input, "S", 2)
  when "long"
    match_packed_integer?(input, "l", 4)
  when "ulong"
    match_packed_integer?(input, "L", 4)
  when "quad"
    match_packed_integer?(input, "q", 8)
  when "uquad"
    match_packed_integer?(input, "Q", 8)
  when "leshort"
    match_packed_integer?(input, "s<", 2)
  when "uleshort"
    match_packed_integer?(input, "S<", 2)
  when "beshort"
    match_packed_integer?(input, "s>", 2)
  when "ubeshort"
    match_packed_integer?(input, "S>", 2)
  when "lelong"
    match_packed_integer?(input, "l<", 4)
  when "ulelong"
    match_packed_integer?(input, "L<", 4)
  when "belong"
    match_packed_integer?(input, "l>", 4)
  when "ubelong"
    match_packed_integer?(input, "L>", 4)
  when "bequad"
    match_packed_integer?(input, "q>", 8)
  when "ubequad"
    match_packed_integer?(input, "Q>", 8)
  when "lequad"
    match_packed_integer?(input, "q<", 8)
  when "ulequad"
    match_packed_integer?(input, "Q<", 8)
  when "pstring"
    return false # FIXME
  when "guid"
    return false # FIXME
  when "der"
    return false # FIXME
  when "lestring16"
    return false # FIXME
  when "default"
    return true # FIXME
  when "clear"
    return true # FIXME
  when "name"
    return false
  when "use"
    return false
  when "offset"
    match_integer?(input.offset)
  when "indirect"
    return false # FIXME
  when "ledate"
    return false # FIXME
  when "bedate"
    return false # FIXME
  when "beldate"
    return false # FIXME
  when "beqdate"
    return false # FIXME
  when "lefloat"
    return false # FIXME
  when "regex"
    if length = flags[0]
      if length.end_with?("l")
        # lines
        length = 8196
      elsif length.match?(/\A[0-9]+\z/)
        length = Integer(length)
      else
        return false # FIXME
      end
    else
      length = 8196
    end
    regex = parse_string(@value)
    # FIXME: seek input to result location
    input.peek(length).match?(regex)
  when "search"
    flags = @flags

    flags.delete("b") # force on binary files
    flags.delete("t") # force on text files

    flags.delete("c") # FIXME: case insensitive
    flags.delete("C") # FIXME: case insensitive

    flags = ["1"] if flags.empty? # FIXME: WTF?
    search_input = input.peek(@value.size + Integer(flags[0]) - 1)
    flags = flags[1..]

    value = parse_string(@value)

    # FIXME: seek input to result location
    search_input.include?(value)
  else
    raise "Unsupported match type: #{@type}"
  end
end

Private Instance Methods

match_integer?(val, bitwidth: 64, match_value: @value) click to toggle source
# File lib/arcana.rb, line 299
def match_integer?(val, bitwidth: 64, match_value: @value)
  return true if match_value == "x"
  return false unless val

  @type_ops.each do |op|
    op.match(/\A([&%])?(0x[0-9a-fA-F]+|-?[0-9]+)[lL]?\z/) || raise
    operand = Integer($2)
    case $1
    when "&"
      val &= operand
    when "%"
      val %= operand
    end
  end

  if match_value.match(/\A([=><!&^])? ?(0x[0-9a-fA-F]+|-?[0-9]+)[lL]?\z/)
    operator = $1
    comparison = Integer($2)

    if $2.start_with?("0x") && !@type.start_with?("u")
      # is it signed?
      if comparison.anybits?(1 << (bitwidth - 1))
        comparison = -(((1 << bitwidth) - 1) ^ comparison) - 1
      end
    end

    if @type_ops.any?
      comparison &= (1 << bitwidth) - 1
    end

    case operator
    when "=", nil
      val == comparison
    when "<"
      val < comparison
    when ">"
      val > comparison
    when "!"
      val != comparison
    when "&"
      (val & comparison) == comparison
    when "^"
      (val & comparison) == 0
    end
  else
    binding.irb
    false # FIXME
  end
end
match_packed_integer?(input, pack_str, length) click to toggle source
# File lib/arcana.rb, line 292
def match_packed_integer?(input, pack_str, length)
  input = input.read(length)
  return false unless input && input.length == length
  val = input.unpack(pack_str)[0]
  match_integer?(val, bitwidth: length*8)
end
parse_string(value) click to toggle source
# File lib/arcana.rb, line 276
def parse_string(value)
  value = value.dup.b
  value.gsub!(/\\([0-7]{1,3})/) { |match| Integer($1, 8).chr rescue binding.irb }
  value.gsub!(/\\x([0-9a-fA-F]{2})/) { |match| Integer($1, 16).chr }
  value.gsub!(/\\(.)/) do
    case $1
    when "n" then "\n"
    when "t" then "\t"
    when "f" then "\f"
    when "r" then "\r"
    else $1
    end
  end
  value
end