Class: Rack::Webconsole::Repl

Inherits:
Object
  • Object
show all
Defined in:
lib/rack/webconsole/repl.rb

Overview

Repl is a Rack middleware acting as a Ruby evaluator application.

In a nutshell, it evaluates a string in a Sandbox instance stored in an evil global variable. Then, to keep the state, it inspects the local variables and stores them in an instance variable for further retrieval.

Constant Summary collapse

@@request =
nil
@@tokens =
{}

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app) ⇒ Repl

Honor the Rack contract by saving the passed Rack application in an ivar.

Parameters:

  • app (Rack::Application)

    the previous Rack application in the middleware chain.



61
62
63
64
# File 'lib/rack/webconsole/repl.rb', line 61

def initialize(app)
  @app = app

end

Class Method Details

.clear_tokensObject



19
20
21
22
23
# File 'lib/rack/webconsole/repl.rb', line 19

def clear_tokens
  @@tokens.each_pair do |k, v|
    @@tokens.delete(k) if v <= Time.now
  end
end

.requestRack::Request

Returns the original request for inspection purposes.

Returns:

  • (Rack::Request)

    the original request



44
45
46
# File 'lib/rack/webconsole/repl.rb', line 44

def request
  @@request
end

.request=(request) ⇒ Object

Sets the original request for inspection purposes.

Parameters:

  • the (Rack::Request)

    original request



51
52
53
# File 'lib/rack/webconsole/repl.rb', line 51

def request=(request)
  @@request = request
end

.reset_token(app, env) ⇒ Object

Regenerates the token.



34
35
36
37
38
39
# File 'lib/rack/webconsole/repl.rb', line 34

def reset_token(app, env)
  clear_tokens
  token = Digest::SHA1.hexdigest("#{rand(36**8)}#{Time.now}")[4..20]
  @@tokens[token] = Time.now + 30 * 60
  token
end

.token_valid?(token) ⇒ String

Returns the autogenerated security token

Returns:

  • (String)

    the autogenerated token



28
29
30
31
# File 'lib/rack/webconsole/repl.rb', line 28

def token_valid? token
  clear_tokens
  @@tokens.keys.include?(token)
end

Instance Method Details

#call(env) ⇒ Array

Evaluates a string as Ruby code and returns the evaluated result as JSON.

It also stores the Sandbox state in a ‘$sandbox` global variable, with its local variables.

Parameters:

  • env (Hash)

    the Rack request environment.

Returns:

  • (Array)

    a Rack response with status code 200, HTTP headers and the evaluated Ruby result.



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/rack/webconsole/repl.rb', line 75

def call(env)
  status, headers, response = @app.call(env)

  req = Rack::Request.new(env)
  params = req.params

  return [status, headers, response] unless check_legitimate(req)

  hash = {}
  $pry_output ||= StringIO.new("")
  $pry_output.string = ""
  if $pry.nil?
    Pry.pager = false
    $pry = Pry.new(:output => $pry_output, :pager => false)
    Pry.initial_session_setup
  end
  pry = $pry
  
  # repl loop
  if pry.binding_stack.last
    target = Pry.binding_for(pry.binding_stack.last)
  else
    target = Pry.binding_for(TOPLEVEL_BINDING)
  end
  pry.repl_prologue(target) unless pry.binding_stack.last == target
  pry.inject_sticky_locals(target)
  code = params['query']
  hash[:prompt] = pry.select_prompt("", target) + Pry::Code.new(code).to_s
  got_output = false
  begin
    read_pipe, write_pipe = IO.pipe
    end_line = "~~~~~ rack-webconsole end output ~~~~~\n"

    thr = Thread.new do
      while true
        new_line = read_pipe.readline
        break if new_line == end_line
        $pry_output << new_line
      end
    end

    old_stdout = STDOUT.dup
    old_stderr = STDERR.dup
    STDOUT.reopen(write_pipe)
    STDERR.reopen(write_pipe)
    if !pry.process_command(code, "", target)
      result = target.eval(code, Pry.eval_path, Pry.current_line)
      got_output = true
    end
  rescue StandardError => e
    error_out = "Error: " + e.message
  ensure
    write_pipe << end_line

    thr.join
    read_pipe.close
    write_pipe.close
    STDOUT.reopen(old_stdout)
    STDERR.reopen(old_stderr)

  end

  if got_output
    pry.set_last_result(result, target, code)
    Pry.print.call($pry_output, result) if pry.should_print?
    # the below line doesn't work well with custom printers
    # (like awesome_print) for some reason
    #pry.show_result(result) if pry.should_print?
  end

  $pry_output.write(error_out) if error_out

  # cleanup (supposed to call when $pry is destroyed)
  # pry.repl_epilogue(target)
  
  hash[:result] = $pry_output.string
  response_body = MultiJson.encode(hash)
  headers = {}
  headers['Content-Type'] = 'application/json'
  headers['Content-Length'] = response_body.bytesize.to_s
  [200, headers, [response_body]]
end