156 lines
5.0 KiB
Ruby
156 lines
5.0 KiB
Ruby
|
|
||
|
# A quick way to produce unique random strings that follow the rules of
|
||
|
# identifiers, i.e., begin with a letter and contain only alphanumeric
|
||
|
# characters and underscore.
|
||
|
#
|
||
|
# The advantage of using this class over, say, {Rex::Text.rand_text_alpha}
|
||
|
# each time you need a new identifier is that it ensures you don't have
|
||
|
# collisions.
|
||
|
#
|
||
|
# @example
|
||
|
# vars = Rex::RandomIdentifierGenerator.new
|
||
|
# asp_code = <<-END_CODE
|
||
|
# Sub #{vars[:func]}()
|
||
|
# Dim #{vars[:fso]}
|
||
|
# Set #{vars[:fso]} = CreateObject("Scripting.FileSystemObject")
|
||
|
# ...
|
||
|
# End Sub
|
||
|
# #{vars[:func]}
|
||
|
# END_CODE
|
||
|
#
|
||
|
class Rex::RandomIdentifierGenerator
|
||
|
|
||
|
# Raised when a RandomIdentifierGenerator cannot create any more
|
||
|
# identifiers without collisions.
|
||
|
class ExhaustedSpaceError < StandardError; end
|
||
|
|
||
|
# Default options
|
||
|
DefaultOpts = {
|
||
|
# Arbitrary
|
||
|
:max_length => 12,
|
||
|
:min_length => 3,
|
||
|
# This should be pretty universal for identifier rules
|
||
|
:char_set => Rex::Text::AlphaNumeric+"_",
|
||
|
}
|
||
|
|
||
|
# @param opts [Hash] Options, see {DefaultOpts} for default values
|
||
|
# @option opts :max_length [Fixnum]
|
||
|
# @option opts :min_length [Fixnum]
|
||
|
# @option opts :char_set [String]
|
||
|
def initialize(opts={})
|
||
|
# Holds all identifiers.
|
||
|
@identifiers = {}
|
||
|
|
||
|
@opts = DefaultOpts.merge(opts)
|
||
|
if @opts[:min_length] < 1 || @opts[:max_length] < 1 || @opts[:max_length] < @opts[:min_length]
|
||
|
raise ArgumentError, "Invalid length options"
|
||
|
end
|
||
|
|
||
|
# This is really just the maximum number of shortest names. Since
|
||
|
# this will still be a pretty big number most of the time, don't
|
||
|
# bother calculating the real one, which will potentially be
|
||
|
# expensive, since we're talking about a 36-digit decimal number to
|
||
|
# represent the total possibilities for the range of 10- to
|
||
|
# 20-character identifiers.
|
||
|
#
|
||
|
# 26 because the first char is lowercase alpha, (min_length - 1) and
|
||
|
# not just min_length because it includes that first alpha char.
|
||
|
@max_permutations = 26 * (@opts[:char_set].length ** (@opts[:min_length]-1))
|
||
|
# The real number of permutations could be calculated thusly:
|
||
|
#((@opts[:min_length]-1) .. (@opts[:max_length]-1)).reduce(0) { |a, e|
|
||
|
# a + (26 * @opts[:char_set].length ** e)
|
||
|
#}
|
||
|
end
|
||
|
|
||
|
# Return a unique random identifier for +name+, generating a new one
|
||
|
# if necessary.
|
||
|
#
|
||
|
# @param name [Symbol] A descriptive, intention-revealing name for an
|
||
|
# identifier. This is what you would normally call the variable if
|
||
|
# you weren't generating it.
|
||
|
# @return [String]
|
||
|
def get(name)
|
||
|
return @identifiers[name] if @identifiers[name]
|
||
|
|
||
|
@identifiers[name] = generate
|
||
|
|
||
|
@identifiers[name]
|
||
|
end
|
||
|
alias [] get
|
||
|
|
||
|
# Add a new identifier. Its name will be checked for uniqueness among
|
||
|
# previously-generated names.
|
||
|
#
|
||
|
# @note This should be called *before* any calls to {#get} to avoid
|
||
|
# potential collisions. If you do hit a collision, this method will
|
||
|
# raise.
|
||
|
#
|
||
|
# @param name (see #get)
|
||
|
# @param value [String] The identifier that will be returned by
|
||
|
# subsequent calls to {#get} with the sane +name+.
|
||
|
# @raise RuntimeError if +value+ already exists
|
||
|
# @return [void]
|
||
|
def store(name, value)
|
||
|
|
||
|
case @identifiers.key(value)
|
||
|
when name
|
||
|
# we already have this value and it is associated with this name
|
||
|
# nothing to do here
|
||
|
when nil
|
||
|
# don't have this value yet, so go ahead and just insert
|
||
|
@identifiers[name] = value
|
||
|
else
|
||
|
# then the caller is trying to insert a duplicate
|
||
|
raise RuntimeError, "Value is not unique!"
|
||
|
end
|
||
|
|
||
|
self
|
||
|
end
|
||
|
|
||
|
# Create a random string that satisfies most languages' requirements
|
||
|
# for identifiers.
|
||
|
#
|
||
|
# Note that the first character will always be lowercase alpha.
|
||
|
#
|
||
|
# @example
|
||
|
# rig = Rex::RandomIdentifierGenerator.new
|
||
|
# const = rig.generate { |val| val.capitalize }
|
||
|
# rig.insert(:SOME_CONSTANT, const)
|
||
|
# ruby_code = <<-EOC
|
||
|
# #{rig[:SOME_CONSTANT]} = %q^generated ruby constant^
|
||
|
# def #{rig[:my_method]}; ...; end
|
||
|
# EOC
|
||
|
#
|
||
|
# @param len [Fixnum] Avoid setting this unless a specific size is
|
||
|
# necessary. Default is random within range of min .. max
|
||
|
# @return [String] A string that matches <tt>[a-z][a-zA-Z0-9_]*</tt>
|
||
|
# @yield [String] The identifier before uniqueness checks. This allows
|
||
|
# you to modify the value and still avoid collisions.
|
||
|
def generate(len=nil)
|
||
|
raise ArgumentError, "len must be positive integer" if len && len < 1
|
||
|
raise ExhaustedSpaceError if @identifiers.length >= @max_permutations
|
||
|
|
||
|
# pick a random length within the limits
|
||
|
len ||= rand(@opts[:min_length] .. (@opts[:max_length]))
|
||
|
|
||
|
ident = ""
|
||
|
|
||
|
# XXX: infinite loop if we've exhausted the space. Mitigated by the
|
||
|
# fact that you'd have to call generate at least 26*62 times (in the
|
||
|
# case of 2-character names) to hit it with the default :char_set.
|
||
|
loop do
|
||
|
ident = Rex::Text.rand_text_alpha_lower(1)
|
||
|
ident << Rex::Text.rand_base(len-1, "", @opts[:char_set])
|
||
|
if block_given?
|
||
|
ident = yield ident
|
||
|
end
|
||
|
# Try to make another one if it collides with a previously
|
||
|
# generated one.
|
||
|
break unless @identifiers.value?(ident)
|
||
|
end
|
||
|
|
||
|
ident
|
||
|
end
|
||
|
|
||
|
end
|