javasand 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +20 -0
- data/README +2 -0
- data/lib/sand_table.jar +0 -0
- data/lib/sandbox.rb +90 -0
- data/lib/sandbox/irb.rb +95 -0
- data/lib/sandbox/prelude.rb +21 -0
- data/lib/sandbox/server.rb +140 -0
- metadata +52 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2006 Ola Bini <ola@ologix.com>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
data/lib/sand_table.jar
ADDED
Binary file
|
data/lib/sandbox.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'sand_table'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module Sandbox
|
5
|
+
|
6
|
+
BUILD = "#{VERSION}.#{REV_ID[6..-3]}" #:nodoc:
|
7
|
+
PRELUDE = File.expand_path("../sandbox/prelude.rb", __FILE__) #:nodoc:
|
8
|
+
|
9
|
+
#
|
10
|
+
# Stands in for an exception raised within the sandbox during evaluation.
|
11
|
+
# (See Sandbox#eval.)
|
12
|
+
#
|
13
|
+
class Exception
|
14
|
+
end
|
15
|
+
|
16
|
+
#
|
17
|
+
# Raised when the duration of a sandbox evaluation exceeds a specified
|
18
|
+
# timeout. (See Sandbox#eval.)
|
19
|
+
#
|
20
|
+
class TimeoutError < Exception
|
21
|
+
end
|
22
|
+
|
23
|
+
class Full
|
24
|
+
private :_eval
|
25
|
+
#
|
26
|
+
# call-seq:
|
27
|
+
# sandbox.eval(str, opts={}) => obj
|
28
|
+
#
|
29
|
+
# Evaluates +str+ as Ruby code inside the sandbox and returns
|
30
|
+
# the result. If an option hash +opts+ is provided, any options
|
31
|
+
# specified in it take precedence over options specified when +sandbox+
|
32
|
+
# was created. (See Sandbox.new.)
|
33
|
+
#
|
34
|
+
# Available options include:
|
35
|
+
#
|
36
|
+
# [:timeout] The maximum time in seconds which Sandbox#eval is allowed to
|
37
|
+
# run before it is forcibly terminated.
|
38
|
+
# [:safelevel] The $SAFE level to use during evaluation in the sandbox.
|
39
|
+
#
|
40
|
+
# If evaluation times out, Sandbox#eval will raise a
|
41
|
+
# Sandbox::TimeoutError. If no timeout is specified, Sandbox#eval will
|
42
|
+
# be allowed to run indefinitely.
|
43
|
+
#
|
44
|
+
def eval(str, opts = {})
|
45
|
+
opts = @options.merge(opts)
|
46
|
+
if opts[:timeout] or opts[:safelevel]
|
47
|
+
th, exc, timed_out = nil, nil, false
|
48
|
+
safelevel = opts[:safelevel]
|
49
|
+
val = nil
|
50
|
+
th = Thread.start(str) do
|
51
|
+
$SAFE = safelevel if safelevel and safelevel > $SAFE
|
52
|
+
begin
|
53
|
+
val = _eval(str)
|
54
|
+
rescue Exception => exc
|
55
|
+
end
|
56
|
+
end
|
57
|
+
th.join(opts[:timeout])
|
58
|
+
if th.alive?
|
59
|
+
if th.respond_to? :kill!
|
60
|
+
th.kill!
|
61
|
+
else
|
62
|
+
th.kill
|
63
|
+
end
|
64
|
+
timed_out = true
|
65
|
+
end
|
66
|
+
if timed_out
|
67
|
+
raise TimeoutError, "#{self.class}#eval timed out"
|
68
|
+
elsif exc
|
69
|
+
raise exc
|
70
|
+
else
|
71
|
+
val
|
72
|
+
end
|
73
|
+
else
|
74
|
+
_eval(str)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# call-seq:
|
80
|
+
# sandbox.load(portname, opts={}) => obj
|
81
|
+
#
|
82
|
+
# Reads all available data from the given I/O port +portname+ and
|
83
|
+
# then evaluates it as a string in +sandbox+. (See Sandbox#eval.)
|
84
|
+
#
|
85
|
+
def load(io, opts = {})
|
86
|
+
eval(IO.read(io), opts)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
data/lib/sandbox/irb.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'irb'
|
2
|
+
require 'sandbox'
|
3
|
+
|
4
|
+
class Sandbox::IRB
|
5
|
+
|
6
|
+
def initialize(box)
|
7
|
+
@box, @sig, @p = box, :IN_IRB
|
8
|
+
@box_errors = %w[StandardError ScriptError].map { |x| @box.eval(x) }
|
9
|
+
@prompt = {:start => ">> ", :continue => ".. ", :nested => ".. ",
|
10
|
+
:string => " ", :return => "=> %s\n"}
|
11
|
+
end
|
12
|
+
|
13
|
+
def box_eval(str)
|
14
|
+
@box.eval(str)
|
15
|
+
end
|
16
|
+
|
17
|
+
def start(io)
|
18
|
+
scanner = RubyLex.new
|
19
|
+
scanner.exception_on_syntax_error = false
|
20
|
+
scanner.set_prompt do |ltype, indent, continue, line_no|
|
21
|
+
if ltype
|
22
|
+
f = @prompt[:string]
|
23
|
+
elsif continue
|
24
|
+
f = @prompt[:continue]
|
25
|
+
elsif indent > 0
|
26
|
+
f = @prompt[:nested]
|
27
|
+
else
|
28
|
+
f = @prompt[:start]
|
29
|
+
end
|
30
|
+
f = "" unless f
|
31
|
+
@p = prompt(f, ltype, indent, line_no)
|
32
|
+
end
|
33
|
+
|
34
|
+
scanner.set_input(io) do
|
35
|
+
signal_status(:IN_INPUT) do
|
36
|
+
io.print @p
|
37
|
+
io.gets
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
scanner.each_top_level_statement do |line, line_no|
|
42
|
+
signal_status(:IN_EVAL) do
|
43
|
+
line.untaint
|
44
|
+
begin
|
45
|
+
val = box_eval(line)
|
46
|
+
io.puts @prompt[:return] % [val.inspect]
|
47
|
+
rescue Sandbox::Exception, Sandbox::TimeoutError => e
|
48
|
+
io.print e, "\n"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def prompt(prompt, ltype, indent, line_no)
|
55
|
+
p = prompt.dup
|
56
|
+
p.gsub!(/%([0-9]+)?([a-zA-Z])/) do
|
57
|
+
case $2
|
58
|
+
# when "N"
|
59
|
+
# @context.irb_name
|
60
|
+
# when "m"
|
61
|
+
# @context.main.to_s
|
62
|
+
# when "M"
|
63
|
+
# @context.main.inspect
|
64
|
+
when "l"
|
65
|
+
ltype
|
66
|
+
when "i"
|
67
|
+
if $1
|
68
|
+
format("%" + $1 + "d", indent)
|
69
|
+
else
|
70
|
+
indent.to_s
|
71
|
+
end
|
72
|
+
when "n"
|
73
|
+
if $1
|
74
|
+
format("%" + $1 + "d", line_no)
|
75
|
+
else
|
76
|
+
line_no.to_s
|
77
|
+
end
|
78
|
+
when "%"
|
79
|
+
"%"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
p
|
83
|
+
end
|
84
|
+
|
85
|
+
def signal_status(status)
|
86
|
+
return yield if @sig == :IN_LOAD
|
87
|
+
sig_back = @sig
|
88
|
+
@sig = status
|
89
|
+
begin
|
90
|
+
yield
|
91
|
+
ensure
|
92
|
+
@sig = sig_back
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Alternate "safer" versions of Ruby methods. Mostly non-blocking.
|
2
|
+
[Fixnum, Bignum, Float].each do |klass|
|
3
|
+
klass.class_eval do
|
4
|
+
|
5
|
+
# A very weak version of pow, it doesn't work on Floats, but it's
|
6
|
+
# gonna fill the most common uses for now.
|
7
|
+
def ** x
|
8
|
+
case x
|
9
|
+
when 0; 1
|
10
|
+
when 1; self
|
11
|
+
else
|
12
|
+
y = 1
|
13
|
+
while 0 <= (x -= 1) do
|
14
|
+
y *= self
|
15
|
+
end
|
16
|
+
y
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'sandbox/irb'
|
3
|
+
|
4
|
+
class Sandbox::IRBServer
|
5
|
+
attr_reader :acceptor
|
6
|
+
attr_reader :workers
|
7
|
+
attr_reader :host
|
8
|
+
attr_reader :port
|
9
|
+
attr_reader :timeout
|
10
|
+
attr_reader :num_processors
|
11
|
+
|
12
|
+
def initialize(host, port, num_processors=(2**30-1), timeout=0)
|
13
|
+
@socket = TCPServer.new(host, port)
|
14
|
+
@host = host
|
15
|
+
@port = port
|
16
|
+
@workers = ThreadGroup.new
|
17
|
+
@timeout = timeout
|
18
|
+
@num_processors = num_processors
|
19
|
+
@death_time = 60
|
20
|
+
@sessions = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def randid
|
24
|
+
abc = %{ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz}
|
25
|
+
(1..20).map { abc[rand(abc.size),1] }.join
|
26
|
+
end
|
27
|
+
|
28
|
+
def new_sandbox
|
29
|
+
Sandbox.safe(:timeout => 10)
|
30
|
+
end
|
31
|
+
|
32
|
+
def process_client(client)
|
33
|
+
begin
|
34
|
+
case client.gets
|
35
|
+
when /^LOGIN (\w+)/
|
36
|
+
sess = $1
|
37
|
+
else
|
38
|
+
sess = randid
|
39
|
+
end
|
40
|
+
|
41
|
+
@sessions[sess] ||= new_sandbox
|
42
|
+
client.puts sess
|
43
|
+
Sandbox::IRB.new(@sessions[sess]).start(client)
|
44
|
+
rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
|
45
|
+
# ignored
|
46
|
+
rescue Errno::EMFILE
|
47
|
+
reap_dead_workers('too many files')
|
48
|
+
rescue Object
|
49
|
+
STDERR.puts "#{Time.now}: ERROR: #$!"
|
50
|
+
STDERR.puts $!.backtrace.join("\n")
|
51
|
+
ensure
|
52
|
+
client.close unless client.closed?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Used internally to kill off any worker threads that have taken too long
|
57
|
+
# to complete processing. Only called if there are too many processors
|
58
|
+
# currently servicing. It returns the count of workers still active
|
59
|
+
# after the reap is done. It only runs if there are workers to reap.
|
60
|
+
def reap_dead_workers(reason='unknown')
|
61
|
+
if @workers.list.length > 0
|
62
|
+
STDERR.puts "#{Time.now}: Reaping #{@workers.list.length} threads for slow workers because of '#{reason}'"
|
63
|
+
mark = Time.now
|
64
|
+
@workers.list.each do |w|
|
65
|
+
w[:started_on] = Time.now if not w[:started_on]
|
66
|
+
|
67
|
+
if mark - w[:started_on] > @death_time + @timeout
|
68
|
+
STDERR.puts "Thread #{w.inspect} is too old, killing."
|
69
|
+
w.raise(TimeoutError.new("Timed out thread."))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
return @workers.list.length
|
75
|
+
end
|
76
|
+
|
77
|
+
# Performs a wait on all the currently running threads and kills any that take
|
78
|
+
# too long. Right now it just waits 60 seconds, but will expand this to
|
79
|
+
# allow setting. The @timeout setting does extend this waiting period by
|
80
|
+
# that much longer.
|
81
|
+
def graceful_shutdown
|
82
|
+
while reap_dead_workers("shutdown") > 0
|
83
|
+
STDERR.print "Waiting for #{@workers.list.length} requests to finish, could take #{@death_time + @timeout} seconds."
|
84
|
+
sleep @death_time / 10
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
# Runs the thing. It returns the thread used so you can "join" it. You can also
|
90
|
+
# access the HttpServer::acceptor attribute to get the thread later.
|
91
|
+
def run
|
92
|
+
BasicSocket.do_not_reverse_lookup=true
|
93
|
+
|
94
|
+
@acceptor = Thread.new do
|
95
|
+
while true
|
96
|
+
begin
|
97
|
+
client = @socket.accept
|
98
|
+
worker_list = @workers.list
|
99
|
+
|
100
|
+
if worker_list.length >= @num_processors
|
101
|
+
STDERR.puts "Server overloaded with #{worker_list.length} processors (#@num_processors max). Dropping connection."
|
102
|
+
client.close
|
103
|
+
reap_dead_workers("max processors")
|
104
|
+
else
|
105
|
+
thread = Thread.new { process_client(client) }
|
106
|
+
thread.abort_on_exception = true
|
107
|
+
thread[:started_on] = Time.now
|
108
|
+
@workers.add(thread)
|
109
|
+
|
110
|
+
sleep @timeout/100 if @timeout > 0
|
111
|
+
end
|
112
|
+
rescue StopServer
|
113
|
+
@socket.close if not @socket.closed?
|
114
|
+
break
|
115
|
+
rescue Errno::EMFILE
|
116
|
+
reap_dead_workers("too many open files")
|
117
|
+
sleep 0.5
|
118
|
+
rescue Errno::ECONNABORTED
|
119
|
+
# client closed the socket even before accept
|
120
|
+
client.close if not client.closed?
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
graceful_shutdown
|
125
|
+
end
|
126
|
+
|
127
|
+
return @acceptor
|
128
|
+
end
|
129
|
+
|
130
|
+
# Stops the acceptor thread and then causes the worker threads to finish
|
131
|
+
# off the request queue before finally exiting.
|
132
|
+
def stop
|
133
|
+
stopper = Thread.new do
|
134
|
+
exc = StopServer.new
|
135
|
+
@acceptor.raise(exc)
|
136
|
+
end
|
137
|
+
stopper.priority = 10
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
metadata
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.0
|
3
|
+
specification_version: 1
|
4
|
+
name: javasand
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.0.1
|
7
|
+
date: 2006-12-02 00:00:00 +01:00
|
8
|
+
summary: Sandbox support for JRuby. Only usable with JRuby
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: ola@ologix.com
|
12
|
+
homepage: http://jruby-extras.rubyforge.org/
|
13
|
+
rubyforge_project:
|
14
|
+
description:
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: false
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- JRuby-extras
|
31
|
+
files:
|
32
|
+
- LICENSE
|
33
|
+
- README
|
34
|
+
- lib/sand_table.jar
|
35
|
+
- lib/sandbox.rb
|
36
|
+
- lib/sandbox/prelude.rb
|
37
|
+
- lib/sandbox/server.rb
|
38
|
+
- lib/sandbox/irb.rb
|
39
|
+
test_files: []
|
40
|
+
|
41
|
+
rdoc_options: []
|
42
|
+
|
43
|
+
extra_rdoc_files: []
|
44
|
+
|
45
|
+
executables: []
|
46
|
+
|
47
|
+
extensions: []
|
48
|
+
|
49
|
+
requirements:
|
50
|
+
- JRuby with the org.jruby.Profile-class
|
51
|
+
dependencies: []
|
52
|
+
|