jruby_sandbox 0.1.0-java
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/.gitignore +6 -0
- data/.rvmrc +47 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +39 -0
- data/README.md +66 -0
- data/Rakefile +24 -0
- data/ext/java/sandbox/BoxedClass.java +25 -0
- data/ext/java/sandbox/SandboxFull.java +308 -0
- data/ext/java/sandbox/SandboxModule.java +16 -0
- data/ext/java/sandbox/SandboxProfile.java +22 -0
- data/ext/java/sandbox/SandboxService.java +33 -0
- data/jruby_sandbox.gemspec +27 -0
- data/lib/sandbox/prelude.rb +19 -0
- data/lib/sandbox/sandbox.jar +0 -0
- data/lib/sandbox/version.rb +3 -0
- data/lib/sandbox.rb +333 -0
- data/spec/sandbox_spec.rb +226 -0
- data/spec/support/foo.txt +1 -0
- metadata +122 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# This is an RVM Project .rvmrc file, used to automatically load the ruby
|
4
|
+
# development environment upon cd'ing into the directory
|
5
|
+
|
6
|
+
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
|
7
|
+
environment_id="jruby-1.6.3@jruby_sandbox"
|
8
|
+
|
9
|
+
#
|
10
|
+
# Uncomment following line if you want options to be set only for given project.
|
11
|
+
#
|
12
|
+
PROJECT_JRUBY_OPTS=( --1.9 )
|
13
|
+
|
14
|
+
#
|
15
|
+
# First we attempt to load the desired environment directly from the environment
|
16
|
+
# file. This is very fast and efficient compared to running through the entire
|
17
|
+
# CLI and selector. If you want feedback on which environment was used then
|
18
|
+
# insert the word 'use' after --create as this triggers verbose mode.
|
19
|
+
#
|
20
|
+
if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
|
21
|
+
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
|
22
|
+
then
|
23
|
+
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
|
24
|
+
|
25
|
+
if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]
|
26
|
+
then
|
27
|
+
. "${rvm_path:-$HOME/.rvm}/hooks/after_use"
|
28
|
+
fi
|
29
|
+
else
|
30
|
+
# If the environment file has not yet been created, use the RVM CLI to select.
|
31
|
+
if ! rvm --create "$environment_id"
|
32
|
+
then
|
33
|
+
echo "Failed to create RVM environment '${environment_id}'."
|
34
|
+
exit 1
|
35
|
+
fi
|
36
|
+
fi
|
37
|
+
|
38
|
+
#
|
39
|
+
# If you use an RVM gemset file to install a list of gems (*.gems), you can have
|
40
|
+
# it be automatically loaded. Uncomment the following and adjust the filename if
|
41
|
+
# necessary.
|
42
|
+
#
|
43
|
+
# filename=".gems"
|
44
|
+
# if [[ -s "$filename" ]] ; then
|
45
|
+
# rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d'
|
46
|
+
# fi
|
47
|
+
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
GIT
|
2
|
+
remote: git://github.com/nanothief/fakefs.git
|
3
|
+
revision: cb63ff262e1ab9dffdda6d20a1b9c90434e97d58
|
4
|
+
specs:
|
5
|
+
fakefs (0.3.2)
|
6
|
+
|
7
|
+
PATH
|
8
|
+
remote: .
|
9
|
+
specs:
|
10
|
+
jruby_sandbox (0.1.0-java)
|
11
|
+
fakefs
|
12
|
+
|
13
|
+
GEM
|
14
|
+
remote: http://rubygems.org/
|
15
|
+
specs:
|
16
|
+
diff-lcs (1.1.2)
|
17
|
+
rake (0.9.2)
|
18
|
+
rake-compiler (0.7.9)
|
19
|
+
rake
|
20
|
+
rspec (2.6.0)
|
21
|
+
rspec-core (~> 2.6.0)
|
22
|
+
rspec-expectations (~> 2.6.0)
|
23
|
+
rspec-mocks (~> 2.6.0)
|
24
|
+
rspec-core (2.6.4)
|
25
|
+
rspec-expectations (2.6.0)
|
26
|
+
diff-lcs (~> 1.1.2)
|
27
|
+
rspec-mocks (2.6.0)
|
28
|
+
yard (0.7.2)
|
29
|
+
|
30
|
+
PLATFORMS
|
31
|
+
java
|
32
|
+
|
33
|
+
DEPENDENCIES
|
34
|
+
fakefs!
|
35
|
+
jruby_sandbox!
|
36
|
+
rake
|
37
|
+
rake-compiler
|
38
|
+
rspec
|
39
|
+
yard
|
data/README.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
JRuby Sandbox
|
2
|
+
=============
|
3
|
+
|
4
|
+
The JRuby sandbox is a reimplementation of _why's freaky freaky sandbox
|
5
|
+
in JRuby, and is heavily based on [javasand][1] by Ola Bini, but updated
|
6
|
+
for JRuby 1.6.
|
7
|
+
|
8
|
+
## Prerequisites
|
9
|
+
|
10
|
+
This gem requires JRuby 1.6. As of the time of this writing, it is known to
|
11
|
+
work with the latest stable version of JRuby, 1.6.3. You can install it via
|
12
|
+
RVM with the following command:
|
13
|
+
|
14
|
+
rvm install jruby-1.6.3
|
15
|
+
|
16
|
+
## Building
|
17
|
+
|
18
|
+
To build the JRuby extension, run `rake compile`. This will build the
|
19
|
+
`lib/sandbox/sandbox.jar` file, which `lib/sandbox.rb` loads.
|
20
|
+
|
21
|
+
## Basic Usage
|
22
|
+
|
23
|
+
Sandbox gives you a self-contained JRuby interpreter in which to eval
|
24
|
+
code without polluting the host environment.
|
25
|
+
|
26
|
+
>> require "sandbox"
|
27
|
+
=> true
|
28
|
+
>> sand = Sandbox::Full.new
|
29
|
+
=> #<Sandbox::Full:0x46377e2a>
|
30
|
+
>> sand.eval("x = 1 + 2")
|
31
|
+
=> 3
|
32
|
+
>> sand.eval("x")
|
33
|
+
=> 3
|
34
|
+
>> x
|
35
|
+
NameError: undefined local variable or method `x' for #<Object:0x11cdc190>
|
36
|
+
|
37
|
+
There's also `Sandbox::Full#require`, which lets you invoke
|
38
|
+
`Kernel#require` directly for the sandbox, so you can load any trusted
|
39
|
+
core libraries. Note that this is a direct binding to `Kernel#require`,
|
40
|
+
so it will only load ruby stdlib libraries (i.e. no rubygems support
|
41
|
+
yet).
|
42
|
+
|
43
|
+
## Sandbox::Safe usage
|
44
|
+
|
45
|
+
Sandbox::Safe exposes an `#activate!` method which will lock down the sandbox, removing unsafe methods. Before calling `#activate!`, Sandbox::Safe is the same as Sandbox::Full.
|
46
|
+
|
47
|
+
>> require 'sandbox'
|
48
|
+
=> true
|
49
|
+
>> sand = Sandbox.safe
|
50
|
+
=> #<Sandbox::Safe:0x17072b90>
|
51
|
+
>> sand.eval %{`echo HELLO`}
|
52
|
+
=> "HELLO\n"
|
53
|
+
>> sand.activate!
|
54
|
+
>> sand.eval %{`echo HELLO`}
|
55
|
+
Sandbox::SandboxException: NoMethodError: undefined method ``' for main:Object
|
56
|
+
|
57
|
+
Sandbox::Safe works by whitelisting methods to keep, and removing the rest. Checkout sandbox.rb for which methods are kept.
|
58
|
+
|
59
|
+
## Known Issues / TODOs
|
60
|
+
|
61
|
+
* It would be a good idea to integrate something like FakeFS to stub
|
62
|
+
out the filesystem in the sandbox.
|
63
|
+
* There is currently no timeout support, so it's possible for a
|
64
|
+
sandbox to loop indefinitely and block the host interpreter.
|
65
|
+
|
66
|
+
[1]: http://ola-bini.blogspot.com/2006/12/freaky-freaky-sandbox-has-come-to-jruby.html
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rake/javaextensiontask'
|
5
|
+
|
6
|
+
Rake::JavaExtensionTask.new('sandbox') do |ext|
|
7
|
+
jruby_home = RbConfig::CONFIG['prefix']
|
8
|
+
ext.ext_dir = 'ext/java'
|
9
|
+
ext.lib_dir = 'lib/sandbox'
|
10
|
+
jars = ["#{jruby_home}/lib/jruby.jar"] + FileList['lib/*.jar']
|
11
|
+
ext.classpath = jars.map { |x| File.expand_path(x) }.join(':')
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'rspec/core/rake_task'
|
15
|
+
|
16
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
17
|
+
t.pattern = 'spec/**/*_spec.rb'
|
18
|
+
t.rspec_opts = ['--backtrace']
|
19
|
+
end
|
20
|
+
|
21
|
+
# Make sure the jar is up to date before running specs
|
22
|
+
task :spec => :compile
|
23
|
+
|
24
|
+
task :default => :spec
|
@@ -0,0 +1,25 @@
|
|
1
|
+
package sandbox;
|
2
|
+
|
3
|
+
import org.jruby.anno.JRubyClass;
|
4
|
+
import org.jruby.anno.JRubyMethod;
|
5
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
6
|
+
import org.jruby.runtime.Block;
|
7
|
+
|
8
|
+
@JRubyClass(name="BoxedClass")
|
9
|
+
public class BoxedClass {
|
10
|
+
@JRubyMethod(module=true, rest=true)
|
11
|
+
public static IRubyObject method_missing(IRubyObject recv, IRubyObject[] args, Block block) {
|
12
|
+
IRubyObject[] args2 = new IRubyObject[args.length - 1];
|
13
|
+
System.arraycopy(args, 1, args2, 0, args2.length);
|
14
|
+
String name = args[0].toString();
|
15
|
+
|
16
|
+
SandboxFull box = (SandboxFull) SandboxFull.getLinkedBox(recv);
|
17
|
+
return box.runMethod(recv, name, args2, block);
|
18
|
+
}
|
19
|
+
|
20
|
+
@JRubyMethod(name="new", meta=true, rest=true)
|
21
|
+
public static IRubyObject _new(IRubyObject recv, IRubyObject[] args, Block block) {
|
22
|
+
SandboxFull box = (SandboxFull) SandboxFull.getLinkedBox(recv);
|
23
|
+
return box.runMethod(recv, "new", args, block);
|
24
|
+
}
|
25
|
+
}
|
@@ -0,0 +1,308 @@
|
|
1
|
+
package sandbox;
|
2
|
+
|
3
|
+
import java.util.Collection;
|
4
|
+
import java.util.Map;
|
5
|
+
|
6
|
+
import org.jruby.Ruby;
|
7
|
+
import org.jruby.RubyClass;
|
8
|
+
import org.jruby.RubyInstanceConfig;
|
9
|
+
import org.jruby.RubyKernel;
|
10
|
+
import org.jruby.RubyModule;
|
11
|
+
import org.jruby.RubyObject;
|
12
|
+
import org.jruby.CompatVersion;
|
13
|
+
import org.jruby.anno.JRubyClass;
|
14
|
+
import org.jruby.anno.JRubyMethod;
|
15
|
+
import org.jruby.internal.runtime.methods.DynamicMethod;
|
16
|
+
import org.jruby.runtime.Block;
|
17
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
18
|
+
import org.jruby.common.IRubyWarnings;
|
19
|
+
import org.jruby.exceptions.RaiseException;
|
20
|
+
|
21
|
+
|
22
|
+
@JRubyClass(name="Sandbox::Full")
|
23
|
+
public class SandboxFull extends RubyObject {
|
24
|
+
private Ruby wrapped;
|
25
|
+
|
26
|
+
public SandboxFull(Ruby runtime, RubyClass type) {
|
27
|
+
super(runtime, type);
|
28
|
+
reload();
|
29
|
+
}
|
30
|
+
|
31
|
+
@JRubyMethod
|
32
|
+
public IRubyObject reload() {
|
33
|
+
RubyInstanceConfig cfg = new RubyInstanceConfig();
|
34
|
+
cfg.setObjectSpaceEnabled(getRuntime().getInstanceConfig().isObjectSpaceEnabled());
|
35
|
+
cfg.setInput(getRuntime().getInstanceConfig().getInput());
|
36
|
+
cfg.setOutput(getRuntime().getInstanceConfig().getOutput());
|
37
|
+
cfg.setError(getRuntime().getInstanceConfig().getError());
|
38
|
+
cfg.setCompatVersion(CompatVersion.RUBY1_9);
|
39
|
+
cfg.setScriptFileName("(sandbox)");
|
40
|
+
|
41
|
+
SandboxProfile profile = new SandboxProfile(this);
|
42
|
+
cfg.setProfile(profile);
|
43
|
+
|
44
|
+
wrapped = Ruby.newInstance(cfg);
|
45
|
+
|
46
|
+
RubyClass cBoxedClass = wrapped.defineClass("BoxedClass", wrapped.getObject(), wrapped.getObject().getAllocator());
|
47
|
+
cBoxedClass.defineAnnotatedMethods(BoxedClass.class);
|
48
|
+
|
49
|
+
return this;
|
50
|
+
}
|
51
|
+
|
52
|
+
@JRubyMethod(required=1)
|
53
|
+
public IRubyObject eval(IRubyObject str) {
|
54
|
+
try {
|
55
|
+
IRubyObject result = wrapped.evalScriptlet(str.asJavaString(), wrapped.getCurrentContext().getCurrentScope());
|
56
|
+
return unbox(result);
|
57
|
+
} catch (RaiseException e) {
|
58
|
+
String msg = e.getException().callMethod(wrapped.getCurrentContext(), "message").asJavaString();
|
59
|
+
String path = e.getException().type().getName();
|
60
|
+
RubyClass eSandboxException = (RubyClass) getRuntime().getClassFromPath("Sandbox::SandboxException");
|
61
|
+
throw new RaiseException(getRuntime(), eSandboxException, path + ": " + msg, false);
|
62
|
+
} catch (Exception e) {
|
63
|
+
e.printStackTrace();
|
64
|
+
getRuntime().getWarnings().warn(IRubyWarnings.ID.MISCELLANEOUS, "NativeException: " + e);
|
65
|
+
return getRuntime().getNil();
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
@JRubyMethod(name="import", required=1)
|
70
|
+
public IRubyObject _import(IRubyObject klass) {
|
71
|
+
if (!(klass instanceof RubyModule)) {
|
72
|
+
throw getRuntime().newTypeError(klass, getRuntime().getClass("Module"));
|
73
|
+
}
|
74
|
+
String name = ((RubyModule) klass).getName();
|
75
|
+
importClassPath(name, false);
|
76
|
+
return getRuntime().getNil();
|
77
|
+
}
|
78
|
+
|
79
|
+
@JRubyMethod(required=1)
|
80
|
+
public IRubyObject ref(IRubyObject klass) {
|
81
|
+
if (!(klass instanceof RubyModule)) {
|
82
|
+
throw getRuntime().newTypeError(klass, getRuntime().getClass("Module"));
|
83
|
+
}
|
84
|
+
String name = ((RubyModule) klass).getName();
|
85
|
+
importClassPath(name, true);
|
86
|
+
return getRuntime().getNil();
|
87
|
+
}
|
88
|
+
|
89
|
+
private RubyModule importClassPath(String path, final boolean link) {
|
90
|
+
RubyModule runtimeModule = getRuntime().getObject();
|
91
|
+
RubyModule wrappedModule = wrapped.getObject();
|
92
|
+
|
93
|
+
if (path.startsWith("#")) {
|
94
|
+
throw getRuntime().newArgumentError("can't import anonymous class " + path);
|
95
|
+
}
|
96
|
+
|
97
|
+
for (String name : path.split("::")) {
|
98
|
+
runtimeModule = (RubyModule) runtimeModule.getConstantAt(name);
|
99
|
+
// Create the module when it did not exist yet...
|
100
|
+
if (wrappedModule.const_defined_p(wrapped.getCurrentContext(), wrapped.newString(name)).isFalse()) {
|
101
|
+
// The BoxedClass takes the place of Object as top of the inheritance
|
102
|
+
// hierarchy. As a result, we can intercept all new instances that are
|
103
|
+
// created and all method_missing calls.
|
104
|
+
RubyModule sup = wrapped.getClass("BoxedClass");
|
105
|
+
if (!link && runtimeModule instanceof RubyClass) {
|
106
|
+
// If we're importing a class, recursively import all of its
|
107
|
+
// superclasses as well.
|
108
|
+
sup = importClassPath(runtimeModule.getSuperClass().getName(), true);
|
109
|
+
}
|
110
|
+
|
111
|
+
RubyClass klass = (RubyClass) sup;
|
112
|
+
if (wrappedModule == wrapped.getObject()) {
|
113
|
+
|
114
|
+
if (link || runtimeModule instanceof RubyClass){ // if this is a ref and not an import
|
115
|
+
wrappedModule = wrapped.defineClass(name, klass, klass.getAllocator());
|
116
|
+
} else {
|
117
|
+
wrappedModule = wrapped.defineModule(name);
|
118
|
+
}
|
119
|
+
|
120
|
+
} else {
|
121
|
+
if (runtimeModule instanceof RubyClass){
|
122
|
+
wrappedModule = wrappedModule.defineClassUnder(name, klass, klass.getAllocator());
|
123
|
+
} else {
|
124
|
+
wrappedModule = wrappedModule.defineModuleUnder(name);
|
125
|
+
}
|
126
|
+
|
127
|
+
}
|
128
|
+
} else {
|
129
|
+
// ...or just resolve it, if it was already known
|
130
|
+
wrappedModule = (RubyModule) wrappedModule.getConstantAt(name);
|
131
|
+
}
|
132
|
+
|
133
|
+
// Check the consistency of the hierarchy
|
134
|
+
if (runtimeModule instanceof RubyClass) {
|
135
|
+
if (!link && !runtimeModule.getSuperClass().getName().equals(wrappedModule.getSuperClass().getName())) {
|
136
|
+
throw getRuntime().newTypeError("superclass mismatch for class " + runtimeModule.getSuperClass().getName());
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
if (link || runtimeModule instanceof RubyClass) {
|
141
|
+
linkObject(runtimeModule, wrappedModule);
|
142
|
+
} else {
|
143
|
+
copyMethods(runtimeModule, wrappedModule);
|
144
|
+
}
|
145
|
+
}
|
146
|
+
|
147
|
+
return runtimeModule;
|
148
|
+
}
|
149
|
+
|
150
|
+
private void copyMethods(RubyModule from, RubyModule to) {
|
151
|
+
to.getMethodsForWrite().putAll(from.getMethods());
|
152
|
+
to.getSingletonClass().getMethodsForWrite().putAll(from.getSingletonClass().getMethods());
|
153
|
+
}
|
154
|
+
|
155
|
+
@JRubyMethod(required=2)
|
156
|
+
public IRubyObject keep_methods(IRubyObject className, IRubyObject methods) {
|
157
|
+
RubyModule module = wrapped.getModule(className.asJavaString());
|
158
|
+
if (module != null) {
|
159
|
+
keepMethods(module, methods.convertToArray());
|
160
|
+
}
|
161
|
+
return methods;
|
162
|
+
}
|
163
|
+
|
164
|
+
@JRubyMethod(required=2)
|
165
|
+
public IRubyObject keep_singleton_methods(IRubyObject className, IRubyObject methods) {
|
166
|
+
RubyModule module = wrapped.getModule(className.asJavaString()).getSingletonClass();
|
167
|
+
if (module != null) {
|
168
|
+
keepMethods(module, methods.convertToArray());
|
169
|
+
}
|
170
|
+
return methods;
|
171
|
+
}
|
172
|
+
|
173
|
+
private void keepMethods(RubyModule module, Collection retain) {
|
174
|
+
for (Map.Entry<String, DynamicMethod> methodEntry : module.getMethods().entrySet()) {
|
175
|
+
String methodName = methodEntry.getKey();
|
176
|
+
if (!retain.contains(methodName)) {
|
177
|
+
removeMethod(module, methodName);
|
178
|
+
}
|
179
|
+
}
|
180
|
+
}
|
181
|
+
|
182
|
+
@JRubyMethod(required=2)
|
183
|
+
public IRubyObject remove_method(IRubyObject className, IRubyObject methodName) {
|
184
|
+
RubyModule module = wrapped.getModule(className.asJavaString());
|
185
|
+
if (module != null) {
|
186
|
+
removeMethod(module, methodName.asJavaString());
|
187
|
+
}
|
188
|
+
return getRuntime().getNil();
|
189
|
+
}
|
190
|
+
|
191
|
+
@JRubyMethod(required=2)
|
192
|
+
public IRubyObject remove_singleton_method(IRubyObject className, IRubyObject methodName) {
|
193
|
+
RubyModule module = wrapped.getModule(className.asJavaString()).getSingletonClass();
|
194
|
+
if (module != null) {
|
195
|
+
removeMethod(module, methodName.asJavaString());
|
196
|
+
}
|
197
|
+
return getRuntime().getNil();
|
198
|
+
}
|
199
|
+
|
200
|
+
private void removeMethod(RubyModule module, String methodName) {
|
201
|
+
// System.err.println("removing method " + methodName + " from " + module.inspect().asJavaString());
|
202
|
+
module.removeMethod(wrapped.getCurrentContext(), methodName);
|
203
|
+
}
|
204
|
+
|
205
|
+
@JRubyMethod(required=1)
|
206
|
+
public IRubyObject load(IRubyObject str) {
|
207
|
+
try {
|
208
|
+
wrapped.getLoadService().load(str.asJavaString(), true);
|
209
|
+
return getRuntime().getTrue();
|
210
|
+
} catch (RaiseException e) {
|
211
|
+
e.printStackTrace();
|
212
|
+
return getRuntime().getFalse();
|
213
|
+
}
|
214
|
+
}
|
215
|
+
|
216
|
+
@JRubyMethod(required=1)
|
217
|
+
public IRubyObject require(IRubyObject str) {
|
218
|
+
try {
|
219
|
+
IRubyObject result = RubyKernel.require(wrapped.getKernel(), wrapped.newString(str.asJavaString()), Block.NULL_BLOCK);
|
220
|
+
return unbox(result);
|
221
|
+
} catch (RaiseException e) {
|
222
|
+
e.printStackTrace();
|
223
|
+
return getRuntime().getFalse();
|
224
|
+
}
|
225
|
+
}
|
226
|
+
|
227
|
+
private IRubyObject unbox(IRubyObject obj) {
|
228
|
+
return box(obj);
|
229
|
+
}
|
230
|
+
|
231
|
+
private IRubyObject rebox(IRubyObject obj) {
|
232
|
+
return box(obj);
|
233
|
+
}
|
234
|
+
|
235
|
+
private IRubyObject box(IRubyObject obj) {
|
236
|
+
if (obj.isImmediate()) {
|
237
|
+
return cross(obj);
|
238
|
+
} else {
|
239
|
+
// If this object already existed and was returned from the wrapped
|
240
|
+
// runtime on an earlier occasion, it will already contain a link to its
|
241
|
+
// brother in the regular runtime and we can safely return that link.
|
242
|
+
IRubyObject link = getLinkedObject(obj);
|
243
|
+
if (!link.isNil()) {
|
244
|
+
IRubyObject box = getLinkedBox(obj);
|
245
|
+
if (box == this) return link;
|
246
|
+
}
|
247
|
+
|
248
|
+
// Is the class already known on both sides of the fence?
|
249
|
+
IRubyObject klass = constFind(obj.getMetaClass().getRealClass().getName());
|
250
|
+
link = getRuntime().getNil();
|
251
|
+
if (!klass.isNil()) {
|
252
|
+
link = getLinkedObject(klass);
|
253
|
+
}
|
254
|
+
|
255
|
+
if (link.isNil()) {
|
256
|
+
return cross(obj);
|
257
|
+
} else {
|
258
|
+
IRubyObject v = ((RubyClass)klass).allocate();
|
259
|
+
linkObject(obj, v);
|
260
|
+
return v;
|
261
|
+
}
|
262
|
+
}
|
263
|
+
}
|
264
|
+
|
265
|
+
private IRubyObject cross(IRubyObject obj) {
|
266
|
+
IRubyObject dumped = wrapped.getModule("Marshal").callMethod(wrapped.getCurrentContext(), "dump", obj);
|
267
|
+
return getRuntime().getModule("Marshal").callMethod(getRuntime().getCurrentContext(), "load", dumped);
|
268
|
+
}
|
269
|
+
|
270
|
+
protected static IRubyObject getLinkedObject(IRubyObject arg) {
|
271
|
+
IRubyObject object = arg.getRuntime().getNil();
|
272
|
+
if (arg.getInstanceVariables().getInstanceVariable("__link__") != null) {
|
273
|
+
object = (IRubyObject) arg.getInstanceVariables().fastGetInstanceVariable("__link__");
|
274
|
+
}
|
275
|
+
return object;
|
276
|
+
}
|
277
|
+
|
278
|
+
protected static IRubyObject getLinkedBox(IRubyObject arg) {
|
279
|
+
IRubyObject object = arg.getRuntime().getNil();
|
280
|
+
if (arg.getInstanceVariables().getInstanceVariable("__box__") != null) {
|
281
|
+
object = (IRubyObject) arg.getInstanceVariables().fastGetInstanceVariable("__box__");
|
282
|
+
}
|
283
|
+
return object;
|
284
|
+
}
|
285
|
+
|
286
|
+
private void linkObject(IRubyObject runtimeObject, IRubyObject wrappedObject) {
|
287
|
+
wrappedObject.getInstanceVariables().setInstanceVariable("__link__", runtimeObject);
|
288
|
+
wrappedObject.getInstanceVariables().setInstanceVariable("__box__", this);
|
289
|
+
}
|
290
|
+
|
291
|
+
private IRubyObject constFind(String path) {
|
292
|
+
try {
|
293
|
+
return wrapped.getClassFromPath(path);
|
294
|
+
} catch (Exception e) {
|
295
|
+
return wrapped.getNil();
|
296
|
+
}
|
297
|
+
}
|
298
|
+
|
299
|
+
protected IRubyObject runMethod(IRubyObject recv, String name, IRubyObject[] args, Block block) {
|
300
|
+
IRubyObject[] args2 = new IRubyObject[args.length];
|
301
|
+
for (int i = 0; i < args.length; i++) {
|
302
|
+
args2[i] = unbox(args[i]);
|
303
|
+
}
|
304
|
+
IRubyObject recv2 = unbox(recv);
|
305
|
+
IRubyObject result = recv2.callMethod(getRuntime().getCurrentContext(), name, args2, block);
|
306
|
+
return rebox(result);
|
307
|
+
}
|
308
|
+
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
package sandbox;
|
2
|
+
|
3
|
+
import org.jruby.Profile;
|
4
|
+
import org.jruby.anno.JRubyMethod;
|
5
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
6
|
+
|
7
|
+
public class SandboxModule {
|
8
|
+
@JRubyMethod(name="current", meta=true)
|
9
|
+
public static IRubyObject s_current(IRubyObject recv) {
|
10
|
+
Profile prof = recv.getRuntime().getProfile();
|
11
|
+
if (prof instanceof SandboxProfile) {
|
12
|
+
return ((SandboxProfile) prof).getSandbox();
|
13
|
+
}
|
14
|
+
return recv.getRuntime().getNil();
|
15
|
+
}
|
16
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
package sandbox;
|
2
|
+
|
3
|
+
import org.jruby.Profile;
|
4
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
5
|
+
|
6
|
+
public class SandboxProfile implements Profile {
|
7
|
+
private IRubyObject sandbox;
|
8
|
+
|
9
|
+
public SandboxProfile(IRubyObject sandbox) {
|
10
|
+
this.sandbox = sandbox;
|
11
|
+
}
|
12
|
+
|
13
|
+
public IRubyObject getSandbox() {
|
14
|
+
return sandbox;
|
15
|
+
}
|
16
|
+
|
17
|
+
public boolean allowBuiltin(String name) { return true; }
|
18
|
+
public boolean allowClass(String name) { return true; }
|
19
|
+
public boolean allowModule(String name) { return true; }
|
20
|
+
public boolean allowLoad(String name) { return true; }
|
21
|
+
public boolean allowRequire(String name) { return true; }
|
22
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
package sandbox;
|
2
|
+
|
3
|
+
import java.io.IOException;
|
4
|
+
|
5
|
+
import org.jruby.Ruby;
|
6
|
+
import org.jruby.RubyClass;
|
7
|
+
import org.jruby.RubyModule;
|
8
|
+
import org.jruby.runtime.ObjectAllocator;
|
9
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
10
|
+
import org.jruby.runtime.load.BasicLibraryService;
|
11
|
+
|
12
|
+
public class SandboxService implements BasicLibraryService {
|
13
|
+
public boolean basicLoad(Ruby runtime) throws IOException {
|
14
|
+
init(runtime);
|
15
|
+
return true;
|
16
|
+
}
|
17
|
+
|
18
|
+
private void init(Ruby runtime) {
|
19
|
+
RubyModule mSandbox = runtime.defineModule("Sandbox");
|
20
|
+
mSandbox.defineAnnotatedMethods(SandboxModule.class);
|
21
|
+
|
22
|
+
RubyClass cSandboxFull = mSandbox.defineClassUnder("Full", runtime.getObject(), FULL_ALLOCATOR);
|
23
|
+
cSandboxFull.defineAnnotatedMethods(SandboxFull.class);
|
24
|
+
|
25
|
+
RubyClass cSandboxException = mSandbox.defineClassUnder("SandboxException", runtime.getException(), runtime.getException().getAllocator());
|
26
|
+
}
|
27
|
+
|
28
|
+
protected static final ObjectAllocator FULL_ALLOCATOR = new ObjectAllocator() {
|
29
|
+
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
|
30
|
+
return new SandboxFull(runtime, klass);
|
31
|
+
}
|
32
|
+
};
|
33
|
+
}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'sandbox/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'jruby_sandbox'
|
7
|
+
s.version = Sandbox::VERSION
|
8
|
+
s.platform = 'java'
|
9
|
+
s.authors = ['Dray Lacy', 'Eric Allam']
|
10
|
+
s.email = ['dray@envylabs.com', 'eric@envylabs.com']
|
11
|
+
s.homepage = 'http://github.com/omghax/jruby_sandbox'
|
12
|
+
s.summary = 'Sandbox support for JRuby'
|
13
|
+
s.description = "A version of _why's Freaky Freaky Sandbox for JRuby."
|
14
|
+
|
15
|
+
s.rubyforge_project = 'jruby_sandbox'
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n") + ['lib/sandbox/sandbox.jar']
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ['lib']
|
21
|
+
|
22
|
+
s.add_dependency 'fakefs'
|
23
|
+
s.add_development_dependency 'rake'
|
24
|
+
s.add_development_dependency 'rake-compiler'
|
25
|
+
s.add_development_dependency 'rspec'
|
26
|
+
s.add_development_dependency 'yard'
|
27
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# Alternate "safer" versions of Ruby methods. Mostly non-blocking.
|
2
|
+
[Fixnum, Bignum, Float].each do |klass|
|
3
|
+
klass.class_eval do
|
4
|
+
# A very weak version of pow, it doesn't work on Floats, but it's gonna
|
5
|
+
# fill the most common uses for now.
|
6
|
+
def **(x)
|
7
|
+
case x
|
8
|
+
when 0; 1
|
9
|
+
when 1; self
|
10
|
+
else
|
11
|
+
y = 1
|
12
|
+
while 0 <= (x -= 1) do
|
13
|
+
y *= self
|
14
|
+
end
|
15
|
+
y
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
Binary file
|
data/lib/sandbox.rb
ADDED
@@ -0,0 +1,333 @@
|
|
1
|
+
require 'sandbox/sandbox'
|
2
|
+
require 'sandbox/version'
|
3
|
+
require 'fakefs/safe'
|
4
|
+
|
5
|
+
module Sandbox
|
6
|
+
PRELUDE = File.expand_path('../sandbox/prelude.rb', __FILE__).freeze # :nodoc:
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def new
|
10
|
+
Full.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def safe
|
14
|
+
Safe.new
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Safe < Full
|
19
|
+
def activate!
|
20
|
+
activate_fakefs
|
21
|
+
|
22
|
+
keep_singleton_methods(:Kernel, KERNEL_S_METHODS)
|
23
|
+
keep_singleton_methods(:Symbol, SYMBOL_S_METHODS)
|
24
|
+
keep_singleton_methods(:String, STRING_S_METHODS)
|
25
|
+
|
26
|
+
keep_methods(:Kernel, KERNEL_METHODS)
|
27
|
+
keep_methods(:NilClass, NILCLASS_METHODS)
|
28
|
+
keep_methods(:Symbol, SYMBOL_METHODS)
|
29
|
+
keep_methods(:TrueClass, TRUECLASS_METHODS)
|
30
|
+
keep_methods(:FalseClass, FALSECLASS_METHODS)
|
31
|
+
keep_methods(:Enumerable, ENUMERABLE_METHODS)
|
32
|
+
keep_methods(:String, STRING_METHODS)
|
33
|
+
end
|
34
|
+
|
35
|
+
def activate_fakefs
|
36
|
+
require 'fileutils'
|
37
|
+
|
38
|
+
# unfortunately, the authors of FakeFS used `extend self` in FileUtils, instead of `module_function`.
|
39
|
+
# I fixed it for them
|
40
|
+
(FakeFS::FileUtils.methods - Module.methods - Kernel.methods).each do |module_method_name|
|
41
|
+
FakeFS::FileUtils.send(:module_function, module_method_name)
|
42
|
+
end
|
43
|
+
|
44
|
+
import FakeFS
|
45
|
+
ref FakeFS::Dir
|
46
|
+
ref FakeFS::File
|
47
|
+
ref FakeFS::FileTest
|
48
|
+
import FakeFS::FileUtils #import FileUtils because it is a module
|
49
|
+
|
50
|
+
eval <<-RUBY
|
51
|
+
Object.class_eval do
|
52
|
+
remove_const(:Dir)
|
53
|
+
remove_const(:File)
|
54
|
+
remove_const(:FileTest)
|
55
|
+
remove_const(:FileUtils)
|
56
|
+
|
57
|
+
const_set(:Dir, FakeFS::Dir)
|
58
|
+
const_set(:File, FakeFS::File)
|
59
|
+
const_set(:FileUtils, FakeFS::FileUtils)
|
60
|
+
const_set(:FileTest, FakeFS::FileTest)
|
61
|
+
end
|
62
|
+
RUBY
|
63
|
+
|
64
|
+
FakeFS::FileSystem.clear
|
65
|
+
end
|
66
|
+
|
67
|
+
KERNEL_S_METHODS = %w[
|
68
|
+
Array
|
69
|
+
binding
|
70
|
+
block_given?
|
71
|
+
catch
|
72
|
+
chomp
|
73
|
+
chomp!
|
74
|
+
chop
|
75
|
+
chop!
|
76
|
+
eval
|
77
|
+
fail
|
78
|
+
Float
|
79
|
+
format
|
80
|
+
global_variables
|
81
|
+
gsub
|
82
|
+
gsub!
|
83
|
+
Integer
|
84
|
+
iterator?
|
85
|
+
lambda
|
86
|
+
local_variables
|
87
|
+
loop
|
88
|
+
method_missing
|
89
|
+
proc
|
90
|
+
raise
|
91
|
+
scan
|
92
|
+
split
|
93
|
+
sprintf
|
94
|
+
String
|
95
|
+
sub
|
96
|
+
sub!
|
97
|
+
throw
|
98
|
+
].freeze
|
99
|
+
|
100
|
+
SYMBOL_S_METHODS = %w[
|
101
|
+
all_symbols
|
102
|
+
].freeze
|
103
|
+
|
104
|
+
STRING_S_METHODS = %w[
|
105
|
+
new
|
106
|
+
].freeze
|
107
|
+
|
108
|
+
KERNEL_METHODS = %w[
|
109
|
+
==
|
110
|
+
===
|
111
|
+
=~
|
112
|
+
Array
|
113
|
+
binding
|
114
|
+
block_given?
|
115
|
+
catch
|
116
|
+
chomp
|
117
|
+
chomp!
|
118
|
+
chop
|
119
|
+
chop!
|
120
|
+
class
|
121
|
+
clone
|
122
|
+
dup
|
123
|
+
eql?
|
124
|
+
equal?
|
125
|
+
eval
|
126
|
+
fail
|
127
|
+
Float
|
128
|
+
format
|
129
|
+
freeze
|
130
|
+
frozen?
|
131
|
+
global_variables
|
132
|
+
gsub
|
133
|
+
gsub!
|
134
|
+
hash
|
135
|
+
id
|
136
|
+
initialize_copy
|
137
|
+
inspect
|
138
|
+
instance_eval
|
139
|
+
instance_of?
|
140
|
+
instance_variables
|
141
|
+
instance_variable_get
|
142
|
+
instance_variable_set
|
143
|
+
instance_variable_defined?
|
144
|
+
Integer
|
145
|
+
is_a?
|
146
|
+
iterator?
|
147
|
+
kind_of?
|
148
|
+
lambda
|
149
|
+
local_variables
|
150
|
+
loop
|
151
|
+
methods
|
152
|
+
method_missing
|
153
|
+
nil?
|
154
|
+
private_methods
|
155
|
+
print
|
156
|
+
proc
|
157
|
+
protected_methods
|
158
|
+
public_methods
|
159
|
+
raise
|
160
|
+
remove_instance_variable
|
161
|
+
respond_to?
|
162
|
+
respond_to_missing?
|
163
|
+
scan
|
164
|
+
send
|
165
|
+
singleton_methods
|
166
|
+
singleton_method_added
|
167
|
+
singleton_method_removed
|
168
|
+
singleton_method_undefined
|
169
|
+
split
|
170
|
+
sprintf
|
171
|
+
String
|
172
|
+
sub
|
173
|
+
sub!
|
174
|
+
taint
|
175
|
+
tainted?
|
176
|
+
throw
|
177
|
+
to_a
|
178
|
+
to_s
|
179
|
+
type
|
180
|
+
untaint
|
181
|
+
__send__
|
182
|
+
].freeze
|
183
|
+
|
184
|
+
NILCLASS_METHODS = %w[
|
185
|
+
&
|
186
|
+
inspect
|
187
|
+
nil?
|
188
|
+
to_a
|
189
|
+
to_f
|
190
|
+
to_i
|
191
|
+
to_s
|
192
|
+
^
|
193
|
+
|
|
194
|
+
].freeze
|
195
|
+
|
196
|
+
SYMBOL_METHODS = %w[
|
197
|
+
===
|
198
|
+
id2name
|
199
|
+
inspect
|
200
|
+
to_i
|
201
|
+
to_int
|
202
|
+
to_s
|
203
|
+
to_sym
|
204
|
+
].freeze
|
205
|
+
|
206
|
+
TRUECLASS_METHODS = %w[
|
207
|
+
&
|
208
|
+
to_s
|
209
|
+
^
|
210
|
+
|
|
211
|
+
].freeze
|
212
|
+
|
213
|
+
FALSECLASS_METHODS = %w[
|
214
|
+
&
|
215
|
+
to_s
|
216
|
+
^
|
217
|
+
|
|
218
|
+
].freeze
|
219
|
+
|
220
|
+
ENUMERABLE_METHODS = %w[
|
221
|
+
all?
|
222
|
+
any?
|
223
|
+
collect
|
224
|
+
detect
|
225
|
+
each_with_index
|
226
|
+
entries
|
227
|
+
find
|
228
|
+
find_all
|
229
|
+
grep
|
230
|
+
include?
|
231
|
+
inject
|
232
|
+
map
|
233
|
+
max
|
234
|
+
member?
|
235
|
+
min
|
236
|
+
partition
|
237
|
+
reject
|
238
|
+
select
|
239
|
+
sort
|
240
|
+
sort_by
|
241
|
+
to_a
|
242
|
+
zip
|
243
|
+
].freeze
|
244
|
+
|
245
|
+
STRING_METHODS = %w[
|
246
|
+
%
|
247
|
+
*
|
248
|
+
+
|
249
|
+
<<
|
250
|
+
<=>
|
251
|
+
==
|
252
|
+
=~
|
253
|
+
capitalize
|
254
|
+
capitalize!
|
255
|
+
casecmp
|
256
|
+
center
|
257
|
+
chomp
|
258
|
+
chomp!
|
259
|
+
chop
|
260
|
+
chop!
|
261
|
+
concat
|
262
|
+
count
|
263
|
+
crypt
|
264
|
+
delete
|
265
|
+
delete!
|
266
|
+
downcase
|
267
|
+
downcase!
|
268
|
+
dump
|
269
|
+
each
|
270
|
+
each_byte
|
271
|
+
each_line
|
272
|
+
empty?
|
273
|
+
eql?
|
274
|
+
gsub
|
275
|
+
gsub!
|
276
|
+
hash
|
277
|
+
hex
|
278
|
+
include?
|
279
|
+
index
|
280
|
+
initialize
|
281
|
+
initialize_copy
|
282
|
+
insert
|
283
|
+
inspect
|
284
|
+
intern
|
285
|
+
length
|
286
|
+
ljust
|
287
|
+
lstrip
|
288
|
+
lstrip!
|
289
|
+
match
|
290
|
+
next
|
291
|
+
next!
|
292
|
+
oct
|
293
|
+
replace
|
294
|
+
reverse
|
295
|
+
reverse!
|
296
|
+
rindex
|
297
|
+
rjust
|
298
|
+
rstrip
|
299
|
+
rstrip!
|
300
|
+
scan
|
301
|
+
size
|
302
|
+
slice
|
303
|
+
slice!
|
304
|
+
split
|
305
|
+
squeeze
|
306
|
+
squeeze!
|
307
|
+
strip
|
308
|
+
strip!
|
309
|
+
start_with?
|
310
|
+
sub
|
311
|
+
sub!
|
312
|
+
succ
|
313
|
+
succ!
|
314
|
+
sum
|
315
|
+
swapcase
|
316
|
+
swapcase!
|
317
|
+
to_f
|
318
|
+
to_i
|
319
|
+
to_s
|
320
|
+
to_str
|
321
|
+
to_sym
|
322
|
+
tr
|
323
|
+
tr!
|
324
|
+
tr_s
|
325
|
+
tr_s!
|
326
|
+
upcase
|
327
|
+
upcase!
|
328
|
+
upto
|
329
|
+
[]
|
330
|
+
[]=
|
331
|
+
].freeze
|
332
|
+
end
|
333
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'sandbox'
|
3
|
+
|
4
|
+
describe Sandbox do
|
5
|
+
after(:each) do
|
6
|
+
Object.class_eval { remove_const(:Foo) } if defined?(Foo)
|
7
|
+
end
|
8
|
+
|
9
|
+
describe ".new" do
|
10
|
+
subject { Sandbox.new }
|
11
|
+
|
12
|
+
it { should_not be_nil }
|
13
|
+
it { should be_an_instance_of(Sandbox::Full) }
|
14
|
+
end
|
15
|
+
|
16
|
+
describe ".safe" do
|
17
|
+
subject { Sandbox.safe }
|
18
|
+
|
19
|
+
it { should be_an_instance_of(Sandbox::Safe) }
|
20
|
+
|
21
|
+
it 'should not lock down until calling activate!' do
|
22
|
+
subject.eval('`echo hello`').should == "hello\n"
|
23
|
+
|
24
|
+
subject.activate!
|
25
|
+
|
26
|
+
expect {
|
27
|
+
subject.eval('`echo hello`')
|
28
|
+
}.to raise_error(Sandbox::SandboxException)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should activate FakeFS inside the sandbox (and not allow it to be deactivated)" do
|
32
|
+
subject.eval('File').should == ::File
|
33
|
+
|
34
|
+
subject.activate!
|
35
|
+
|
36
|
+
foo = File.join(File.dirname(__FILE__), 'support', 'foo.txt')
|
37
|
+
|
38
|
+
expect {
|
39
|
+
subject.eval(%{File.read('#{foo}')})
|
40
|
+
}.to raise_error(Sandbox::SandboxException, /Errno::ENOENT: No such file or directory/)
|
41
|
+
|
42
|
+
subject.eval('File').should == FakeFS::File
|
43
|
+
subject.eval('Dir').should == FakeFS::Dir
|
44
|
+
subject.eval('FileUtils').should == FakeFS::FileUtils
|
45
|
+
subject.eval('FileTest').should == FakeFS::FileTest
|
46
|
+
|
47
|
+
subject.eval(%{FakeFS.deactivate!})
|
48
|
+
|
49
|
+
expect {
|
50
|
+
subject.eval(%{File.read('#{foo}')})
|
51
|
+
}.to raise_error(Sandbox::SandboxException, /Errno::ENOENT: No such file or directory/)
|
52
|
+
|
53
|
+
subject.eval(%{File.open('/bar.txt', 'w') {|file| file << "bar" }})
|
54
|
+
|
55
|
+
expect {
|
56
|
+
subject.eval(%{FileUtils.cp('/bar.txt', '/baz.txt')})
|
57
|
+
}.to_not raise_error(Sandbox::SandboxException, /NoMethodError/)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "sandboxes global variables" do
|
61
|
+
subject.eval('$0').should == '(sandbox)'
|
62
|
+
/(.)(.)(.)/.match('abc')
|
63
|
+
subject.eval('$TEST = "TEST"; $TEST').should == 'TEST'
|
64
|
+
subject.eval('/(.)(.)(.)/.match("def"); $2').should == 'e'
|
65
|
+
$2.should == 'b'
|
66
|
+
subject.eval('$TEST').should == 'TEST'
|
67
|
+
subject.eval('$2').should == 'e'
|
68
|
+
/(.)(.)(.)/.match('ghi')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe ".current" do
|
73
|
+
it "should not return a current sandbox outside a sandbox" do
|
74
|
+
Sandbox.current.should be_nil
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should return the current sandbox inside a sandbox" do
|
78
|
+
pending do
|
79
|
+
sandbox = Sandbox.new
|
80
|
+
sandbox.ref(Sandbox)
|
81
|
+
sandbox.eval('Sandbox.current').should == sandbox
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "#eval" do
|
87
|
+
subject { Sandbox.new }
|
88
|
+
|
89
|
+
it "should allow a range of common operations" do
|
90
|
+
operations = <<-OPS
|
91
|
+
1 + 1
|
92
|
+
'foo'.chomp
|
93
|
+
'foo'
|
94
|
+
OPS
|
95
|
+
subject.eval(operations).should == 'foo'
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should persist state between evaluations" do
|
99
|
+
subject.eval('o = Object.new')
|
100
|
+
subject.eval('o').should_not be_nil
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should be able to define a new class in the sandbox" do
|
104
|
+
result = subject.eval('Foo = Struct.new(:foo); struct = Foo.new("baz"); struct.foo')
|
105
|
+
result.should == 'baz'
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should be able to use a class across invocations" do
|
109
|
+
# Return nil, because the environment doesn't know "Foo"
|
110
|
+
subject.eval('Foo = Struct.new(:foo); nil')
|
111
|
+
subject.eval('struct = Foo.new("baz"); nil')
|
112
|
+
subject.eval('struct.foo').should == 'baz'
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "communication between sandbox and environment" do
|
116
|
+
it "should be possible to pass data from the box to the environment" do
|
117
|
+
Foo = Struct.new(:foo)
|
118
|
+
subject.ref(Foo)
|
119
|
+
struct = subject.eval('struct = Foo.new')
|
120
|
+
subject.eval('struct.foo = "baz"')
|
121
|
+
struct.foo.should == 'baz'
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should be possible to pass data from the environment to the box" do
|
125
|
+
Foo = Struct.new(:foo)
|
126
|
+
subject.ref(Foo)
|
127
|
+
struct = subject.eval('struct = Foo.new')
|
128
|
+
struct.foo = 'baz'
|
129
|
+
subject.eval('struct.foo').should == 'baz'
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should be able to pass large object data from the box to the environment" do
|
133
|
+
expect {
|
134
|
+
subject.eval %{
|
135
|
+
(0..1000).to_a.inject({}) {|h,i| h[i] = "HELLO WORLD"; h }
|
136
|
+
}
|
137
|
+
}.to_not raise_error(Sandbox::SandboxException)
|
138
|
+
|
139
|
+
expect {
|
140
|
+
subject.eval %{'RUBY'*100}
|
141
|
+
}.to_not raise_error(Sandbox::SandboxException)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe "#import" do
|
147
|
+
subject { Sandbox.new }
|
148
|
+
|
149
|
+
it "should be able to call a referenced namespaced module method" do
|
150
|
+
Foo = Class.new
|
151
|
+
Foo::Bar = Module.new do
|
152
|
+
def baz
|
153
|
+
'baz'
|
154
|
+
end
|
155
|
+
module_function :baz
|
156
|
+
end
|
157
|
+
|
158
|
+
subject.import(Foo::Bar)
|
159
|
+
subject.eval('Foo::Bar.baz').should == 'baz'
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should be able to include a module from the environment" do
|
163
|
+
Foo = Module.new do
|
164
|
+
def baz
|
165
|
+
'baz'
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
subject.import(Foo)
|
170
|
+
subject.eval("class Bar; include Foo; end; nil")
|
171
|
+
subject.eval('Bar.new.baz').should == 'baz'
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should be able to copy instance methods from a module that uses module_function" do
|
175
|
+
Foo = Module.new do
|
176
|
+
def baz; 'baz'; end
|
177
|
+
|
178
|
+
module_function :baz
|
179
|
+
end
|
180
|
+
|
181
|
+
subject.import Foo
|
182
|
+
subject.eval('Foo.baz').should == 'baz'
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe "#ref" do
|
187
|
+
subject { Sandbox.new }
|
188
|
+
|
189
|
+
it "should be possible to reference a class defined outside the box" do
|
190
|
+
Foo = Class.new
|
191
|
+
subject.ref(Foo)
|
192
|
+
subject.eval('Foo.new').should be_an_instance_of(Foo)
|
193
|
+
end
|
194
|
+
|
195
|
+
it "should be possible to change the class after the ref" do
|
196
|
+
Foo = Class.new
|
197
|
+
subject.ref(Foo)
|
198
|
+
def Foo.foo; 'baz'; end
|
199
|
+
subject.eval('Foo.foo').should == 'baz'
|
200
|
+
end
|
201
|
+
|
202
|
+
it "should be possible to dynamically add a class method after the ref" do
|
203
|
+
Foo = Class.new
|
204
|
+
subject.ref(Foo)
|
205
|
+
Foo.class_eval('def Foo.foo; "baz"; end')
|
206
|
+
subject.eval('Foo.foo').should == 'baz'
|
207
|
+
end
|
208
|
+
|
209
|
+
it "should be possible to dynamically add a class method after the ref" do
|
210
|
+
Foo = Class.new
|
211
|
+
subject.ref(Foo)
|
212
|
+
Foo.instance_eval('def Foo.foo; "baz"; end')
|
213
|
+
subject.eval('Foo.foo').should == 'baz'
|
214
|
+
end
|
215
|
+
|
216
|
+
it "should be possible to call a method on the class that receives a block" do
|
217
|
+
Foo = Class.new do
|
218
|
+
def self.bar
|
219
|
+
yield
|
220
|
+
end
|
221
|
+
end
|
222
|
+
subject.ref(Foo)
|
223
|
+
subject.eval(%{Foo.bar { "baz" }}).should == 'baz'
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
foo
|
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jruby_sandbox
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: java
|
7
|
+
authors:
|
8
|
+
- Dray Lacy
|
9
|
+
- Eric Allam
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2011-09-15 00:00:00.000000000Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: fakefs
|
17
|
+
requirement: &70097860842780 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *70097860842780
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: rake
|
28
|
+
requirement: &70097860842160 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *70097860842160
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: rake-compiler
|
39
|
+
requirement: &70097860841480 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
type: :development
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *70097860841480
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rspec
|
50
|
+
requirement: &70097860840900 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
type: :development
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: *70097860840900
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: yard
|
61
|
+
requirement: &70097860840300 !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
type: :development
|
68
|
+
prerelease: false
|
69
|
+
version_requirements: *70097860840300
|
70
|
+
description: A version of _why's Freaky Freaky Sandbox for JRuby.
|
71
|
+
email:
|
72
|
+
- dray@envylabs.com
|
73
|
+
- eric@envylabs.com
|
74
|
+
executables: []
|
75
|
+
extensions: []
|
76
|
+
extra_rdoc_files: []
|
77
|
+
files:
|
78
|
+
- .gitignore
|
79
|
+
- .rvmrc
|
80
|
+
- Gemfile
|
81
|
+
- Gemfile.lock
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- ext/java/sandbox/BoxedClass.java
|
85
|
+
- ext/java/sandbox/SandboxFull.java
|
86
|
+
- ext/java/sandbox/SandboxModule.java
|
87
|
+
- ext/java/sandbox/SandboxProfile.java
|
88
|
+
- ext/java/sandbox/SandboxService.java
|
89
|
+
- jruby_sandbox.gemspec
|
90
|
+
- lib/sandbox.rb
|
91
|
+
- lib/sandbox/prelude.rb
|
92
|
+
- lib/sandbox/version.rb
|
93
|
+
- spec/sandbox_spec.rb
|
94
|
+
- spec/support/foo.txt
|
95
|
+
- lib/sandbox/sandbox.jar
|
96
|
+
homepage: http://github.com/omghax/jruby_sandbox
|
97
|
+
licenses: []
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ! '>='
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
110
|
+
requirements:
|
111
|
+
- - ! '>='
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
requirements: []
|
115
|
+
rubyforge_project: jruby_sandbox
|
116
|
+
rubygems_version: 1.8.10
|
117
|
+
signing_key:
|
118
|
+
specification_version: 3
|
119
|
+
summary: Sandbox support for JRuby
|
120
|
+
test_files:
|
121
|
+
- spec/sandbox_spec.rb
|
122
|
+
- spec/support/foo.txt
|