module Ronin::Support::Encoding::Shell

Contains methods for encoding/decoding escaping/unescaping Shell data.

## Core-Ext Methods

@api public

Constants

BACKSLASHED_CHARS

Shell characters that must be back-slashed.

ESCAPE_BYTES

Special shell bytes and their escaped Strings.

Public Class Methods

decode(data) click to toggle source

Alias for {unescape}.

@param [String] data

The data to shell unescape.

@return [String]

The shell unescaped string.

@see unescape

# File lib/ronin/support/encoding/shell.rb, line 243
def self.decode(data)
  unescape(data)
end
encode(data) click to toggle source

Shell encodes every character in the given data.

@param [String] data

The data to shell encode.

@return [String]

The shell encoded String.

@example

Encoding::Shell.encode("hello world")
# => "\\x68\\x65\\x6c\\x6c\\x6f\\x0a\\x77\\x6f\\x72\\x6c\\x64"
# File lib/ronin/support/encoding/shell.rb, line 216
def self.encode(data)
  encoded = String.new

  if data.valid_encoding?
    data.each_codepoint do |codepoint|
      encoded << encode_byte(codepoint)
    end
  else
    data.each_byte do |byte|
      encoded << encode_byte(byte)
    end
  end

  return encoded
end
encode_byte(byte) click to toggle source

Encodes the byte as a shell character.

@param [Integer] byte

The byte value to encode.

@return [String]

The encoded shell character.

@raise [RangeError]

The byte value is negative.

@example

Encoding::Shell.encode_byte(0x41)
# => "\\x41"
Encoding::Shell.encode_byte(0x0a)
# => "\\n"

@example Encoding unicode characters:

Encoding::Shell.encode_byte(1001)
# => "\\u1001"
# File lib/ronin/support/encoding/shell.rb, line 79
def self.encode_byte(byte)
  if byte >= 0x00 && byte <= 0xff
    "\\x%.2x" % byte
  elsif byte > 0xff
    "\\u%x" % byte
  else
    raise(RangeError,"#{byte.inspect} out of char range")
  end
end
escape(data) click to toggle source

Shell escapes any special characters in the given data.

@param [String] data

The data to shell escape.

@return [String]

The shell escaped string.

@example

Encoding::Shell.escape("hello\nworld")
# => "hello\\nworld"
# File lib/ronin/support/encoding/shell.rb, line 155
def self.escape(data)
  escaped = String.new

  if data.valid_encoding?
    data.each_codepoint do |codepoint|
      escaped << escape_byte(codepoint)
    end
  else
    data.each_byte do |byte|
      escaped << escape_byte(byte)
    end
  end

  return escaped
end
escape_byte(byte) click to toggle source

Escapes the byte as a shell character.

@param [Integer] byte

The byte value to escape.

@return [String]

The escaped shell character.

@raise [RangeError]

The integer value is negative.

@example

Encoding::Shell.escape(0x41)
# => "A"
Encoding::Shell.escape(0x08)
# => "\b"
Encoding::Shell.escape(0xff)
# => "\xff"

@example Escaping unicode characters:

Encoding::Shell.escape(1001)
# => "\\u1001"
# File lib/ronin/support/encoding/shell.rb, line 113
def self.escape_byte(byte)
  if byte >= 0x00 && byte <= 0xff
    ESCAPE_BYTES.fetch(byte) do
      if byte >= 0x20 && byte <= 0x7e
        byte.chr
      else
        encode_byte(byte)
      end
    end
  else
    encode_byte(byte)
  end
end
quote(data) click to toggle source

Converts the given data into a double-quoted shell escaped String.

@param [String] data

The given data to shell escape and quote.

@return [String]

The quoted and escaped shell string.

@example

Encoding::Shell.quote("hello world")
# => "\"hello world\""
Encoding::Shell.quote("hello\nworld")
# => "$'hello\\nworld'"
# File lib/ronin/support/encoding/shell.rb, line 262
def self.quote(data)
  if data =~ /[^[:print:]]/
    "$'#{escape(data)}'"
  else
    "\"#{escape(data)}\""
  end
end
unescape(data) click to toggle source

Shell unescapes the given data.

@param [String] data

The data to shell unescape.

@return [String]

The shell unescaped string.

@example

"hello\\nworld".shell_unescape
# => "hello\nworld"
# File lib/ronin/support/encoding/shell.rb, line 184
def self.unescape(data)
  unescaped = String.new
  scanner   = StringScanner.new(data)

  until scanner.eos?
    unescaped << if (backslash_char = scanner.scan(/\\[0abetnvfr\'\"]/)) # \n
                   BACKSLASHED_CHARS[backslash_char[1..]]
                 elsif (hex_char     = scanner.scan(/\\x[0-9a-fA-F]+/)) # \XX
                   hex_char[2..].to_i(16).chr
                 elsif (unicode_char = scanner.scan(/\\u[0-9a-fA-F]+/)) # \uXXXX
                   unicode_char[2..].to_i(16).chr(Encoding::UTF_8)
                 else
                   scanner.getch
                 end
  end

  return unescaped
end
unquote(data) click to toggle source

Removes the quotes an unescapes a shell string.

@param [String] data

The quoted and escaped shell string to unquote and unescape.

@return [String]

The un-quoted String if the String begins and ends with quotes, or
the same String if it is not quoted.

@example

Encoding::Shell.unquote("\"hello \\\"world\\\"\"")
# => "hello \"world\""
Encoding::Shell.unquote("'hello\\'world'")
# => "hello'world"
Encoding::Shell.unquote("$'hello\\nworld'")
# => "hello\nworld"
# File lib/ronin/support/encoding/shell.rb, line 288
def self.unquote(data)
  if (data[0,2] == "$'" && data[-1] == "'")
    unescape(data[2..-2])
  elsif (data[0] == '"' && data[-1] == '"')
    data[1..-2].gsub("\\\"",'"')
  elsif (data[0] == "'" && data[-1] == "'")
    data[1..-2].gsub("\\'","'")
  else
    data
  end
end