Skip to content

Commit c33fb1c

Browse files
committed
Added Encoding::PHP (closes #518).
1 parent 30b080a commit c33fb1c

File tree

9 files changed

+1037
-0
lines changed

9 files changed

+1037
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ research and development.
5050
* [PowerShell][docs-encoding-powershell]
5151
* [Punycode][docs-encoding-punycode]
5252
* [Quoted-printable][docs-encoding-quoted-printable]
53+
* [PHP strings][docs-encoding-php]
5354
* [Ruby strings][docs-encoding-ruby]
5455
* [Shell][docs-encoding-shell]
5556
* [SQL][docs-encoding-sql]
@@ -209,6 +210,7 @@ along with ronin-support. If not, see <https://www.gnu.org/licenses/>.
209210
[docs-encoding-powershell]: https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Encoding/PowerShell.html
210211
[docs-encoding-punycode]: https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Encoding/Punycode.html
211212
[docs-encoding-quoted-printable]: https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Encoding/QuotedPrintable.html
213+
[docs-encoding-php]: https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Encoding/PHP.html
212214
[docs-encoding-ruby]: https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Encoding/Ruby.html
213215
[docs-encoding-shell]: https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Encoding/Shell.html
214216
[docs-encoding-sql]: https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Encoding/SQL.html

lib/ronin/support/encoding.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
require 'ronin/support/encoding/sql'
3333
require 'ronin/support/encoding/quoted_printable'
3434
require 'ronin/support/encoding/smtp'
35+
require 'ronin/support/encoding/php'
3536
require 'ronin/support/encoding/ruby'
3637
require 'ronin/support/encoding/uri'
3738
require 'ronin/support/encoding/punycode'
@@ -99,6 +100,12 @@ module Support
99100
# * {String#js_string}
100101
# * {String#js_unescape}
101102
# * {String#js_unquote}
103+
# * {String#php_escape}
104+
# * {String#php_unescape}
105+
# * {String#php_encode}
106+
# * {String#php_decode}
107+
# * {String#php_string}
108+
# * {String#php_unquote}
102109
# * {String#powershell_decode}
103110
# * {String#powershell_encode}
104111
# * {String#powershell_escape}

lib/ronin/support/encoding/php.rb

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
# frozen_string_literal: true
2+
#
3+
# Copyright (c) 2006-2024 Hal Brodigan (postmodern.mod3 at gmail.com)
4+
#
5+
# ronin-support is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU Lesser General Public License as published
7+
# by the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# ronin-support is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU Lesser General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU Lesser General Public License
16+
# along with ronin-support. If not, see <https://www.gnu.org/licenses/>.
17+
#
18+
19+
require 'strscan'
20+
21+
module Ronin
22+
module Support
23+
class Encoding < ::Encoding
24+
#
25+
# Contains methods for encoding/decoding escaping/unescaping PHP strings.
26+
#
27+
# ## Core-Ext Methods
28+
#
29+
# * {String#php_escape}
30+
# * {String#php_unescape}
31+
# * {String#php_encode}
32+
# * {String#php_decode}
33+
# * {String#php_string}
34+
# * {String#php_unquote}
35+
#
36+
# @api public
37+
#
38+
# @since 1.2.0
39+
#
40+
module PHP
41+
# Special PHP bytes and their escaped Strings.
42+
ESCAPE_BYTES = {
43+
0x00 => '\0',
44+
0x09 => '\t',
45+
0x0a => '\n',
46+
0x0c => '\f',
47+
0x0d => '\r',
48+
0x1b => '\e',
49+
0x22 => '\"',
50+
0x24 => '\$',
51+
0x5c => '\\\\'
52+
}
53+
54+
#
55+
# Encodes a byte as a PHP escaped character.
56+
#
57+
# @param [Integer] byte
58+
# The byte value to encode.
59+
#
60+
# @return [String]
61+
# The escaped PHP character.
62+
#
63+
# @example
64+
# Encoding::PHP.encode_byte(0x41)
65+
# # => "\\x41"
66+
# Encoding::PHP.encode_byte(0x100)
67+
# # => "\\u100"
68+
# Encoding::PHP.encode_byte(0x10000)
69+
# # => "\\u{10000}"
70+
#
71+
def self.encode_byte(byte)
72+
if byte >= 0x00 && byte <= 0xff
73+
"\\x%.2x" % byte
74+
elsif byte >= 0x100 && byte <= 0x10ffff
75+
"\\u{%.x}" % byte
76+
else
77+
raise(RangeError,"#{byte.inspect} out of char range")
78+
end
79+
end
80+
81+
#
82+
# Escapes a byte as a PHP character.
83+
#
84+
# @param [Integer] byte
85+
# The byte value to escape.
86+
#
87+
# @return [String]
88+
# The escaped PHP character.
89+
#
90+
# @raise [RangeError]
91+
# The byte value isn't a valid ASCII or UTF byte.
92+
#
93+
def self.escape_byte(byte)
94+
if byte >= 0x00 && byte <= 0xff
95+
ESCAPE_BYTES.fetch(byte) do
96+
if byte >= 0x20 && byte <= 0x7e
97+
byte.chr
98+
else
99+
encode_byte(byte)
100+
end
101+
end
102+
else
103+
encode_byte(byte)
104+
end
105+
end
106+
107+
#
108+
# Encodes each byte of the given data as a PHP escaped character.
109+
#
110+
# @param [String] data
111+
# The given data to encode.
112+
#
113+
# @return [String]
114+
# The encoded PHP String.
115+
#
116+
# @example
117+
# Encoding::PHP.encode("hello")
118+
# # => "\\x68\\x65\\x6c\\x6c\\x6f"
119+
#
120+
def self.encode(data)
121+
encoded = String.new
122+
123+
if data.valid_encoding?
124+
data.each_codepoint do |codepoint|
125+
encoded << encode_byte(codepoint)
126+
end
127+
else
128+
data.each_byte do |byte|
129+
encoded << encode_byte(byte)
130+
end
131+
end
132+
133+
return encoded
134+
end
135+
136+
#
137+
# Decodes the PHP encoded data.
138+
#
139+
# @param [String] data
140+
# The given PHP data to decode.
141+
#
142+
# @return [String]
143+
# The decoded data.
144+
#
145+
# @see unescape
146+
#
147+
def self.decode(data)
148+
unescape(data)
149+
end
150+
151+
#
152+
# Escapes the PHP string.
153+
#
154+
# @param [String] data
155+
# The data to escape.
156+
#
157+
# @return [String]
158+
# The PHP escaped String.
159+
#
160+
def self.escape(data)
161+
escaped = String.new
162+
163+
if data.valid_encoding?
164+
data.each_codepoint do |codepoint|
165+
escaped << escape_byte(codepoint)
166+
end
167+
else
168+
data.each_byte do |byte|
169+
escaped << escape_byte(byte)
170+
end
171+
end
172+
173+
return escaped
174+
end
175+
176+
# PHP characters that must be backslash escaped.
177+
#
178+
# @see https://www.php.net/manual/en/language.types.string.php
179+
BACKSLASH_CHARS = {
180+
'\0' => "\0",
181+
'\t' => "\t",
182+
'\n' => "\n",
183+
'\v' => "\v",
184+
'\f' => "\f",
185+
'\r' => "\r",
186+
'\"' => '"',
187+
'\$' => "$",
188+
'\\\\' => "\\"
189+
}
190+
191+
#
192+
# Unescapes the escaped String.
193+
#
194+
# @param [String] data
195+
# The given PHP escaped data.
196+
#
197+
# @return [String]
198+
# The unescaped version of the hex escaped String.
199+
#
200+
# @example
201+
# Encoding::PHP.unescape("\\x68\\x65\\x6c\\x6c\\x6f")
202+
# # => "hello"
203+
#
204+
def self.unescape(data)
205+
unescaped = String.new(encoding: Encoding::UTF_8)
206+
scanner = StringScanner.new(data)
207+
208+
until scanner.eos?
209+
# see https://www.php.net/manual/en/language.types.string.php
210+
unescaped << if (hex_escape = scanner.scan(/\\x[0-9a-fA-F]{1,2}/))
211+
hex_escape[2..].to_i(16).chr
212+
elsif (unicode_escape = scanner.scan(/\\u\{[0-9a-fA-F]+\}/))
213+
unicode_escape[3..-2].to_i(16).chr(Encoding::UTF_8)
214+
elsif (octal_escape = scanner.scan(/\\[0-7]{1,3}/))
215+
octal_escape[1..].to_i(8).chr
216+
217+
elsif (backslash_char = scanner.scan(/\\./))
218+
BACKSLASH_CHARS.fetch(backslash_char,backslash_char)
219+
else
220+
scanner.getch
221+
end
222+
end
223+
224+
return unescaped
225+
end
226+
227+
#
228+
# Escapes and quotes the given data as a PHP string.
229+
#
230+
# @param [String] data
231+
# The given data to escape and quote.
232+
#
233+
# @return [String]
234+
# The quoted PHP string.
235+
#
236+
# @example
237+
# Encoding::PHP.quote("hello\nworld\n")
238+
# # => "\"hello\\nworld\\n\""
239+
#
240+
def self.quote(data)
241+
"\"#{escape(data)}\""
242+
end
243+
244+
#
245+
# Removes the quotes an unescapes a quoted string.
246+
#
247+
# @param [String] data
248+
# The given PHP string.
249+
#
250+
# @return [String]
251+
# The un-quoted String if the String begins and ends with quotes, or
252+
# the same String if it is not quoted.
253+
#
254+
# @example
255+
# Encoding::PHP.unquote("\"hello\\nworld\"")
256+
# # => "hello\nworld"
257+
# Encoding::PHP.unquote("'hello\\'world'")
258+
# # => "hello'world"
259+
#
260+
def self.unquote(data)
261+
if (data[0] == '"' && data[-1] == '"')
262+
unescape(data[1..-2])
263+
elsif (data[0] == "'" && data[-1] == "'")
264+
data[1..-2].gsub(/\\(['\\])/,'\1')
265+
else
266+
data
267+
end
268+
end
269+
end
270+
end
271+
end
272+
end
273+
274+
require 'ronin/support/encoding/php/core_ext'
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# frozen_string_literal: true
2+
#
3+
# Copyright (c) 2006-2024 Hal Brodigan (postmodern.mod3 at gmail.com)
4+
#
5+
# ronin-support is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU Lesser General Public License as published
7+
# by the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# ronin-support is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU Lesser General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU Lesser General Public License
16+
# along with ronin-support. If not, see <https://www.gnu.org/licenses/>.
17+
#
18+
19+
require 'ronin/support/encoding/php/core_ext/integer'
20+
require 'ronin/support/encoding/php/core_ext/string'

0 commit comments

Comments
 (0)