sandrbox 0.0.2

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/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :development do
4
+ gem "mocha"
5
+ gem "rake"
6
+ gem "rspec"
7
+ gem "bundler"
8
+ gem "jeweler"
9
+ gem "yard"
10
+ gem "redcarpet", '1.17.2'
11
+ gem 'github-markup'
12
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,41 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.3)
5
+ git (1.2.5)
6
+ github-markup (0.7.1)
7
+ jeweler (1.8.3)
8
+ bundler (~> 1.0)
9
+ git (>= 1.2.5)
10
+ rake
11
+ rdoc
12
+ json (1.6.6)
13
+ metaclass (0.0.1)
14
+ mocha (0.10.5)
15
+ metaclass (~> 0.0.1)
16
+ rake (0.9.2.2)
17
+ rdoc (3.12)
18
+ json (~> 1.4)
19
+ redcarpet (1.17.2)
20
+ rspec (2.9.0)
21
+ rspec-core (~> 2.9.0)
22
+ rspec-expectations (~> 2.9.0)
23
+ rspec-mocks (~> 2.9.0)
24
+ rspec-core (2.9.0)
25
+ rspec-expectations (2.9.0)
26
+ diff-lcs (~> 1.1.3)
27
+ rspec-mocks (2.9.0)
28
+ yard (0.7.5)
29
+
30
+ PLATFORMS
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ bundler
35
+ github-markup
36
+ jeweler
37
+ mocha
38
+ rake
39
+ redcarpet (= 1.17.2)
40
+ rspec
41
+ yard
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Josh Symonds
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.markdown ADDED
@@ -0,0 +1,45 @@
1
+ # Sandrbox
2
+
3
+ Sandrbox allows you to execute arbitrary Ruby code while being assured it won't destroy your life (or your server). It's intended to be small, fast, and secure. I built it to replace TryRuby's really slow Ruby sandbox, since I wanted something faster.
4
+
5
+ Note that while I made a concentrated effort to make this secure, it's still possible it's not. I wouldn't run this code outside of a secure prison of some sort, and definitely not on anything connected to a database whose data you care about. (I intend to run this on Heroku without a database.)
6
+
7
+ ## Set It Up
8
+
9
+ I automatically remove all the bad methods and classes I can think of. But maybe you need more:
10
+
11
+ ```ruby
12
+ Sandrbox.configure do |config|
13
+ config.bad_classes << [:Rails]
14
+ config.bad_classes << [:ActiveRecord]
15
+ end
16
+ ```
17
+
18
+ ## How To Use It
19
+
20
+ ```ruby
21
+ require 'sandrbox'
22
+
23
+ Sandrbox.perform(['a = 1']).output # => [1]
24
+ Sandrbox.perform(['a = 1', 'a = a + a', 'a ** a']).output # => [1, 2, 4]
25
+ Sandrbox.perform(['a = 1', 'a = a + a', 'a ** b']).output # => [1, 2, "NameError: undefined local variable or method `b' for main:Object"]
26
+
27
+ Sandrbox.perform(['`rm -rf /`']).output # => ["NameError: undefined local variable or method ``' for Kernel:Module"]
28
+ Sandrbox.perform(['exec("rm -rf /")']).output # => ["NameError: undefined local variable or method `exec' for main:Object"]
29
+ Sandrbox.perform(['Kernel.exec("rm -rf /")']).output # => ["NameError: undefined local variable or method `exec' for Kernel:Module"]
30
+
31
+ Sandrbox.perform(['require "open3"']).output # => ["NameError: undefined local variable or method `require' for main:Object"]
32
+
33
+ Sandrbox.perform(['class Foo', 'def test', '"hi"', 'end', 'end']).output # => [nil]
34
+ Sandrbox.perform(['class Foo', 'def test', '"hi"', 'end', 'end', 'Foo.new.test']).output # => [nil, "hi"]
35
+ Sandrbox.perform(['Foo.new.test']).output # => ["NameError: uninitialized constant Foo"] Each perform is independent of previous performs
36
+
37
+ Sandrbox.perform(['class Foo']).output # => []
38
+ Sandrbox.perform(['class Foo']).complete? # => false
39
+ Sandrbox.perform(['class Foo']).indent_level # => 1
40
+ Sandrbox.perform(['class Foo']).indent_character # => "class"
41
+ ```
42
+
43
+ ## Copyright
44
+
45
+ Copyright (c) 2012 Josh Symonds. See LICENSE.txt for further details.
data/Rakefile ADDED
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "sandrbox"
18
+ gem.homepage = "http://github.com/Veraticus/Sandrbox"
19
+ gem.license = "MIT"
20
+ gem.summary = 'A sanitizing sandbox for executing Ruby code'
21
+ gem.description = 'A sandbox for that tries to change all Ruby code executed to be safe and non-destructive, both to the filesystem and the currently running process'
22
+ gem.email = "josh@joshsymonds.com"
23
+ gem.authors = ["Josh Symonds"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ desc "Open an irb session preloaded with this library"
40
+ task :cons do
41
+ sh "irb -rubygems -I lib -r sandrbox.rb"
42
+ end
43
+
44
+ task :default => :spec
data/Sandrbox.gemspec ADDED
@@ -0,0 +1,78 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "sandrbox"
8
+ s.version = "0.0.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Josh Symonds"]
12
+ s.date = "2012-03-30"
13
+ s.description = "A sandbox for that tries to change all Ruby code executed to be safe and non-destructive, both to the filesystem and the currently running process"
14
+ s.email = "josh@joshsymonds.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.markdown"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE.txt",
24
+ "README.markdown",
25
+ "Rakefile",
26
+ "Sandrbox.gemspec",
27
+ "VERSION",
28
+ "lib/sandrbox.rb",
29
+ "lib/sandrbox/config.rb",
30
+ "lib/sandrbox/options/option.rb",
31
+ "lib/sandrbox/response.rb",
32
+ "lib/sandrbox/value.rb",
33
+ "spec/sandrbox/config_spec.rb",
34
+ "spec/sandrbox/response_spec.rb",
35
+ "spec/sandrbox/value_spec.rb",
36
+ "spec/sandrbox_spec.rb",
37
+ "spec/spec_helper.rb"
38
+ ]
39
+ s.homepage = "http://github.com/Veraticus/Sandrbox"
40
+ s.licenses = ["MIT"]
41
+ s.require_paths = ["lib"]
42
+ s.rubygems_version = "1.8.19"
43
+ s.summary = "A sanitizing sandbox for executing Ruby code"
44
+
45
+ if s.respond_to? :specification_version then
46
+ s.specification_version = 3
47
+
48
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
49
+ s.add_development_dependency(%q<mocha>, [">= 0"])
50
+ s.add_development_dependency(%q<rake>, [">= 0"])
51
+ s.add_development_dependency(%q<rspec>, [">= 0"])
52
+ s.add_development_dependency(%q<bundler>, [">= 0"])
53
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
54
+ s.add_development_dependency(%q<yard>, [">= 0"])
55
+ s.add_development_dependency(%q<redcarpet>, ["= 1.17.2"])
56
+ s.add_development_dependency(%q<github-markup>, [">= 0"])
57
+ else
58
+ s.add_dependency(%q<mocha>, [">= 0"])
59
+ s.add_dependency(%q<rake>, [">= 0"])
60
+ s.add_dependency(%q<rspec>, [">= 0"])
61
+ s.add_dependency(%q<bundler>, [">= 0"])
62
+ s.add_dependency(%q<jeweler>, [">= 0"])
63
+ s.add_dependency(%q<yard>, [">= 0"])
64
+ s.add_dependency(%q<redcarpet>, ["= 1.17.2"])
65
+ s.add_dependency(%q<github-markup>, [">= 0"])
66
+ end
67
+ else
68
+ s.add_dependency(%q<mocha>, [">= 0"])
69
+ s.add_dependency(%q<rake>, [">= 0"])
70
+ s.add_dependency(%q<rspec>, [">= 0"])
71
+ s.add_dependency(%q<bundler>, [">= 0"])
72
+ s.add_dependency(%q<jeweler>, [">= 0"])
73
+ s.add_dependency(%q<yard>, [">= 0"])
74
+ s.add_dependency(%q<redcarpet>, ["= 1.17.2"])
75
+ s.add_dependency(%q<github-markup>, [">= 0"])
76
+ end
77
+ end
78
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.2
@@ -0,0 +1,57 @@
1
+ require 'sandrbox/options/option'
2
+
3
+ module Sandrbox
4
+
5
+ module Config
6
+ extend self
7
+ extend Options
8
+
9
+ option :bad_methods, :default => [
10
+ [:Object, :abort],
11
+ [:Kernel, :abort],
12
+ [:Object, :autoload],
13
+ [:Kernel, :autoload],
14
+ [:Object, :autoload?],
15
+ [:Kernel, :autoload?],
16
+ [:Object, :callcc],
17
+ [:Kernel, :callcc],
18
+ [:Object, :exit],
19
+ [:Kernel, :exit],
20
+ [:Object, :exit!],
21
+ [:Kernel, :exit!],
22
+ [:Object, :at_exit],
23
+ [:Kernel, :at_exit],
24
+ [:Object, :exec],
25
+ [:Kernel, :exec],
26
+ [:Object, :fork],
27
+ [:Kernel, :fork],
28
+ [:Object, :load],
29
+ [:Kernel, :load],
30
+ [:Object, :open],
31
+ [:Kernel, :open],
32
+ [:Object, :set_trace_func],
33
+ [:Kernel, :set_trace_func],
34
+ [:Object, :spawn],
35
+ [:Kernel, :spawn],
36
+ [:Object, :syscall],
37
+ [:Kernel, :syscall],
38
+ [:Object, :system],
39
+ [:Kernel, :system],
40
+ [:Object, :test],
41
+ [:Kernel, :test],
42
+ [:Object, :remove_method],
43
+ [:Kernel, :remove_method],
44
+ [:Object, :require],
45
+ [:Kernel, :require],
46
+ [:Object, :require_relative],
47
+ [:Kernel, :require_relative],
48
+ [:Object, :undef_method],
49
+ [:Kernel, :undef_method],
50
+ [:Object, "`".to_sym],
51
+ [:Kernel, "`".to_sym],
52
+ [:Class, "`".to_sym]
53
+ ]
54
+ option :bad_constants, :default => [:Continuation, :Open3, :File, :Dir, :IO, :Sandrbox, :Process, :Thread, :Fiber, :Gem, :Net, :ThreadGroup, :SystemExit, :SignalException, :Interrupt, :FileTest, :Signal]
55
+
56
+ end
57
+ end
@@ -0,0 +1,70 @@
1
+ # Shamelessly stolen from Mongoid!
2
+ module Sandrbox #:nodoc
3
+ module Config
4
+
5
+ # Encapsulates logic for setting options.
6
+ module Options
7
+
8
+ # Get the defaults or initialize a new empty hash.
9
+ #
10
+ # @example Get the defaults.
11
+ # options.defaults
12
+ #
13
+ # @return [ Hash ] The default options.
14
+ def defaults
15
+ @defaults ||= {}
16
+ end
17
+
18
+ # Define a configuration option with a default.
19
+ #
20
+ # @example Define the option.
21
+ # Options.option(:persist_in_safe_mode, :default => false)
22
+ #
23
+ # @param [ Symbol ] name The name of the configuration option.
24
+ # @param [ Hash ] options Extras for the option.
25
+ #
26
+ # @option options [ Object ] :default The default value.
27
+ def option(name, options = {})
28
+ defaults[name] = settings[name] = options[:default]
29
+
30
+ class_eval <<-RUBY
31
+ def #{name}
32
+ settings[#{name.inspect}]
33
+ end
34
+
35
+ def #{name}=(value)
36
+ settings[#{name.inspect}] = value
37
+ end
38
+
39
+ def #{name}?
40
+ #{name}
41
+ end
42
+
43
+ def reset_#{name}
44
+ settings[#{name.inspect}] = defaults[#{name.inspect}]
45
+ end
46
+ RUBY
47
+ end
48
+
49
+ # Reset the configuration options to the defaults.
50
+ #
51
+ # @example Reset the configuration options.
52
+ # config.reset
53
+ #
54
+ # @return [ Hash ] The defaults.
55
+ def reset
56
+ settings.replace(defaults)
57
+ end
58
+
59
+ # Get the settings or initialize a new empty hash.
60
+ #
61
+ # @example Get the settings.
62
+ # options.settings
63
+ #
64
+ # @return [ Hash ] The setting options.
65
+ def settings
66
+ @settings ||= {}
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,101 @@
1
+ module Sandrbox
2
+ class Response
3
+ attr_accessor :array, :compressed_array, :expanded_array, :indents, :results, :old_constants
4
+ attr_accessor :class_count, :module_count, :def_count, :end_count, :do_count, :left_curly_count, :right_curly_count, :left_bracket_count, :right_bracket_count
5
+
6
+ def initialize(array)
7
+ self.array = array
8
+ [:expanded_array, :compressed_array, :indents, :results].each do |arr|
9
+ self.send("#{arr}=".to_sym, [])
10
+ end
11
+ [:class, :module, :def, :end, :do, :left_curly, :right_curly, :left_bracket, :right_bracket].each do |count|
12
+ self.send("#{count}_count=".to_sym, 0)
13
+ end
14
+ expand
15
+ compress
16
+ end
17
+
18
+ def expand
19
+ self.array.each do |line|
20
+ next if line.empty?
21
+ self.expanded_array << uncomment(line).split(';').collect(&:strip)
22
+ end
23
+ self.expanded_array.flatten!
24
+ end
25
+
26
+ def compress
27
+ self.expanded_array.each do |line|
28
+ uncommented_line = uncomment(line)
29
+ ending = nil
30
+
31
+ [:class, :module, :def, :end, :do].each do |count|
32
+ if uncommented_line =~ /\s*#{count.to_s}\s*/
33
+ self.send("#{count}_count=".to_sym, self.send("#{count}_count".to_sym) + 1)
34
+ self.indents.push(count.to_s) unless count == :end
35
+ ending = self.indents.pop if count == :end
36
+ end
37
+ end
38
+
39
+ {:left_curly => ['{', '}'], :right_curly => ['}', '{'], :left_bracket => ['[', ']'], :right_bracket => [']', '[']}.each do |name, sym|
40
+ if uncommented_line.count(sym.first) > uncommented_line.count(sym.last)
41
+ self.send("#{name}_count=".to_sym, self.send("#{name}_count".to_sym) + 1)
42
+ self.indents.push(sym.first) if name.to_s.include?('left')
43
+ ending = self.indents.pop if name.to_s.include?('right')
44
+ end
45
+ end
46
+
47
+ if ending
48
+ self.compressed_array.last << "#{uncommented_line}#{semicolon(ending)}"
49
+ elsif indent_level == 1 || self.compressed_array.empty?
50
+ self.compressed_array << "#{uncommented_line}#{semicolon}"
51
+ elsif indent_level > 1
52
+ self.compressed_array.last << "#{uncommented_line}#{semicolon}"
53
+ else
54
+ self.compressed_array << uncommented_line
55
+ end
56
+ end
57
+
58
+ return evaluate if complete?
59
+ end
60
+
61
+ def evaluate
62
+ preserve_namespace
63
+ self.compressed_array.each_with_index {|line, line_no| self.results << Sandrbox::Value.new(line, line_no)}
64
+ restore_namespace
65
+ end
66
+
67
+ def semicolon(char = nil)
68
+ char ||= indent_character
69
+ (char == '{' || char == '[') ? '' : ';'
70
+ end
71
+
72
+ def uncomment(line)
73
+ line.split('#').first.strip
74
+ end
75
+
76
+ def indent_level
77
+ self.indents.count
78
+ end
79
+
80
+ def indent_character
81
+ self.indents.last
82
+ end
83
+
84
+ def complete?
85
+ self.indents.empty?
86
+ end
87
+
88
+ def output
89
+ results.collect(&:to_s)
90
+ end
91
+
92
+ def preserve_namespace
93
+ self.old_constants = Object.constants
94
+ end
95
+
96
+ def restore_namespace
97
+ (Object.constants - self.old_constants).each {|bad_constant| Object.send(:remove_const, bad_constant)}
98
+ end
99
+
100
+ end
101
+ end
@@ -0,0 +1,90 @@
1
+ module Sandrbox
2
+ class Value
3
+ attr_accessor :line, :line_no, :result, :time, :unbound_methods, :unbound_constants
4
+
5
+ def initialize(line, line_no)
6
+ self.unbound_methods = []
7
+ self.unbound_constants = []
8
+ self.line = line
9
+ self.line_no = line_no
10
+ evaluate
11
+ end
12
+
13
+ def evaluate
14
+ t = Thread.new do
15
+ $SAFE = 2
16
+ begin
17
+ Timeout::timeout(0.5) do
18
+ Sandrbox.config.bad_methods.each {|meth| remove_method(meth.first, meth.last)}
19
+ Sandrbox.config.bad_constants.each {|const| remove_constant(const)}
20
+ self.result = eval(line, TOPLEVEL_BINDING, "sandrbox", line_no)
21
+ end
22
+ rescue Exception => e
23
+ self.result = "#{e.class}: #{e.to_s}"
24
+ ensure
25
+ restore_constants
26
+ restore_methods
27
+ end
28
+ end
29
+
30
+ timeout = t.join(3)
31
+ self.result = "SandrboxError: execution expired" if timeout.nil?
32
+
33
+ self
34
+ end
35
+
36
+ def to_s
37
+ self.result
38
+ end
39
+
40
+ private
41
+
42
+ def remove_method(klass, method)
43
+ const = Object.const_get(klass.to_s)
44
+ if const.methods.include?(method) || const.instance_methods.include?(method)
45
+ self.unbound_methods << [const, const.method(method).unbind]
46
+ metaclass = class << const; self; end
47
+
48
+ message = if const == Object
49
+ "undefined local variable or method `#{method}' for main:Object"
50
+ else
51
+ "undefined local variable or method `#{method}' for #{klass}:#{const.class}"
52
+ end
53
+
54
+ metaclass.send(:define_method, method) do |*args|
55
+ raise NameError, message
56
+ end
57
+
58
+ const.send(:define_method, method) do |*args|
59
+ raise NameError, message
60
+ end
61
+ end
62
+ end
63
+
64
+ def restore_methods
65
+ self.unbound_methods.each do |unbound|
66
+ klass = unbound.first
67
+ method = unbound.last
68
+
69
+ metaclass = class << klass; self; end
70
+
71
+ metaclass.send(:define_method, method.name) do |*args|
72
+ method.bind(klass).call(*args)
73
+ end
74
+
75
+ klass.send(:define_method, method.name) do |*args|
76
+ method.bind(klass).call(*args)
77
+ end
78
+ end
79
+ end
80
+
81
+ def remove_constant(constant)
82
+ self.unbound_constants << Object.send(:remove_const, constant) if Object.const_defined?(constant)
83
+ end
84
+
85
+ def restore_constants
86
+ self.unbound_constants.each {|const| Object.const_set(const.to_s.to_sym, const) unless Object.const_defined?(const.to_s.to_sym)}
87
+ end
88
+
89
+ end
90
+ end
data/lib/sandrbox.rb ADDED
@@ -0,0 +1,20 @@
1
+ require 'irb'
2
+ require 'timeout'
3
+
4
+ require 'sandrbox/response'
5
+ require 'sandrbox/value'
6
+ require 'sandrbox/config'
7
+
8
+ module Sandrbox
9
+ extend self
10
+
11
+ def configure
12
+ block_given? ? yield(Sandrbox::Config) : Sandrbox::Config
13
+ end
14
+ alias :config :configure
15
+
16
+ def perform(array)
17
+ Sandrbox::Response.new(array)
18
+ end
19
+
20
+ end
@@ -0,0 +1,5 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe "Sandrbox::Config" do
4
+
5
+ end
@@ -0,0 +1,105 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe "Sandrbox::Response" do
4
+ it 'initializes values correctly' do
5
+ @response = Sandrbox::Response.new([])
6
+
7
+ @response.class_count.should == 0
8
+ @response.module_count.should == 0
9
+ @response.def_count.should == 0
10
+ @response.end_count.should == 0
11
+ @response.do_count.should == 0
12
+ @response.left_curly_count.should == 0
13
+ @response.right_curly_count.should == 0
14
+ @response.compressed_array.should == []
15
+ end
16
+
17
+ it 'expands semicolons into new lines' do
18
+ Sandrbox::Response.new(['class Test; def foo; "hi"; end; end']).expanded_array == ['class Test', 'def foo', '"hi"', 'end', 'end']
19
+ end
20
+
21
+ it 'detects class correctly' do
22
+ Sandrbox::Response.new(['class Test < ActiveRecord::Base']).class_count.should == 1
23
+ end
24
+
25
+ it 'detects module correctly' do
26
+ Sandrbox::Response.new([' module Enumerable']).module_count.should == 1
27
+ end
28
+
29
+ it 'detects def correctly' do
30
+ Sandrbox::Response.new(['def method(test)']).def_count.should == 1
31
+ end
32
+
33
+ it 'detects end correctly' do
34
+ Sandrbox::Response.new([' end']).end_count.should == 1
35
+ end
36
+
37
+ it 'detects do correctly' do
38
+ Sandrbox::Response.new(['test.each do |foo, wee|']).do_count.should == 1
39
+ end
40
+
41
+ it 'detects left curly correctly' do
42
+ Sandrbox::Response.new(['test.each { |foo| ']).left_curly_count.should == 1
43
+ end
44
+
45
+ it 'detects right curly correctly' do
46
+ Sandrbox::Response.new([' } ']).right_curly_count.should == 1
47
+ end
48
+
49
+ it 'detects left bracket correctly' do
50
+ Sandrbox::Response.new(['[ "test", ']).left_bracket_count.should == 1
51
+ end
52
+
53
+ it 'detects right curly correctly' do
54
+ Sandrbox::Response.new(['] ']).right_bracket_count.should == 1
55
+ end
56
+
57
+ it 'compresses multiline statements into one line' do
58
+ Sandrbox::Response.new(['class Test', 'def foo', '"hi"', 'end', 'end']).compressed_array.should == ['class Test;def foo;"hi";end;end;']
59
+ end
60
+
61
+ it 'ignores comments in parsing' do
62
+ response = Sandrbox::Response.new([]).uncomment('def method(test) # end class module').should == 'def method(test)'
63
+ end
64
+
65
+ it 'correctly determines a line is complete for def, class, module, do and end' do
66
+ Sandrbox::Response.new(['class Test; def foo; "hi"; end; end']).should be_complete
67
+ Sandrbox::Response.new(['class Test', 'def foo', '"hi"', 'end', 'end']).should be_complete
68
+ Sandrbox::Response.new(['module Wee', '[1, 2, 3].each do |num|', '"#{num}"', 'end', 'end']).should be_complete
69
+ end
70
+
71
+ it 'correctly determines a line is incomplete for def, class, module, do and end' do
72
+ Sandrbox::Response.new(['class Test; def foo; "hi"; end']).should_not be_complete
73
+ Sandrbox::Response.new(['class Test', 'def foo', '"hi"', 'end']).should_not be_complete
74
+ Sandrbox::Response.new(['module Wee', '[1, 2, 3].each do |num|', '"#{num}"']).should_not be_complete
75
+ end
76
+
77
+ it 'correctly determines indent level' do
78
+ Sandrbox::Response.new(['class Test; def foo; "hi"; end']).indent_level.should == 1
79
+ Sandrbox::Response.new(['class Test', 'def foo', '"hi"', 'end']).indent_level.should == 1
80
+ Sandrbox::Response.new(['module Wee', '[1, 2, 3].each do |num|', '"#{num}"']).indent_level.should == 2
81
+ Sandrbox::Response.new(['[1, 2, 3].each { |num|', '']).indent_level.should == 1
82
+ Sandrbox::Response.new(['module Test', 'class Weeha', 'def Foo', '[ "test", ']).indent_level.should == 4
83
+ end
84
+
85
+ it 'correctly determines indent character' do
86
+ Sandrbox::Response.new(['class Test; def foo; "hi"; end']).indent_character.should == 'class'
87
+ Sandrbox::Response.new(['class Test', 'def foo', '"hi"', 'end']).indent_character.should == 'class'
88
+ Sandrbox::Response.new(['module Wee', '[1, 2, 3].each do |num|', '"#{num}"']).indent_character.should == 'do'
89
+ Sandrbox::Response.new(['[1, 2, 3].each { |num|', '']).indent_character.should == '{'
90
+ Sandrbox::Response.new(['module Test', 'class Weeha', 'def Foo', '[ "test", ']).indent_character.should == '['
91
+ end
92
+
93
+ it 'does not make an indent character if indents are on the same line' do
94
+ Sandrbox::Response.new(['[1, 2, 3]']).indent_character.should == nil
95
+ Sandrbox::Response.new(['{:test => "foo"}']).indent_character.should == nil
96
+ end
97
+
98
+ it 'does make an indent character if indents are on the same line but are unmatched' do
99
+ Sandrbox::Response.new(['{:test => "foo"}, {:test => "wee"']).indent_character.should == '{'
100
+ end
101
+
102
+ it 'does not return a negative indent level' do
103
+ Sandrbox::Response.new(['end', 'end', 'end']).indent_level.should == 0
104
+ end
105
+ end
File without changes
@@ -0,0 +1,85 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Sandrbox" do
4
+
5
+ it 'performs arbitrary correct irb commands' do
6
+ Sandrbox.perform(['a = 1 + 1', 'a + a', 'a * a']).output.should == [2, 4, 4]
7
+ end
8
+
9
+ it 'returns syntax errors immediately' do
10
+ Sandrbox.perform(['a = 1 + 1', 'b', 'a * a']).output.should == [2, "NameError: undefined local variable or method `b' for main:Object", 4]
11
+ end
12
+
13
+ it 'allows constants to be used after uninitializing them' do
14
+ Sandrbox.perform(['a = 1 + 1'])
15
+ lambda {Object.const_get(:File)}.should_not raise_error
16
+ end
17
+
18
+ it 'allows methods to be called after removing them' do
19
+ Sandrbox.perform(['a = 1 + 1'])
20
+ Kernel.methods.should include(:exit)
21
+ end
22
+
23
+ it 'only waits half a second for each command' do
24
+ Sandrbox.perform(['sleep 5']).output.should == ["Timeout::Error: execution expired"]
25
+ end
26
+
27
+ it 'does multiline class definitions correctly' do
28
+ Sandrbox.perform(['class Foo', 'def test', '"hi"', 'end', 'end', 'Foo.new.test']).output.should == [nil, "hi"]
29
+ end
30
+
31
+ it 'removes previous class definitions and methods between calls' do
32
+ Sandrbox.perform(['class Foo', 'def test', '"hi"', 'end', 'end'])
33
+ Sandrbox.perform(['Foo.new.test']).output.should == ["NameError: uninitialized constant Foo"]
34
+ end
35
+
36
+ context 'unsafe commands' do
37
+ it 'does not exit' do
38
+ Sandrbox.perform(['exit']).output.should == ["NameError: undefined local variable or method `exit' for main:Object"]
39
+ end
40
+
41
+ it 'does not exit for kernel' do
42
+ Sandrbox.perform(['Kernel.exit']).output.should == ["NameError: undefined local variable or method `exit' for Kernel:Module"]
43
+ end
44
+
45
+ it 'does not exec' do
46
+ Sandrbox.perform(['exec("ps")']).output.should == ["NameError: undefined local variable or method `exec' for main:Object"]
47
+ end
48
+
49
+ it 'does not exec for kernel' do
50
+ Sandrbox.perform(['Kernel.exec("ps")']).output.should == ["NameError: undefined local variable or method `exec' for Kernel:Module"]
51
+ end
52
+
53
+ it 'does not `' do
54
+ Sandrbox.perform(['`ls`']).output.should == ["NameError: undefined local variable or method ``' for main:Object"]
55
+ end
56
+
57
+ it 'does not implement File' do
58
+ Sandrbox.perform(['File']).output.should == ["NameError: uninitialized constant File"]
59
+ end
60
+
61
+ it 'does not implement Dir' do
62
+ Sandrbox.perform(['Dir']).output.should == ["NameError: uninitialized constant Dir"]
63
+ end
64
+
65
+ it 'does not implement IO' do
66
+ Sandrbox.perform(['IO']).output.should == ["NameError: uninitialized constant IO"]
67
+ end
68
+
69
+ it 'does not implement Open3' do
70
+ Sandrbox.perform(['Open3']).output.should == ["NameError: uninitialized constant Open3"]
71
+ end
72
+
73
+ it 'does not implement Open3 even after requiring it' do
74
+ Sandrbox.perform(['require "open3"', 'Open3']).output.should == ["NameError: undefined local variable or method `require' for main:Object", "NameError: uninitialized constant Open3"]
75
+ end
76
+
77
+ it 'does not allow you to manually call protected Sandrbox methods' do
78
+ Sandrbox.perform(['Sandrbox Sandrbox.inspect']).output.should == ["NameError: uninitialized constant Sandrbox"]
79
+ end
80
+
81
+ it 'does not allow you to manually call children of removed classes' do
82
+ Sandrbox.perform(['Sandrbox Sandrbox::Config.inspect']).output.should == ["NameError: uninitialized constant Sandrbox"]
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,16 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+
4
+ require 'rspec'
5
+ require 'sandrbox'
6
+ require 'mocha'
7
+
8
+ # Requires supporting files with custom matchers and macros, etc,
9
+ # in ./support/ and its subdirectories.
10
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
11
+
12
+ RSpec.configure do |config|
13
+ config.mock_with(:mocha)
14
+ config.backtrace_clean_patterns = []
15
+ config.before(:each) {Sandrbox.config.reset_bad_methods}
16
+ end
metadata ADDED
@@ -0,0 +1,197 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sandrbox
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Josh Symonds
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-30 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: mocha
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: bundler
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: jeweler
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: yard
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: redcarpet
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - '='
116
+ - !ruby/object:Gem::Version
117
+ version: 1.17.2
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - '='
124
+ - !ruby/object:Gem::Version
125
+ version: 1.17.2
126
+ - !ruby/object:Gem::Dependency
127
+ name: github-markup
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ description: A sandbox for that tries to change all Ruby code executed to be safe
143
+ and non-destructive, both to the filesystem and the currently running process
144
+ email: josh@joshsymonds.com
145
+ executables: []
146
+ extensions: []
147
+ extra_rdoc_files:
148
+ - LICENSE.txt
149
+ - README.markdown
150
+ files:
151
+ - .document
152
+ - Gemfile
153
+ - Gemfile.lock
154
+ - LICENSE.txt
155
+ - README.markdown
156
+ - Rakefile
157
+ - Sandrbox.gemspec
158
+ - VERSION
159
+ - lib/sandrbox.rb
160
+ - lib/sandrbox/config.rb
161
+ - lib/sandrbox/options/option.rb
162
+ - lib/sandrbox/response.rb
163
+ - lib/sandrbox/value.rb
164
+ - spec/sandrbox/config_spec.rb
165
+ - spec/sandrbox/response_spec.rb
166
+ - spec/sandrbox/value_spec.rb
167
+ - spec/sandrbox_spec.rb
168
+ - spec/spec_helper.rb
169
+ homepage: http://github.com/Veraticus/Sandrbox
170
+ licenses:
171
+ - MIT
172
+ post_install_message:
173
+ rdoc_options: []
174
+ require_paths:
175
+ - lib
176
+ required_ruby_version: !ruby/object:Gem::Requirement
177
+ none: false
178
+ requirements:
179
+ - - ! '>='
180
+ - !ruby/object:Gem::Version
181
+ version: '0'
182
+ segments:
183
+ - 0
184
+ hash: 928819448596422268
185
+ required_rubygems_version: !ruby/object:Gem::Requirement
186
+ none: false
187
+ requirements:
188
+ - - ! '>='
189
+ - !ruby/object:Gem::Version
190
+ version: '0'
191
+ requirements: []
192
+ rubyforge_project:
193
+ rubygems_version: 1.8.19
194
+ signing_key:
195
+ specification_version: 3
196
+ summary: A sanitizing sandbox for executing Ruby code
197
+ test_files: []