javasand 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,2 @@
1
+ An implementation of Sandbox for JRuby.
2
+ Based on Sandbox 0.3 by _why the lucky stiff.
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
@@ -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
+