Skip to content

Commit cc650cc

Browse files
committed
Added Encoding::Java (closes #521).
1 parent 8a39af1 commit cc650cc

File tree

10 files changed

+1145
-0
lines changed

10 files changed

+1145
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ research and development.
4646
* [Hex][docs-encoding-hex]
4747
* [HTML][docs-encoding-html]
4848
* [HTTP][docs-encoding-http]
49+
* [Java][docs-encoding-java]
4950
* [JavaScript][docs-encoding-js]
5051
* [Node.js][docs-encoding-node-js]
5152
* [PowerShell][docs-encoding-powershell]
@@ -209,6 +210,7 @@ along with ronin-support. If not, see <https://www.gnu.org/licenses/>.
209210
[docs-encoding-hex]: https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Encoding/Hex.html
210211
[docs-encoding-html]: https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Encoding/HTML.html
211212
[docs-encoding-http]: https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Encoding/HTTP.html
213+
[docs-encoding-java]: https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Encoding/Java.html
212214
[docs-encoding-js]: https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Encoding/JS.html
213215
[docs-encoding-node-js]: https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Encoding/NodeJS.html
214216
[docs-encoding-powershell]: https://ronin-rb.dev/docs/ronin-support/Ronin/Support/Encoding/PowerShell.html

lib/ronin/support/encoding.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
require 'ronin/support/encoding/http'
2929
require 'ronin/support/encoding/xml'
3030
require 'ronin/support/encoding/html'
31+
require 'ronin/support/encoding/java'
3132
require 'ronin/support/encoding/js'
3233
require 'ronin/support/encoding/node_js'
3334
require 'ronin/support/encoding/sql'
@@ -97,6 +98,13 @@ module Support
9798
# * {String#http_encode}
9899
# * {String#http_escape}
99100
# * {String#http_unescape}
101+
# * {Integer#java_escape}
102+
# * {Integer#java_encode}
103+
# * {String#java_escape}
104+
# * {String#java_unescape}
105+
# * {String#java_encode}
106+
# * {String#java_string}
107+
# * {String#java_unquote}
100108
# * {String#js_decode}
101109
# * {String#js_encode}
102110
# * {String#js_escape}

lib/ronin/support/encoding/core_ext.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
require 'ronin/support/encoding/powershell/core_ext'
2626
require 'ronin/support/encoding/html/core_ext'
2727
require 'ronin/support/encoding/http/core_ext'
28+
require 'ronin/support/encoding/java/core_ext'
2829
require 'ronin/support/encoding/js/core_ext'
2930
require 'ronin/support/encoding/node_js/core_ext'
3031
require 'ronin/support/encoding/sql/core_ext'

lib/ronin/support/encoding/java.rb

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
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 Java data.
26+
#
27+
# ## Core-Ext Methods
28+
#
29+
# * {Integer#java_escape}
30+
# * {Integer#java_encode}
31+
# * {String#java_escape}
32+
# * {String#java_unescape}
33+
# * {String#java_encode}
34+
# * {String#java_string}
35+
# * {String#java_unquote}
36+
#
37+
# @see https://docs.oracle.com/javase/tutorial/java/data/characters.html
38+
#
39+
# @api public
40+
#
41+
# @since 1.2.0
42+
#
43+
module Java
44+
#
45+
# Encodes a byte as a Java escaped character.
46+
#
47+
# @param [Integer] byte
48+
# The byte value to encode.
49+
#
50+
# @return [String]
51+
# The escaped Java character.
52+
#
53+
# @example
54+
# Encoding::Java.encode_byte(0x41)
55+
# # => "\\u0041"
56+
# Encoding::Java.encode_byte(0x221e)
57+
# # => "\\u221e"
58+
#
59+
# @see https://docs.oracle.com/javase/tutorial/java/data/characters.html
60+
#
61+
def self.encode_byte(byte)
62+
if byte >= 0x00 && byte <= 0xffff
63+
"\\u%.4X" % byte
64+
else
65+
raise(RangeError,"#{byte.inspect} out of char range")
66+
end
67+
end
68+
69+
# Special Java bytes and their escaped characters.
70+
#
71+
# @see https://docs.oracle.com/javase/tutorial/java/data/characters.html
72+
ESCAPE_BYTES = {
73+
0x00 => '\0', # prefer \0 over \u0000
74+
0x08 => '\b',
75+
0x09 => '\t',
76+
0x0a => '\n',
77+
0x0b => '\v',
78+
0x0c => '\f',
79+
0x0d => '\r',
80+
0x22 => '\"',
81+
0x27 => "\\'",
82+
0x5c => '\\\\'
83+
}
84+
85+
#
86+
# Escapes a byte as a Java character.
87+
#
88+
# @param [Integer] byte
89+
# The byte value to escape.
90+
#
91+
# @return [String]
92+
# The escaped Java character.
93+
#
94+
# @raise [RangeError]
95+
# The integer value is negative.
96+
#
97+
# @example
98+
# Encoding::Java.escape_byte(0x41)
99+
# # => "A"
100+
# Encoding::Java.escape_byte(0x22)
101+
# # => "\\\""
102+
# Encoding::Java.escape_byte(0x7f)
103+
# # => "\\u007f"
104+
#
105+
# @example Escaping unicode characters:
106+
# Encoding::Java.escape_byte(0xffff)
107+
# # => "\\uffff"
108+
#
109+
# @see https://docs.oracle.com/javase/tutorial/java/data/characters.html
110+
#
111+
def self.escape_byte(byte)
112+
if byte >= 0x00 && byte <= 0xff
113+
ESCAPE_BYTES.fetch(byte) do
114+
if byte >= 0x20 && byte <= 0x7e
115+
byte.chr
116+
else
117+
encode_byte(byte)
118+
end
119+
end
120+
else
121+
encode_byte(byte)
122+
end
123+
end
124+
125+
#
126+
# Encodes each character of the given data as Java escaped characters.
127+
#
128+
# @param [String] data
129+
# The given data to encode.
130+
#
131+
# @return [String]
132+
# The Java encoded String.
133+
#
134+
# @example
135+
# Encoding::Java.encode("hello")
136+
# # => "\\u0068\\u0065\\u006c\\u006c\\u006f"
137+
#
138+
# @see https://docs.oracle.com/javase/tutorial/java/data/characters.html
139+
#
140+
def self.encode(data)
141+
encoded = String.new
142+
143+
if data.valid_encoding?
144+
data.each_codepoint do |codepoint|
145+
encoded << encode_byte(codepoint)
146+
end
147+
else
148+
data.each_byte do |byte|
149+
encoded << encode_byte(byte)
150+
end
151+
end
152+
153+
return encoded
154+
end
155+
156+
#
157+
# Decodes the Java encoded data.
158+
#
159+
# @param [String] data
160+
# The given Java data to decode.
161+
#
162+
# @return [String]
163+
# The decoded data.
164+
#
165+
# @see unescape
166+
# @see https://docs.oracle.com/javase/tutorial/java/data/characters.html
167+
#
168+
def self.decode(data)
169+
unescape(data)
170+
end
171+
172+
#
173+
# Escapes the Java encoded data.
174+
#
175+
# @param [String] data
176+
# The data to Java escape.
177+
#
178+
# @return [String]
179+
# The Java escaped String.
180+
#
181+
# @see https://docs.oracle.com/javase/tutorial/java/data/characters.html
182+
#
183+
def self.escape(data)
184+
escaped = String.new
185+
186+
if data.valid_encoding?
187+
data.each_codepoint do |codepoint|
188+
escaped << escape_byte(codepoint)
189+
end
190+
else
191+
data.each_byte do |byte|
192+
escaped << escape_byte(byte)
193+
end
194+
end
195+
196+
return escaped
197+
end
198+
199+
# Java characters that must be back-slashed.
200+
#
201+
# @see https://docs.oracle.com/javase/tutorial/java/data/characters.html
202+
BACKSLASHED_CHARS = {
203+
'\\b' => "\b",
204+
'\\t' => "\t",
205+
'\\n' => "\n",
206+
'\\f' => "\f",
207+
'\\r' => "\r",
208+
"\\\"" => '"',
209+
"\\'" => "'",
210+
"\\\\" => "\\"
211+
}
212+
213+
#
214+
# Unescapes the given Java escaped data.
215+
#
216+
# @param [String] data
217+
# The given Java escaped data.
218+
#
219+
# @return [String]
220+
# The unescaped Java String.
221+
#
222+
# @raise [ArgumentError]
223+
# An invalid Java backslach escape sequence was encountered while
224+
# parsing the String.
225+
#
226+
# @example
227+
# Encoding::Java.unescape("\\u0068\\u0065\\u006c\\u006c\\u006f\\u0020\\u0077\\u006f\\u0072\\u006c\\u0064")
228+
# # => "hello world"
229+
#
230+
# @see https://docs.oracle.com/javase/tutorial/java/data/characters.html
231+
#
232+
def self.unescape(data)
233+
unescaped = String.new(encoding: Encoding::UTF_8)
234+
scanner = StringScanner.new(data)
235+
236+
until scanner.eos?
237+
unescaped << if (unicode_escape = scanner.scan(/\\u[0-9a-fA-F]{4}/)) # \uXXXX
238+
unicode_escape[2..].to_i(16).chr(Encoding::UTF_8)
239+
elsif (octal_escape = scanner.scan(/\\[0-7]{1,3}/)) # \N, \NN, or \NNN
240+
octal_escape[1..].to_i(8).chr
241+
elsif (backslash_escape = scanner.scan(/\\./)) # \[A-Za-z]
242+
BACKSLASHED_CHARS.fetch(backslash_escape) do
243+
raise(ArgumentError,"invalid Java backslash escape sequence: #{backslash_escape.inspect}")
244+
end
245+
else
246+
scanner.getch
247+
end
248+
end
249+
250+
return unescaped
251+
end
252+
253+
#
254+
# Escapes and quotes the given data as a Java String.
255+
#
256+
# @param [String] data
257+
# The given data to escape and quote.
258+
#
259+
# @return [String]
260+
# The quoted Java String.
261+
#
262+
# @example
263+
# Encoding::Java.quote("hello\nworld\n")
264+
# # => "\"hello\\nworld\\n\""
265+
#
266+
# @see https://docs.oracle.com/javase/tutorial/java/data/characters.html
267+
#
268+
def self.quote(data)
269+
"\"#{escape(data)}\""
270+
end
271+
272+
#
273+
# Unquotes and unescapes the given Java String.
274+
#
275+
# @param [String] data
276+
# The given Java String.
277+
#
278+
# @return [String]
279+
# The un-quoted String if the String begins and ends with quotes, or
280+
# the same String if it is not quoted.
281+
#
282+
# @example
283+
# Encoding::Java.unquote("\"hello\\nworld\"")
284+
# # => "hello\nworld"
285+
#
286+
# @see https://docs.oracle.com/javase/tutorial/java/data/characters.html
287+
#
288+
def self.unquote(data)
289+
if (data.start_with?('"') && data.end_with?('"'))
290+
unescape(data[1..-2])
291+
else
292+
data
293+
end
294+
end
295+
end
296+
end
297+
end
298+
end
299+
300+
require 'ronin/support/encoding/java/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/java/core_ext/integer'
20+
require 'ronin/support/encoding/java/core_ext/string'

0 commit comments

Comments
 (0)