1
mirror of https://github.com/rapid7/metasploit-framework synced 2024-11-12 11:52:01 +01:00
metasploit-framework/lib/rabal/tree.rb
sinn3r d45cdd61aa Resolve #4507 - respond_to? + send = evil
Since Ruby 2.1, the respond_to? method is more strict because it does
not check protected methods. So when you use send(), clearly you're
ignoring this type of access control. The patch is meant to preserve
this behavior to avoid potential breakage.

Resolve #4507
2015-01-02 13:29:17 -06:00

245 lines
5.5 KiB
Ruby

#
# Rabal Tree,rb
# A basic Tree structure
#
class Tree
include Enumerable
# The name of this Tree
attr_accessor :name
# The parent of this node. If this is nil then this Tree is a
# root.
attr_accessor :parent
# The children of this node. If this Hash is empty, then this
# Tree is a leaf.
attr_accessor :children
# An abstract data point that can be utilized by child classes
# for whatever they like. If this is non-nil and responds to
# method calls it will be searched as part of the
# 'method_missing' protocol.
attr_accessor :parameters
#
# Create a new Tree with the given object.to_s as its +name+.
#
def initialize(name)
@name = name.to_s
@parent = nil
@children = {}
@parameters = nil
end
#
# Return true if this Tree has no children.
#
def is_leaf?
@children.empty?
end
#
# Return true if this Tree has no parent.
#
def is_root?
@parent.nil?
end
#
# Return the root node of the tree
#
def root
return self if is_root?
return @parent.root
end
#
# Return the distance from the root
#
def depth
return 0 if is_root?
return (1 + @parent.depth)
end
#
# Attach the given tree at the indicated path. The path given
# is always assumed to be from the root of the Tree being
# attached to.
#
# The path is given as a string separated by '/'. Each portion
# of the string is matched against the name of the particular
# tree.
#
# Given :
#
# a --- b --- c
# \
# d - e --- f
# \
# g - h
#
# * the path +a/b/c+ will match the path to Tree +c+
# * the path +d/e/g+ will _not_ match anything as the path must start at +a+ here
# * the path +a/d/e+ will _not_ match anytthin as +e+ is not a child of +d+
# * the path +a/d/e/g+ will match node +g+
#
# Leading and trailing '/' on the path are not necessary and removed.
#
def add_at_path(path,subtree)
parent_tree = tree_at_path(path)
parent_tree << subtree
return self
end
#
# Return the Tree that resides at the given path
#
def tree_at_path(path_str)
path_str = path_str.chomp("/").reverse.chomp("/").reverse
path = path_str.split("/")
# strip of the redundant first match if it is the same as
# the current node
find_subtree(path)
end
#
# Add the given object to the Tree as a child of this node. If
# the given object is not a Tree then wrap it with a Tree.
#
def <<(subtree)
# this should not generally be the case, but wrap things
# up to be nice.
if not subtree.kind_of?(Tree) then
subtree = Tree.new(subtree)
end
subtree.parent = self
# FIXME: techinically this should no longer be called 'post_add'
# but maybe 'add_hook'
subtree.post_add
# Don't overwrite any existing children with the same name,
# just put the new subtree's children into the children of
# the already existing subtree.
if children.has_key?(subtree.name) then
subtree.children.each_pair do |subtree_child_name,subtree_child_tree|
children[subtree.name] << subtree_child_tree
end
else
children[subtree.name] = subtree
end
return self
end
alias :add :<<
#
# Allow for Enumerable to be included. This just wraps walk.
#
def each
self.walk(self,lambda { |tree| yield tree })
end
#
# Count how many items are in the tree
#
def size
inject(0) { |count,n| count + 1 }
end
#
# Walk the tree in a depth first manner, visiting the Tree
# first, then its children
#
def walk(tree,method)
method.call(tree)
tree.children.each_pair do |name,child|
walk(child,method)
end
#for depth first
#method.call(tree)
end
#
# Allow for a method call to cascade up the tree looking for a
# Tree that responds to the call.
#
def method_missing(method_id,*params,&block)
if not parameters.nil? and parameters.respond_to?(method_id, true) then
return parameters.send(method_id, *params, &block)
elsif not is_root? then
@parent.send method_id, *params, &block
else
raise NoMethodError, "undefined method `#{method_id}' for #{name}:Tree"
end
end
#
# allow for a hook so newly added Tree objects may do custom
# processing after being added to the tree, but before the tree
# is walked or processed
#
def post_add
end
#
# find the current path of the tree from root to here, return it
# as a '/' separates string.
#
def current_path
#
# WMAP: removed the asterixs and modified the first return
# to just return a '/'
#
return "/" if is_root?
return ([name,parent.current_path]).flatten.reverse.join("/")
end
#
# Given the initial path to match as an Array find the Tree that
# matches that path.
#
def find_subtree(path)
if path.empty? then
return self
else
possible_child = path.shift
if children.has_key?(possible_child) then
children[possible_child].find_subtree(path)
else
raise PathNotFoundError, "`#{possible_child}' does not match anything at the current path in the Tree (#{current_path})"
end
end
end
#
# Return true of false if the given subtree exists or not
#
def has_subtree?(path)
begin
find_subtree(path)
return true
rescue PathNotFoundError
return false
end
end
#
# Allow for numerable to be included. This just wraps walk
#
def each
self.walk(self,lambda { |tree| yield tree })
end
def each_datum
self.walk(self,lambda { |tree| yield tree.data})
end
end