Class: Rex::RandomIdentifier::Generator

Inherits:
Object
  • Object
show all
Defined in:
lib/rex/random_identifier/generator.rb

Overview

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, Text.rand_text_alpha each time you need a new identifier is that it ensures you don’t have collisions.

Examples:

vars = Rex::RandomIdentifier::Generator.new
asp_code = "  Sub \#{vars[:func]}()\n    Dim \#{vars[:fso]}\n    Set \#{vars[:fso]} = CreateObject(\"Scripting.FileSystemObject\")\n    ...\n  End Sub\n  \#{vars[:func]}\n"

Defined Under Namespace

Classes: ExhaustedSpaceError

Constant Summary collapse

DefaultOpts =

Default options

{
  # Arbitrary
  :max_length => 12,
  :min_length => 3,
  # This should be pretty universal for identifier rules
  :char_set => Rex::Text::AlphaNumeric+"_",
  :first_char_set => Rex::Text::LowerAlpha,
  :forbidden => [].freeze
}
JavaOpts =
DefaultOpts.merge(
  forbidden: (
    DefaultOpts[:forbidden] +
    %w[
      abstract assert boolean break byte case catch char class const
      continue default do double else enum extends false final finally
      float for goto if implements import instanceof int interface long
      native new null package private protected public return short
      static strictfp super switch synchronized this throw throws
      transient true try void volatile while _
    ]
  ).uniq.freeze
)
JSPOpts =
JavaOpts.merge(
  forbidden: (
    JavaOpts[:forbidden] +
    # Reserved Words for Implicit Objects
    # https://docs.oracle.com/cd/E13222_01/wls/docs90/webapp/reference.html#66991
    %w[
      application config out page pageContext request response session var
    ]
  ).uniq.freeze
)
JavaScriptOpts =
DefaultOpts.merge(
  forbidden: (
      # https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_words
      # https://developer.mozilla.org/en-US/docs/Web/API/Window Instance methods
      %w[
        const continue debugger default delete do else export extends false finally for function if import in
        instanceof new null return super switch this throw true try typeof var void while with let static yield
        await arguments as async eval from get of set enum implements interface package private protected public
        abstract boolean byte char double final float goto int long native short synchronized throws transient volatile
        atob alert blur btoa cancelAnimationFrame cancelIdleCallback clearInterval clearTimeout close confirm
        createImageBitmap dump fetch find focus getComputedStyle getDefaultComputedStyle getScreenDetails getSelection
        matchMedia moveBy moveTo open postMessage print prompt queryLocalFonts queueMicrotask reportError
        requestAnimationFrame requestIdleCallback resizeBy resizeTo scroll scrollBy scrollByLines scrollByPages
        scrollTo setInterval setTimeout showDirectoryPicker showOpenFilePicker showSaveFilePicker sizeToContent
        stop structuredClone updateCommands
    ]
  ).uniq.freeze
)
Opts =
{
  default: DefaultOpts,
  java: JavaOpts,
  jsp: JSPOpts,
  javascript: JavaScriptOpts
}

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Generator

Returns a new instance of Generator.

Parameters:

  • opts (Hash) (defaults to: {})

    Options, see DefaultOpts for default values

Options Hash (opts):

  • :language (Symbol)

    See the Opts keys for supported languages

  • :max_length (Fixnum)
  • :min_length (Fixnum)
  • :char_set (String)
  • :forbidden (Array)


97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/rex/random_identifier/generator.rb', line 97

def initialize(opts={})
  # Holds all identifiers.
  @value_by_name = {}
  # Inverse of value_by_name so we can ensure uniqueness without
  # having to search through the whole list of values
  @name_by_value = {}

  language = opts[:language] || :default
  unless Opts.has_key?(language)
    raise ArgumentError, "Language option #{language} is not supported. Expected one of #{Opts.keys}"
  end
  @opts = Opts[language]
  @opts = @opts.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. This
  # will still be a pretty big number most of the time, so 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

Instance Method Details

#forbid_id?(ident = nil) ⇒ Boolean

Check if an identifier is forbidden

Parameters:

  • str (String)

    String for which to check permissions

Returns:

  • (Boolean)

    Is identifier forbidden?



243
244
245
# File 'lib/rex/random_identifier/generator.rb', line 243

def forbid_id?(ident = nil)
  ident.nil? or @opts[:forbidden].any? {|f| f.match(/^#{ident}$/i) }
end

#generate(len = nil) {|String| ... } ⇒ String

Note:

Calling this method with a block that returns only values that this generator already contains will result in an infinite loop.

Create a random string that satisfies most languages’ requirements for identifiers. In particular, with a default configuration, the first character will always be lowercase alpha (unless modified by a block), and the whole thing will contain only a-zA-Z0-9_ characters.

If called with a block, the block will be given the identifier before uniqueness checks. The block’s return value will be the new identifier. Note that the block may be called multiple times if it returns a non-unique value.

Examples:

rig = Rex::RandomIdentifier::Generator.new
const = rig.generate { |val| val.capitalize }
rig.insert(:SOME_CONSTANT, const)
ruby_code = "  \#{rig[:SOME_CONSTANT]} = %q^generated ruby constant^\n  def \#{rig[:my_method]}; ...; end\n"

Parameters:

  • len (Fixnum) (defaults to: nil)

    Avoid setting this unless a specific size is necessary. Default is random within range of min .. max

Yields:

  • (String)

    The identifier before uniqueness checks. This allows you to modify the value and still avoid collisions.

Returns:

  • (String)

    A string that matches [a-z][a-zA-Z0-9_]*

Raises:

  • (ArgumentError)


212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/rex/random_identifier/generator.rb', line 212

def generate(len = nil)
  raise ArgumentError, "len must be positive integer" if len && len < 1
  raise ExhaustedSpaceError if @value_by_name.length >= @max_permutations

  # pick a random length within the limits
  len ||= rand(@opts[:min_length] .. (@opts[:max_length]))

  ident = ""

  # XXX: Infinite loop if block returns only values we've already
  # generated.
  loop do
    ident  = Rex::Text.rand_base(1, "", @opts[:first_char_set])
    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 @name_by_value.key?(ident) or forbid_id?(ident)
  end

  ident
end

#get(name, len = nil) ⇒ String Also known as: [], init_var

Return a unique random identifier for name, generating a new one if necessary.

Parameters:

  • 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.

Returns:

  • (String)


144
145
146
147
148
149
150
151
# File 'lib/rex/random_identifier/generator.rb', line 144

def get(name, len = nil)
  return @value_by_name[name] if @value_by_name[name]

  @value_by_name[name] = generate(len)
  @name_by_value[@value_by_name[name]] = name

  @value_by_name[name]
end

#store(name, value) ⇒ void

Note:

This should be called before any calls to #get to avoid potential collisions. If you do hit a collision, this method will raise.

This method returns an undefined value.

Add a new identifier. Its name will be checked for uniqueness among previously-generated names.

Parameters:

  • value (String)

    The identifier that will be returned by subsequent calls to #get with the sane name.

  • 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.

Raises:

  • RuntimeError if value already exists



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/rex/random_identifier/generator.rb', line 167

def store(name, value)

  case @name_by_value[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
    @value_by_name[name] = value
    @name_by_value[value] = name
  else
    # then the caller is trying to insert a duplicate
    raise RuntimeError, "Value is not unique!"
  end

  self
end

#to_hHash

Returns the @value_by_name hash

Returns:

  • (Hash)


133
134
135
# File 'lib/rex/random_identifier/generator.rb', line 133

def to_h
  return @value_by_name
end