tobias-rack-webconsole 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+ module Rack
3
+ class Webconsole
4
+ # Railtie loaded in Rails applications. Its purpose is to automatically use
5
+ # the middleware in development environment, so that Rails users only have
6
+ # to require 'rack-webconsole' in their Gemfile and nothing more than that.
7
+ #
8
+ class Railtie < Rails::Railtie
9
+ initializer 'rack-webconsole.add_middleware' do |app|
10
+ app.middleware.use Rack::Webconsole if Rails.env.development?
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,86 @@
1
+ # encoding: utf-8
2
+ require 'multi_json'
3
+ require 'digest/sha1'
4
+
5
+ module Rack
6
+ class Webconsole
7
+ # {Repl} is a Rack middleware acting as a Ruby evaluator application.
8
+ #
9
+ # In a nutshell, it evaluates a string in a {Sandbox} instance stored in an
10
+ # evil global variable. Then, to keep the state, it inspects the local
11
+ # variables and stores them in an instance variable for further retrieval.
12
+ #
13
+ class Repl
14
+ @@request = nil
15
+ @@token = nil
16
+
17
+ class << self
18
+ # Returns the autogenerated security token
19
+ #
20
+ # @return [String] the autogenerated token
21
+ def token
22
+ @@token
23
+ end
24
+
25
+ # Regenerates the token.
26
+ def reset_token
27
+ @@token = Digest::SHA1.hexdigest("#{rand(36**8)}#{Time.now}")[4..20]
28
+ end
29
+
30
+ # Returns the original request for inspection purposes.
31
+ #
32
+ # @return [Rack::Request] the original request
33
+ def request
34
+ @@request
35
+ end
36
+
37
+ # Sets the original request for inspection purposes.
38
+ #
39
+ # @param [Rack::Request] the original request
40
+ def request=(request)
41
+ @@request = request
42
+ end
43
+ end
44
+
45
+ # Honor the Rack contract by saving the passed Rack application in an ivar.
46
+ #
47
+ # @param [Rack::Application] app the previous Rack application in the
48
+ # middleware chain.
49
+ def initialize(app)
50
+ @app = app
51
+ end
52
+
53
+ # Evaluates a string as Ruby code and returns the evaluated result as
54
+ # JSON.
55
+ #
56
+ # It also stores the {Sandbox} state in a `$sandbox` global variable, with
57
+ # its local variables.
58
+ #
59
+ # @param [Hash] env the Rack request environment.
60
+ # @return [Array] a Rack response with status code 200, HTTP headers
61
+ # and the evaluated Ruby result.
62
+ def call(env)
63
+ status, headers, response = @app.call(env)
64
+
65
+ req = Rack::Request.new(env)
66
+ params = req.params
67
+
68
+ return [status, headers, response] unless check_legitimate(req)
69
+
70
+ $sandbox ||= Sandbox.new
71
+ hash = Shell.eval_query params['query']
72
+ response_body = MultiJson.encode(hash)
73
+ headers = {}
74
+ headers['Content-Type'] = 'application/json'
75
+ headers['Content-Length'] = response_body.bytesize.to_s
76
+ [200, headers, [response_body]]
77
+ end
78
+
79
+ private
80
+
81
+ def check_legitimate(req)
82
+ req.post? && !Repl.token.nil? && req.params['token'] == Repl.token
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+ module Rack
3
+ class Webconsole
4
+ # A sandbox to evaluate Ruby in. It is responsible for retrieving local
5
+ # variables stored in `@locals`, and resetting the environment.
6
+ #
7
+ class Sandbox
8
+ # Catches all the undefined local variables and tries to retrieve them
9
+ # from `@locals`. If it doesn't find them, it falls back to the default
10
+ # method missing behavior.
11
+ def method_missing(method, *args, &block)
12
+ @locals ||= {}
13
+ @locals[method.to_sym] || super(method, *args, &block)
14
+ end
15
+
16
+ # Makes the console use a fresh, new {Sandbox} with all local variables
17
+ # resetted.
18
+ #
19
+ # @return [String] 'ok' to make the user notice.
20
+ def reload!
21
+ $sandbox = Sandbox.new
22
+ 'ok'
23
+ end
24
+
25
+ # Returns the current page request object for inspection purposes.
26
+ #
27
+ # @return [Rack::Request] the current page request object.
28
+ def request
29
+ Webconsole::Repl.request
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,74 @@
1
+ require 'ripl'
2
+
3
+ class Rack::Webconsole
4
+ module Shell
5
+ def self.eval_query(query)
6
+ # Initialize ripl plugins
7
+ @before_loop_called ||= Ripl.shell.before_loop
8
+
9
+ Ripl.shell.input = query
10
+ Ripl.shell.loop_once
11
+ {}.tap do |hash|
12
+ hash[:result] = Ripl.shell.return_result
13
+ hash[:multi_line] = Ripl.shell.multi_line?
14
+ hash[:previous_multi_line] = Ripl.shell.previous_multi_line?
15
+ hash[:prompt] = Ripl.shell.previous_multi_line? ?
16
+ Ripl.config[:multi_line_prompt] : Ripl.config[:prompt]
17
+ end
18
+ end
19
+
20
+ # TODO: move to plugin
21
+ def multi_line?
22
+ @buffer.is_a?(Array)
23
+ end
24
+
25
+ def previous_multi_line?
26
+ @old_buffer.is_a?(Array)
27
+ end
28
+
29
+ def get_input
30
+ @old_buffer = @buffer
31
+ history << @input
32
+ @input
33
+ end
34
+
35
+ def loop_eval(query)
36
+ # Force conversion to symbols due to issues with lovely 1.8.7
37
+ boilerplate = local_variables.map(&:to_sym) + [:ls, :result]
38
+
39
+ $sandbox.instance_eval """
40
+ result = (#{query})
41
+ ls = (local_variables.map(&:to_sym) - [#{boilerplate.map(&:inspect).join(', ')}])
42
+ @locals ||= {}
43
+ @locals.update(ls.inject({}) do |hash, value|
44
+ hash.update({value => eval(value.to_s)})
45
+ end)
46
+ result
47
+ """
48
+ end
49
+
50
+ def print_eval_error(err)
51
+ @result = "Error: " + err.message
52
+ end
53
+
54
+ def return_result
55
+ @error_raised ? result : result.inspect
56
+ end
57
+
58
+ def print_result(result) end
59
+ end
60
+ end
61
+
62
+ # Disable readline's #get_input
63
+ Ripl.config[:readline] = false
64
+ Ripl.config[:multi_line_prompt] = '|| '
65
+ Ripl.config[:prompt] = '>> '
66
+ Ripl.config[:irbrc] = false
67
+ # Let ripl users detect web shells
68
+ Ripl.config[:web] = true
69
+
70
+ Ripl::Shell.include Rack::Webconsole::Shell
71
+ # must come after Webconsole plugin
72
+ require 'ripl/multi_line'
73
+
74
+ at_exit { Ripl.shell.after_loop }
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+ module Rack
3
+ class Webconsole
4
+ # rack-webconsole version number.
5
+ VERSION = "0.1.4"
6
+ end
7
+ end
@@ -0,0 +1 @@
1
+ <script type="text/javascript" src="http://code.jquery.com/jquery-1.6.2.min.js"></script>
@@ -0,0 +1,86 @@
1
+ #rack-webconsole {
2
+ opacity: 0.9;
3
+ z-index: 999;
4
+ background: #000;
5
+ color: #DDD;
6
+ font-family: monospace;
7
+ height: 40%;
8
+ position: fixed;
9
+ width: 100%;
10
+ bottom: 0px;
11
+ left: 0px;
12
+ right:0px;
13
+ outline-top: 3px solid #DEDEDE;
14
+ box-shadow: 0px -4px 5px rgba(0,0,0,0.5);
15
+ -moz-box-shadow: 0px -4px 5px rgba(0,0,0,0.5);
16
+ -webkit-box-shadow: 0px -4px 5px rgba(0,0,0,0.5);
17
+ font-size: 11px;
18
+ }
19
+ #rack-webconsole div.query{
20
+ margin-top: 10px;
21
+ font-weight: bold;
22
+ padding-top: 10px;
23
+ border-top: 1px dashed #333;
24
+ margin-bottom: 5px;
25
+ }
26
+ #rack-webconsole div.query_multiline{
27
+ font-weight: bold;
28
+ margin-bottom: 5px;
29
+ }
30
+ #rack-webconsole div.query:first-child{
31
+ margin-top: 0px;
32
+ padding-top: 0px;
33
+ border-top: none;
34
+ }
35
+ #rack-webconsole div.result{
36
+ font-weight: normal;
37
+ }
38
+ #rack-webconsole form div, #console form span {
39
+ font-size: 14px;
40
+ border: 0px;
41
+ font-family: monospace;
42
+ color: #FFF;
43
+ }
44
+ #rack-webconsole form div.results_wrapper{
45
+ width: 100%;
46
+ position: absolute;
47
+ overflow-x: auto;
48
+ top: 0;
49
+ bottom: 40px;
50
+ }
51
+ #rack-webconsole form div.results{
52
+ padding: 10px;
53
+ }
54
+ #rack-webconsole .prompt{
55
+ width: 30px;
56
+ text-align: center;
57
+ display: block;
58
+ float: left;
59
+ height: 25px;
60
+ line-height: 25px;
61
+ }
62
+ #rack-webconsole form div.input{
63
+ width: 100%;
64
+ position: absolute;
65
+ bottom: 0px;
66
+ background: #000;
67
+ }
68
+ #rack-webconsole form div.input input{
69
+ -webkit-box-sizing: border-box;
70
+ -moz-box-sizing: border-box;
71
+ box-sizing: border-box;
72
+ margin-top: 0px;
73
+ margin-bottom: 0px;
74
+ padding: 0px;
75
+ width: 100%;
76
+ font-size: 14px;
77
+ background: transparent;
78
+ border: 0px;
79
+ font-family: monospace;
80
+ color: #FFF;
81
+ }
82
+ #rack-webconsole .input .input_box{
83
+ margin-left: 30px;
84
+ margin-right: 10px;
85
+ display: block;
86
+ }
@@ -0,0 +1,15 @@
1
+ <div id="rack-webconsole" style="display:none">
2
+ <form accept-charset="UTF-8" action="/webconsole" method="post">
3
+ <input name="utf8" type="hidden" value="✓"/>
4
+ <div class="results_wrapper">
5
+ <div class="results">
6
+ </div>
7
+ </div>
8
+ <div class="input">
9
+ <span class="prompt">>></span>
10
+ <span class="input_box">
11
+ <input id="webconsole_query" name="webconsole_query" type="text" />
12
+ </span>
13
+ </div>
14
+ </form>
15
+ </div>
@@ -0,0 +1,92 @@
1
+ (function($) {
2
+
3
+ var webconsole = {
4
+ history:[],
5
+ pointer:0,
6
+ query:$('#webconsole_query')
7
+ }
8
+
9
+ $('#rack-webconsole form').submit(function(e){
10
+ e.preventDefault();
11
+ });
12
+
13
+ $("#rack-webconsole form input").keyup(function(event) {
14
+ function escapeHTML(string) {
15
+ return(string.replace(/&/g,'&amp;').
16
+ replace(/>/g,'&gt;').
17
+ replace(/</g,'&lt;').
18
+ replace(/"/g,'&quot;')
19
+ );
20
+ };
21
+
22
+ // enter
23
+ if (event.which == 13) {
24
+ webconsole.history.push(webconsole.query.val());
25
+ webconsole.pointer = webconsole.history.length - 1;
26
+ $.ajax({
27
+ url: '$CONTEXT/webconsole',
28
+ type: 'POST',
29
+ dataType: 'json',
30
+ data: ({query: webconsole.query.val(), token: "$TOKEN"}),
31
+ success: function (data) {
32
+ var query_class = data.previous_multi_line ? 'query_multiline' : 'query';
33
+ var result = "<div class='" + query_class + "'>" +
34
+ escapeHTML(data.prompt + webconsole.query.val()) + "</div>";
35
+ if (!data.multi_line) {
36
+ result += "<div class='result'>" + escapeHTML("=> " + data.result) + "</div>";
37
+ }
38
+ $("#rack-webconsole .results").append(result);
39
+ $("#rack-webconsole .results_wrapper").scrollTop(
40
+ $("#rack-webconsole .results").height()
41
+ );
42
+ webconsole.query.val('');
43
+ }
44
+ });
45
+ }
46
+
47
+ // up
48
+ if (event.which == 38) {
49
+ if (webconsole.pointer < 0) {
50
+ webconsole.query.val('');
51
+ } else {
52
+ if (webconsole.pointer == webconsole.history.length) {
53
+ webconsole.pointer = webconsole.history.length - 1;
54
+ }
55
+ webconsole.query.val(webconsole.history[webconsole.pointer]);
56
+ webconsole.pointer--;
57
+ }
58
+ }
59
+
60
+ // down
61
+ if (event.which == 40) {
62
+ if (webconsole.pointer == webconsole.history.length) {
63
+ webconsole.query.val('');
64
+ } else {
65
+ if (webconsole.pointer < 0) {
66
+ webconsole.pointer = 0;
67
+ }
68
+ webconsole.query.val(webconsole.history[webconsole.pointer]);
69
+ webconsole.pointer++;
70
+ }
71
+ }
72
+
73
+ });
74
+
75
+ $(document).ready(function() {
76
+ $(this).keypress(function(event) {
77
+ if (event.which == $KEY_CODE) {
78
+ $("#rack-webconsole").slideToggle('fast', function() {
79
+ if ($(this).is(':visible')) {
80
+ $("#rack-webconsole form input").focus();
81
+ $("#rack-webconsole .results_wrapper").scrollTop(
82
+ $("#rack-webconsole .results").height()
83
+ );
84
+ } else {
85
+ $("#rack-webconsole form input").blur();
86
+ }
87
+ });
88
+ event.preventDefault();
89
+ }
90
+ });
91
+ });
92
+ })(jQuery);
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rack/webconsole/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "tobias-rack-webconsole"
7
+ s.version = Rack::Webconsole::VERSION
8
+ s.authors = ["Josep M. Bach", "Josep Jaume Rey", "Oriol Gual"]
9
+ s.email = ["toby@tcrawley.org"]
10
+ s.homepage = "http://github.com/tobias/rack-webconsole"
11
+ s.summary = %q{Rack-based console inside your web applications - this is a fork of http://github.com/codegram/rack-webconsole}
12
+ s.description = s.summary
13
+
14
+ s.rubyforge_project = "rack-webconsole"
15
+
16
+ s.add_runtime_dependency 'rack'
17
+ s.add_runtime_dependency 'multi_json', '~> 1.0.3'
18
+ s.add_runtime_dependency 'ripl', '~> 0.5.1'
19
+ s.add_runtime_dependency 'ripl-multi_line', '~> 0.3.0'
20
+
21
+ s.add_development_dependency 'minitest'
22
+ s.add_development_dependency 'purdytest'
23
+
24
+ # Since we can't have a git dependency in gemspec, we specify this
25
+ # dependency directly in the Gemfile. Once a new mocha version is released,
26
+ # we should uncomment this line and remove mocha from the Gemfile.
27
+ # s.add_development_dependency 'mocha'
28
+
29
+ s.add_development_dependency 'yard'
30
+ s.add_development_dependency 'bluecloth'
31
+ s.add_development_dependency 'rake'
32
+
33
+ s.files = `git ls-files`.split("\n")
34
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
35
+ s.require_paths = ["lib"]
36
+ end