Merge pull request #96 from chao-mu/master
Updates to Railgun [Fixes #6128] among other things.
This commit is contained in:
commit
9e78eff968
|
@ -0,0 +1,60 @@
|
||||||
|
require 'rex/post/meterpreter/extensions/stdapi/railgun/railgun'
|
||||||
|
|
||||||
|
module Msf
|
||||||
|
class Post
|
||||||
|
module Windows
|
||||||
|
module Railgun
|
||||||
|
|
||||||
|
# Go through each dll and add a corresponding convenience method of the same name
|
||||||
|
Rex::Post::Meterpreter::Extensions::Stdapi::Railgun::Railgun::BUILTIN_DLLS.each do |api|
|
||||||
|
# We will be interpolating within an eval. We exercise due paranoia.
|
||||||
|
unless api.to_s =~ /^\w+$/
|
||||||
|
print_error 'Something is seriously wrong with Railgun.BUILTIN_DLLS list'
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
# don't override existing methods
|
||||||
|
if method_defined? api.to_sym
|
||||||
|
# We don't warn as the override may have been intentional
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
# evaling a String is faster than calling define_method
|
||||||
|
eval "def #{api.to_s}; railgun.#{api.to_s}; end"
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return an array of windows constants names matching +winconst+
|
||||||
|
#
|
||||||
|
def select_const_names(winconst, filter_regex=nil)
|
||||||
|
railgun.constant_manager.select_const_names(winconst, filter_regex)
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Returns an array of windows error code names for a given windows error code matching +err_code+
|
||||||
|
#
|
||||||
|
def lookup_error (err_code, filter_regex=nil)
|
||||||
|
select_const_names(err_code, /^ERROR_/).select do |name|
|
||||||
|
name =~ filter_regex
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def memread(address, length)
|
||||||
|
railgun.memread(address, length)
|
||||||
|
end
|
||||||
|
|
||||||
|
def memwrite(address, length)
|
||||||
|
railgun.memwrite(address, length)
|
||||||
|
end
|
||||||
|
|
||||||
|
def railgun
|
||||||
|
client.railgun
|
||||||
|
end
|
||||||
|
|
||||||
|
def pointer_size
|
||||||
|
railgun.util.pointer_size
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -5,6 +5,8 @@ require 'test/unit'
|
||||||
require 'rex'
|
require 'rex'
|
||||||
|
|
||||||
require 'railgun/api_constants.rb.ut'
|
require 'railgun/api_constants.rb.ut'
|
||||||
|
require 'railgun/type/pointer_util.rb.ut'
|
||||||
|
require 'railgun/platform_util.rb.ut'
|
||||||
require 'railgun/buffer_item.rb.ut'
|
require 'railgun/buffer_item.rb.ut'
|
||||||
require 'railgun/dll_function.rb.ut'
|
require 'railgun/dll_function.rb.ut'
|
||||||
require 'railgun/dll_helper.rb.ut'
|
require 'railgun/dll_helper.rb.ut'
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
module Rex
|
||||||
|
module Post
|
||||||
|
module Meterpreter
|
||||||
|
module Extensions
|
||||||
|
module Stdapi
|
||||||
|
module Railgun
|
||||||
|
module PlatformUtil
|
||||||
|
|
||||||
|
X86_64 = :x86_64
|
||||||
|
X86_32 = :x86_32
|
||||||
|
|
||||||
|
def self.parse_client_platform(meterp_client_platform)
|
||||||
|
meterp_client_platform =~ /win64/ ? X86_64 : X86_32
|
||||||
|
end
|
||||||
|
|
||||||
|
end # PlatformUtil
|
||||||
|
end # Railgun
|
||||||
|
end # Stdapi
|
||||||
|
end # Extensions
|
||||||
|
end # Meterpreter
|
||||||
|
end # Post
|
||||||
|
end # Rex
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
$:.unshift(File.join(File.dirname(__FILE__), '..','..','..','..','..', '..', '..', 'lib'))
|
||||||
|
|
||||||
|
require 'rex/post/meterpreter/extensions/stdapi/railgun/platform_util'
|
||||||
|
require 'rex/post/meterpreter/extensions/stdapi/railgun/mock_magic'
|
||||||
|
require 'test/unit'
|
||||||
|
|
||||||
|
module Rex
|
||||||
|
module Post
|
||||||
|
module Meterpreter
|
||||||
|
module Extensions
|
||||||
|
module Stdapi
|
||||||
|
module Railgun
|
||||||
|
class PlatformUtil::UnitTest < Test::Unit::TestCase
|
||||||
|
def test_parse_client_platform
|
||||||
|
assert_equal(PlatformUtil.parse_client_platform('x86/win32'), PlatformUtil::X86_32,
|
||||||
|
'parse_client_platform should translate Win32 client platforms')
|
||||||
|
assert_equal(PlatformUtil.parse_client_platform('x86/win64'), PlatformUtil::X86_64,
|
||||||
|
'parse_client_platform should translate Win64 client platforms')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -61,9 +61,9 @@ class Railgun
|
||||||
# definition class 'rex/post/meterpreter/extensions/stdapi/railgun/def/'.
|
# definition class 'rex/post/meterpreter/extensions/stdapi/railgun/def/'.
|
||||||
# Naming is important and should follow convention. For example, if your
|
# Naming is important and should follow convention. For example, if your
|
||||||
# dll's name was "my_dll"
|
# dll's name was "my_dll"
|
||||||
# file name:: def_my_dll.rb
|
# file name: def_my_dll.rb
|
||||||
# class name:: Def_my_dll
|
# class name: Def_my_dll
|
||||||
# entry below:: 'my_dll'
|
# entry below: 'my_dll'
|
||||||
#
|
#
|
||||||
BUILTIN_DLLS = [
|
BUILTIN_DLLS = [
|
||||||
'kernel32',
|
'kernel32',
|
||||||
|
@ -104,6 +104,10 @@ class Railgun
|
||||||
self.dlls = {}
|
self.dlls = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.builtin_dlls
|
||||||
|
BUILTIN_DLLS
|
||||||
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# Return this Railgun's Util instance.
|
# Return this Railgun's Util instance.
|
||||||
#
|
#
|
||||||
|
@ -184,8 +188,8 @@ class Railgun
|
||||||
|
|
||||||
# For backwards compatibility, we ensure the dll is thawed
|
# For backwards compatibility, we ensure the dll is thawed
|
||||||
if dll.frozen?
|
if dll.frozen?
|
||||||
# dup will copy values, but not the frozen status
|
# Duplicate not only the dll, but its functions as well. Frozen status will be lost
|
||||||
dll = dll.dup
|
dll = Marshal.load(Marshal.dump(dll))
|
||||||
|
|
||||||
# Update local dlls with the modifiable duplicate
|
# Update local dlls with the modifiable duplicate
|
||||||
dlls[dll_name] = dll
|
dlls[dll_name] = dll
|
||||||
|
@ -277,22 +281,6 @@ class Railgun
|
||||||
def const(str)
|
def const(str)
|
||||||
return constant_manager.parse(str)
|
return constant_manager.parse(str)
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# Return an array of windows constants names matching +winconst+
|
|
||||||
#
|
|
||||||
|
|
||||||
def const_reverse_lookup(winconst,filter_regex=nil)
|
|
||||||
return constant_manager.rev_lookup(winconst,filter_regex)
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# Returns an array of windows error code names for a given windows error code matching +err_code+
|
|
||||||
#
|
|
||||||
|
|
||||||
def error_lookup (err_code,filter_regex=/^ERROR_/)
|
|
||||||
return constant_manager.rev_lookup(err_code,filter_regex)
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# The multi-call shorthand (["kernel32", "ExitProcess", [0]])
|
# The multi-call shorthand (["kernel32", "ExitProcess", [0]])
|
||||||
|
|
|
@ -120,6 +120,14 @@ class Railgun::UnitTest < Test::Unit::TestCase
|
||||||
|
|
||||||
assert(!unfrozen_dll.frozen?,
|
assert(!unfrozen_dll.frozen?,
|
||||||
"add_function should create a local unfrozen instance that get_dll can then access")
|
"add_function should create a local unfrozen instance that get_dll can then access")
|
||||||
|
|
||||||
|
railgun2 = Railgun.new(make_mock_client())
|
||||||
|
|
||||||
|
assert(!railgun2.get_dll(dll_name).functions.has_key?('__lolz'),
|
||||||
|
"functions added to one instance of railgun should not be accessible to others")
|
||||||
|
|
||||||
|
assert_not_same(!railgun2.get_dll(dll_name).functions, unfrozen_dll.functions,
|
||||||
|
"function hash should have been duplicated during unfreeze")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
require 'rex/post/meterpreter/extensions/stdapi/railgun/platform_util'
|
||||||
|
|
||||||
|
module Rex
|
||||||
|
module Post
|
||||||
|
module Meterpreter
|
||||||
|
module Extensions
|
||||||
|
module Stdapi
|
||||||
|
module Railgun
|
||||||
|
module Type
|
||||||
|
module PointerUtil
|
||||||
|
|
||||||
|
ARCH_POINTER_SIZE = {
|
||||||
|
PlatformUtil::X86_64 => 8,
|
||||||
|
PlatformUtil::X86_32 => 4
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
# Returns the pointer size for this architecture. Should accept client or platform or arch
|
||||||
|
def self.pointer_size(platform)
|
||||||
|
ARCH_POINTER_SIZE[platform]
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.pack_pointer(pointer, platform)
|
||||||
|
if pointer.nil?
|
||||||
|
return pack_pointer(0, platform)
|
||||||
|
end
|
||||||
|
|
||||||
|
case platform
|
||||||
|
when PlatformUtil::X86_64
|
||||||
|
# XXX: Only works if attacker and victim are like-endianed
|
||||||
|
[pointer].pack('Q')
|
||||||
|
when PlatformUtil::X86_32
|
||||||
|
[pointer].pack('V')
|
||||||
|
else
|
||||||
|
raise "platform symbol #{platform.to_s} not supported"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Given a packed pointer, unpack it according to architecture
|
||||||
|
def self.unpack_pointer(packed_pointer, platform)
|
||||||
|
case platform
|
||||||
|
when PlatformUtil::X86_64
|
||||||
|
# XXX: Only works if attacker and victim are like-endianed
|
||||||
|
packed_pointer.unpack('Q').first
|
||||||
|
when PlatformUtil::X86_32
|
||||||
|
packed_pointer.unpack('V').first
|
||||||
|
else
|
||||||
|
raise "platform symbol #{platform.to_s} not supported"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.null_pointer(pointer, platform)
|
||||||
|
pack_pointer(0, platform)
|
||||||
|
end
|
||||||
|
|
||||||
|
###
|
||||||
|
# Summary: Returns true if pointer will be considered a 'null' pointer
|
||||||
|
#
|
||||||
|
# If given nil, returns true
|
||||||
|
# If given 0, returns true
|
||||||
|
# If given a string, if 0 after unpacking, returns true
|
||||||
|
# false otherwise
|
||||||
|
##
|
||||||
|
def self.is_null_pointer?(pointer, platform)
|
||||||
|
if pointer.kind_of?(String)
|
||||||
|
pointer = unpack_pointer(pointer, platform)
|
||||||
|
end
|
||||||
|
|
||||||
|
return pointer.nil? || pointer == 0
|
||||||
|
end
|
||||||
|
#
|
||||||
|
# def self.is_unpacked_pointer?(pointer, platform)
|
||||||
|
# # TODO also check that the integer size is appropriate for the platform
|
||||||
|
# unless pointer.kind_of?(Fixnum) and pointer > 0 # and pointer <
|
||||||
|
# return false
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# packed_pointer = pack_pointer(pointer, platform)
|
||||||
|
# if !packed_pointer.nil? and packed_pointer.length == pointer_size(platform)
|
||||||
|
# return true
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# return false
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# Returns true if the data type is a pointer, false otherwise
|
||||||
|
def self.is_pointer_type?(type)
|
||||||
|
if type == :pointer
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if type.kind_of?(String) && type =~ /^L?P/
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
end # PointerUtil
|
||||||
|
end # Type
|
||||||
|
end # Railgun
|
||||||
|
end # Stdapi
|
||||||
|
end # Extensions
|
||||||
|
end # Meterpreter
|
||||||
|
end # Post
|
||||||
|
end # Rex
|
|
@ -0,0 +1,127 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
$:.unshift(File.join(File.dirname(__FILE__), '..', '..','..','..','..','..', '..', '..', 'lib'))
|
||||||
|
|
||||||
|
require 'rex/post/meterpreter/extensions/stdapi/railgun/type/pointer_util'
|
||||||
|
require 'rex/post/meterpreter/extensions/stdapi/railgun/platform_util'
|
||||||
|
require 'rex/post/meterpreter/extensions/stdapi/railgun/mock_magic'
|
||||||
|
require 'test/unit'
|
||||||
|
|
||||||
|
module Rex
|
||||||
|
module Post
|
||||||
|
module Meterpreter
|
||||||
|
module Extensions
|
||||||
|
module Stdapi
|
||||||
|
module Railgun
|
||||||
|
module Type
|
||||||
|
class PlatformUtil::UnitTest < Test::Unit::TestCase
|
||||||
|
|
||||||
|
include Rex::Post::Meterpreter::Extensions::Stdapi::Railgun::MockMagic
|
||||||
|
|
||||||
|
# memread value of win x86 pointer mapped to target unpack value
|
||||||
|
X86_32_POINTERS = {
|
||||||
|
"8D\x15\x00" => 1393720,
|
||||||
|
"\x1C\x84\x15\x00" => 1410076,
|
||||||
|
"\x0E\x84\x15\x00" => 1410062,
|
||||||
|
"\x02\x84\x15\x00" => 1410050,
|
||||||
|
"\xE6\x83\x15\x00" => 1410022,
|
||||||
|
"\xC4\x83\x15\x00" => 1409988,
|
||||||
|
"\x00\x00\x00\x00" => 0,
|
||||||
|
}
|
||||||
|
X86_64_POINTERS = {
|
||||||
|
"\x10^ \x00\x00\x00\x00\x00" => 2121232,
|
||||||
|
"\xCA\x9D \x00\x00\x00\x00\x00" => 2137546,
|
||||||
|
"\xC8\x9D \x00\x00\x00\x00\x00" => 2137544,
|
||||||
|
"Z\x9D \x00\x00\x00\x00\x00" => 2137434,
|
||||||
|
"X\x9D \x00\x00\x00\x00\x00" => 2137432,
|
||||||
|
"\x00\x00\x00\x00\x00\x00\x00\x00" => 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
X86_64_NULL_POINTER = "\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||||
|
X86_32_NULL_POINTER = "\x00\x00\x00\x00"
|
||||||
|
|
||||||
|
X86_64 = PlatformUtil::X86_64
|
||||||
|
X86_32 = PlatformUtil::X86_32
|
||||||
|
|
||||||
|
def test_pack_pointer
|
||||||
|
X86_64_POINTERS.invert.each_pair do |unpacked, packed|
|
||||||
|
assert_equal(packed, PointerUtil.pack_pointer(unpacked.to_i, X86_64),
|
||||||
|
"pack_pointer should pack 64-bit numberic pointers")
|
||||||
|
end
|
||||||
|
|
||||||
|
X86_32_POINTERS.invert.each_pair do |unpacked, packed|
|
||||||
|
assert_equal(packed, PointerUtil.pack_pointer(unpacked.to_i, X86_32),
|
||||||
|
"pack_pointer should pack 32-bit numberic pointers")
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal(X86_64_NULL_POINTER, PointerUtil.pack_pointer(nil, X86_64),
|
||||||
|
'pack_pointer should pack "nil" as a null pointer for x86_64')
|
||||||
|
|
||||||
|
assert_equal(X86_32_NULL_POINTER, PointerUtil.pack_pointer(nil, X86_32),
|
||||||
|
'pack_pointer should pack "nil" as a null pointer for x86_32')
|
||||||
|
|
||||||
|
assert_equal(X86_64_NULL_POINTER, PointerUtil.pack_pointer(0, X86_64),
|
||||||
|
'pack_pointer should pack numeric 0 as a null pointer for x86_64')
|
||||||
|
|
||||||
|
assert_equal(X86_32_NULL_POINTER, PointerUtil.pack_pointer(0, X86_32),
|
||||||
|
'pack_pointer should pack numeric 9 as a null pointer for x86_32')
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_unpack_pointer
|
||||||
|
X86_64_POINTERS.each_pair do |packed, unpacked|
|
||||||
|
assert_equal(unpacked, PointerUtil.unpack_pointer(packed, X86_64),
|
||||||
|
"unpack_pointer should unpack 64-bit pointers")
|
||||||
|
end
|
||||||
|
|
||||||
|
X86_32_POINTERS.each_pair do |packed, unpacked|
|
||||||
|
assert_equal(unpacked, PointerUtil.unpack_pointer(packed, X86_32),
|
||||||
|
"unpack_pointer should unpack 32-bit pointers")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_is_null_pointer
|
||||||
|
[X86_32, X86_64].each do |platform|
|
||||||
|
assert(PointerUtil.is_null_pointer?(nil, platform), 'nil should be a null pointer')
|
||||||
|
assert(PointerUtil.is_null_pointer?(0, platform), 'numeric 0 should be a null pointer')
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal(true, PointerUtil.is_null_pointer?(X86_32_NULL_POINTER, X86_32),
|
||||||
|
'is_null_pointer? should return true for packed 32-bit null pointers')
|
||||||
|
|
||||||
|
assert_equal(true, PointerUtil.is_null_pointer?(X86_64_NULL_POINTER, X86_64),
|
||||||
|
'is_null_pointer? should return true for packed 64-bit null pointers')
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_pointer_size
|
||||||
|
assert_equal(8, PointerUtil.pointer_size(X86_64),
|
||||||
|
'pointer_size should report X86_64 arch as 8 (bytes)')
|
||||||
|
|
||||||
|
assert_equal(4, PointerUtil.pointer_size(X86_32),
|
||||||
|
'pointer_size should report X86_32 arch as 4 (bytes)')
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_is_pointer_type
|
||||||
|
assert_equal(true, PointerUtil.is_pointer_type?(:pointer),
|
||||||
|
'pointer_type should return true for the symbol :pointer')
|
||||||
|
|
||||||
|
assert_equal(true, PointerUtil.is_pointer_type?('LPVOID'),
|
||||||
|
'pointer_type should return true if string begins with LP')
|
||||||
|
|
||||||
|
assert_equal(true, PointerUtil.is_pointer_type?('PDWORD'),
|
||||||
|
'pointer_type should return true if string begins with P')
|
||||||
|
|
||||||
|
assert_equal(false, PointerUtil.is_pointer_type?('LOLZ'),
|
||||||
|
'pointer_type should return false if not a pointer type')
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -33,6 +33,7 @@ module Railgun
|
||||||
# Manages our library of windows constants
|
# Manages our library of windows constants
|
||||||
#
|
#
|
||||||
class WinConstManager
|
class WinConstManager
|
||||||
|
attr_reader :consts
|
||||||
|
|
||||||
def initialize(initial_consts = {})
|
def initialize(initial_consts = {})
|
||||||
@consts = {}
|
@consts = {}
|
||||||
|
@ -40,12 +41,10 @@ class WinConstManager
|
||||||
initial_consts.each_pair do |name, value|
|
initial_consts.each_pair do |name, value|
|
||||||
add_const(name, value)
|
add_const(name, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Load utility
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_const(name, value)
|
def add_const(name, value)
|
||||||
@consts[name] = value
|
consts[name] = value
|
||||||
end
|
end
|
||||||
|
|
||||||
# parses a string constaining constants and returns an integer
|
# parses a string constaining constants and returns an integer
|
||||||
|
@ -59,41 +58,38 @@ class WinConstManager
|
||||||
return_value = 0
|
return_value = 0
|
||||||
for one_const in s.split('|')
|
for one_const in s.split('|')
|
||||||
one_const = one_const.strip()
|
one_const = one_const.strip()
|
||||||
if not @consts.has_key? one_const
|
if not consts.has_key? one_const
|
||||||
return nil # at least one "Constant" is unknown to us
|
return nil # at least one "Constant" is unknown to us
|
||||||
end
|
end
|
||||||
return_value |= @consts[one_const]
|
return_value |= consts[one_const]
|
||||||
end
|
end
|
||||||
return return_value
|
return return_value
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_parseable(s)
|
def is_parseable(s)
|
||||||
return parse(s) != nil
|
return !parse(s).nil?
|
||||||
end
|
|
||||||
|
|
||||||
# looks up a windows constant (integer or hex) and returns an array of matching winconstant names
|
|
||||||
#
|
|
||||||
# this function will NOT throw an exception but return "nil" if it can't find an error code
|
|
||||||
def rev_lookup(winconst, filter_regex=nil)
|
|
||||||
c = winconst.to_i # this is what we're gonna reverse lookup
|
|
||||||
arr = [] # results array
|
|
||||||
@consts.each_pair do |k,v|
|
|
||||||
arr << k if v == c
|
|
||||||
end
|
|
||||||
if filter_regex # this is how we're going to filter the results
|
|
||||||
# in case we get passed a string instead of a Regexp
|
|
||||||
filter_regex = Regexp.new(filter_regex) unless filter_regex.class == Regexp
|
|
||||||
# do the actual filtering
|
|
||||||
arr.select! do |item|
|
|
||||||
item if item =~ filter_regex
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return arr
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_parseable(s)
|
#
|
||||||
return parse(s) != nil
|
# Returns an array of constant names that have a value matching "winconst"
|
||||||
end
|
# and (optionally) a name that matches "filter_regex"
|
||||||
|
#
|
||||||
|
def select_const_names(winconst, filter_regex=nil)
|
||||||
|
matches = []
|
||||||
|
|
||||||
|
consts.each_pair do |name, value|
|
||||||
|
matches << name if value == winconst
|
||||||
|
end
|
||||||
|
|
||||||
|
# Filter matches by name if a filter has been provided
|
||||||
|
unless filter_regex.nil?
|
||||||
|
matches.reject! do |name|
|
||||||
|
name !~ filter_regex
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return matches
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end; end; end; end; end; end
|
end; end; end; end; end; end
|
||||||
|
|
|
@ -12,6 +12,26 @@ module Extensions
|
||||||
module Stdapi
|
module Stdapi
|
||||||
module Railgun
|
module Railgun
|
||||||
class WinConstManager::UnitTest < Test::Unit::TestCase
|
class WinConstManager::UnitTest < Test::Unit::TestCase
|
||||||
|
|
||||||
|
def test_select_const_names
|
||||||
|
const_manager = WinConstManager.new
|
||||||
|
|
||||||
|
names = %w(W WW WWW)
|
||||||
|
|
||||||
|
names.each do |name|
|
||||||
|
const_manager.add_const(name, 23)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert(const_manager.select_const_names(23).sort == names,
|
||||||
|
'select_const_names should return all names for given value')
|
||||||
|
|
||||||
|
const_manager.add_const('Skidoo!', 23)
|
||||||
|
|
||||||
|
assert(const_manager.select_const_names(23, /^\w{1,3}$/).sort == names,
|
||||||
|
'select_const_names should filter names with provided regex')
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
def test_is_parseable
|
def test_is_parseable
|
||||||
const_manager = WinConstManager.new
|
const_manager = WinConstManager.new
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue