tobias-rack-webconsole 0.1.4

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.
@@ -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