web-console-2.2.1/0000755000076400007640000000000012560702257012773 5ustar pravipraviweb-console-2.2.1/CHANGELOG.markdown0000644000076400007640000000532012560702257016026 0ustar pravipravi# CHANGELOG ## master (unreleased) ## 2.2.1 * [#150](https://github.com/rails/web-console/pull/150) Change config.development_only default until 4.2.4 is released. ## 2.2.0 * [#140](https://github.com/rails/web-console/pull/140) Add the ability to close the console on each page ([@sh19910711]) * [#135](https://github.com/rails/web-console/pull/135) Run the console only in development mode and raise warning in tests ([@frenesim]) * [#134](https://github.com/rails/web-conscle/pull/134) Force development only web console by default ([@gsamokovarov]) * [#123](https://github.com/rails/web-console/pull/123) Replace deprecated `alias_method_chain` with `alias_method` ([@jonatack]) ## 2.1.3 * Fix remote code execution vulnerability in Web Console. CVE-2015-3224. ## 2.1.2 * [#115](https://github.com/rails/web-console/pull/115) Show proper binding when raising an error in a template ([@gsamokovarov]) * [#114](https://github.com/rails/web-console/pull/114) Fix templates non rendering, because of missing template suffix ([@gsamokovarov]) ## 2.1.1 * [#112](https://github.com/rails/web-console/pull/112) Always allow application/x-www-form-urlencoded content type ([@gsamokovarov]) ## 2.1.0 * [#109](https://github.com/rails/web-console/pull/109) Revamp unavailable session response message ([@gsamokovarov]) * [#107](https://github.com/rails/web-console/pull/107) Fix pasting regression for all browsers ([@parterburn]) * [#105](https://github.com/rails/web-console/pull/105) Lock scroll bottom on console window resize ([@noahpatterson]) * [#104](https://github.com/rails/web-console/pull/104) Always whitelist localhost and inform users why no console is displayed ([@gsamokovarov]) * [#100](https://github.com/rails/web-console/pull/100) Accept text/plain as acceptable content type for Puma ([@gsamokovarov]) * [#98](https://github.com/rails/web-console/pull/98) Add arbitrary big z-index to the console ([@bglbruno]) * [#88](https://github.com/rails/web-console/pull/88) Spelling fixes ([@jeffnv]) * [#86](https://github.com/rails/web-console/pull/86) Disable autofocus when initializing the console ([@ryandao]) * [#84](https://github.com/rails/web-console/pull/84) Allow Rails 5 as dependency in gemspec ([@jonatack]) * [#69](https://github.com/rails/web-console/pull/69) Introduce middleware for request dispatch and console rendering ([@gsamokovarov]) [@jonatack]: https://github.com/jonatack [@ryandao]: https://github.com/ryandao [@jeffnv]: https://github.com/jeffnv [@gsamokovarov]: https://github.com/gsamokovarov [@bglbruno]: https://github.com/bglbruno [@noahpatterson]: https://github.com/noahpatterson [@parterburn]: https://github.com/parterburn [@sh19910711]: https://github.com/sh19910711 [@frenesim]: https://github.com/frenesim web-console-2.2.1/metadata.yml0000644000076400007640000001066212560702257015303 0ustar pravipravi--- !ruby/object:Gem::Specification name: web-console version: !ruby/object:Gem::Version version: 2.2.1 platform: ruby authors: - Charlie Somerville - Genadi Samokovarov - Guillermo Iguaran - Ryan Dao autorequire: bindir: bin cert_chain: [] date: 2015-07-10 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: railties requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '4.0' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '4.0' - !ruby/object:Gem::Dependency name: activemodel requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '4.0' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '4.0' - !ruby/object:Gem::Dependency name: sprockets-rails requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '2.0' - - "<" - !ruby/object:Gem::Version version: '4.0' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '2.0' - - "<" - !ruby/object:Gem::Version version: '4.0' - !ruby/object:Gem::Dependency name: binding_of_caller requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 0.7.2 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 0.7.2 - !ruby/object:Gem::Dependency name: actionmailer requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '4.0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '4.0' - !ruby/object:Gem::Dependency name: activerecord requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '4.0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '4.0' description: email: - charlie@charliesomerville.com - gsamokovarov@gmail.com - guilleiguaran@gmail.com - daoduyducduong@gmail.com executables: [] extensions: [] extra_rdoc_files: [] files: - CHANGELOG.markdown - MIT-LICENSE - README.markdown - Rakefile - lib/web-console.rb - lib/web_console.rb - lib/web_console/errors.rb - lib/web_console/evaluator.rb - lib/web_console/extensions.rb - lib/web_console/helper.rb - lib/web_console/integration.rb - lib/web_console/integration/cruby.rb - lib/web_console/integration/jruby.rb - lib/web_console/integration/rubinius.rb - lib/web_console/middleware.rb - lib/web_console/railtie.rb - lib/web_console/request.rb - lib/web_console/session.rb - lib/web_console/template.rb - lib/web_console/templates/_inner_console_markup.html.erb - lib/web_console/templates/_markup.html.erb - lib/web_console/templates/_prompt_box_markup.html.erb - lib/web_console/templates/console.js.erb - lib/web_console/templates/error_page.js.erb - lib/web_console/templates/index.html.erb - lib/web_console/templates/layouts/inlined_string.erb - lib/web_console/templates/layouts/javascript.erb - lib/web_console/templates/main.js.erb - lib/web_console/templates/style.css.erb - lib/web_console/tracer.rb - lib/web_console/version.rb - lib/web_console/whiny_request.rb - lib/web_console/whitelist.rb homepage: https://github.com/rails/web-console licenses: - MIT metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.4.5 signing_key: specification_version: 4 summary: A debugging tool for your Ruby on Rails applications. test_files: [] web-console-2.2.1/README.markdown0000644000076400007640000001455112560702257015502 0ustar pravipravi

Documentation for: v2.0.0 v2.1.0 v2.1.1 v2.1.2 v2.1.3

# Web Console [![Build Status](https://travis-ci.org/rails/web-console.svg?branch=master)](https://travis-ci.org/rails/web-console) _Web Console_ is a debugging tool for your Ruby on Rails applications. - [Installation](#installation) - [Runtime](#runtime) - [CRuby](#cruby) - [JRuby](#jruby) - [Rubinius](#rubinius) - [Configuration](#configuration) - [Usage](#usage) - [FAQ](#faq) - [Credits](#credits) ## Installation _Web Console_ is meant to work as a Rails plugin. To install it in your current application, add the following to your `Gemfile`. ```ruby group :development do gem 'web-console', '~> 2.0' end ``` After you save the `Gemfile` changes, make sure to run `bundle install` and restart your server for the _Web Console_ to kick in. ## Runtime _Web Console_ uses [John Mair]'s [binding_of_caller] to spawn a console in a specific binding. This comes at the price of limited Ruby runtime support. ### CRuby CRuby 1.9.2 and below is **not** supported. ### JRuby JRuby needs to run in interpreted mode. You can enable it by: ```bash export JRUBY_OPTS=-J-Djruby.compile.mode=OFF # If you run JRuby 1.7.12 and above, you can use: export JRUBY_OPTS=--dev ``` An unstable version of [binding_of_caller] is needed as the latest stable one won't compile on _JRuby_. To install it, put the following in your application `Gemfile`: ```ruby group :development do gem 'binding_of_caller', '0.7.3.pre1' end ``` Only _JRuby_ 1.7, is supported (no JRuby 9K support at the moment). ### Rubinius Internal errors like `ZeroDevisionError` aren't caught. ## Usage The web console allows you to create an interactive Ruby session in your browser. Those sessions are launched automatically in case on an error, but they can also be launched manually in in any page. For example, calling `console` in a view will display a console in the current page in the context of the view binding. ```html <% console %> ``` Calling `console` in a controller will result in a console in the context of the controller action: ```ruby class PostsController < ApplicationController def new console @post = Post.new end end ``` Only one `console` invocation is allowed per request. If you happen to have multiple ones, a `WebConsole::DoubleRenderError` is raised. ## Configuration _Web Console_ allows you to execute arbitrary code on the server, so you should be very careful, who you give access to. ### config.web_console.whitelisted_ips By default, only requests coming from IPv4 and IPv6 localhosts are allowed. `config.web_console.whitelisted_ips` lets you control which IP's have access to the console. You can whitelist single IP's or whole networks. Say you want to share your console with `192.168.0.100`. You can do this: ```ruby class Application < Rails::Application config.web_console.whitelisted_ips = '192.168.0.100' end ``` If you want to whitelist the whole private network, you can do: ```ruby Rails.application.configure do config.web_console.whitelisted_ips = '192.168.0.0/16' end ``` Take a note that IPv4 and IPv6 localhosts are always allowed. This wasn't the case in 2.0. ### config.web_console.whiny_requests When a console cannot be shown for a given IP address or content type, a messages like the following is printed in the server logs: > Cannot render console from 192.168.1.133! Allowed networks: > 127.0.0.0/127.255.255.255, ::1 If you don't wanna see this message anymore, set this option to `false`: ```ruby Rails.application.configure do config.web_console.whiny_requests = false end ``` ### config.web_console.template_paths If you wanna style the console yourself, you can place `style.css` at a directory pointed by `config.web_console.template_paths`: ```ruby Rails.application.configure do config.web_console.template_paths = 'app/views/web_console' end ``` You may wanna check the [templates] folder at the source tree for the files you may override. ## FAQ ### Where did /console go? The remote terminal emulator was extracted in its own gem that is no longer bundled with _Web Console_. If you miss this feature, check out [rvt]. ### Why I constantly get unavailable session errors? All of _Web Console_ sessions are stored in memory. If you happen to run on a multi-process server (like Unicorn) you may get unavailable session errors while the server is still running. This is because a request may hit a different worker (process) that doesn't have the desired session in memory. To avoid that, if you use such servers in development, configure them so they server requests only out of one process. ### How to inspect local and instance variables? The interactive console executes Ruby code. Invoking `instance_variables` and `local_variables` will give you what you want. ### Why does console only appear on error pages but not when I call it? This can be happening if you are using `Rack::Deflater`. Be sure that `WebConsole::Middleware` is used after `Rack::Deflater`. The easiest way to do this is to insert `Rack::Deflater` as early as possible ```ruby Rails.application.configure do config.middleware.insert(0, Rack::Deflater) end ``` ### Why I'm getting an undefined method `web_console`? Make sure you configuration lives in `config/environments/development.rb`. ## Credits * Shoutout to [Charlie Somerville] for [better_errors] and [this] code. * Kudos to [John Mair] for [binding_of_caller]. * Thanks to [Charles Oliver Nutter] for all the _JRuby_ feedback. * Hugs and kisses to all of our [contributors]. [better_errors]: https://github.com/charliesome/better_errors [binding_of_caller]: https://github.com/banister/binding_of_caller [Charlie Somerville]: https://github.com/charliesome [John Mair]: https://github.com/banister [Charles Oliver Nutter]: https://github.com/headius [templates]: https://github.com/rails/web-console/tree/master/lib/web_console/templates [this]: https://github.com/rails/web-console/blob/master/lib/web_console/integration/cruby.rb#L20-L32 [rvt]: https://github.com/gsamokovarov/rvt [contributors]: https://github.com/rails/web-console/graphs/contributors web-console-2.2.1/lib/0000755000076400007640000000000012560702257013541 5ustar pravipraviweb-console-2.2.1/lib/web_console.rb0000644000076400007640000000112512560702257016364 0ustar pravipravirequire 'binding_of_caller' require 'active_support/lazy_load_hooks' require 'active_support/logger' require 'web_console/integration' require 'web_console/railtie' require 'web_console/errors' require 'web_console/helper' require 'web_console/evaluator' require 'web_console/session' require 'web_console/template' require 'web_console/middleware' require 'web_console/whitelist' require 'web_console/request' require 'web_console/whiny_request' module WebConsole mattr_accessor :logger @@logger = ActiveSupport::Logger.new($stderr) ActiveSupport.run_load_hooks(:web_console, self) end web-console-2.2.1/lib/web-console.rb0000644000076400007640000000002612560702257016301 0ustar pravipravirequire 'web_console' web-console-2.2.1/lib/web_console/0000755000076400007640000000000012560702257016040 5ustar pravipraviweb-console-2.2.1/lib/web_console/whitelist.rb0000644000076400007640000000204212560702257020377 0ustar pravipravirequire 'ipaddr' module WebConsole # Whitelist of allowed networks that can access Web Console. # # Networks are represented by standard IPAddr and can be either IPv4 or IPv6 # networks. class Whitelist # IPv4 and IPv6 localhost should be always whitelisted. ALWAYS_WHITELISTED_NETWORKS = %w( 127.0.0.0/8 ::1 ) def initialize(networks = nil) @networks = normalize_networks(networks).map(&method(:coerce_network_to_ipaddr)).uniq end def include?(network) @networks.any? { |whitelist| whitelist.include?(network.to_s) } end def to_s @networks.map(&method(:human_readable_ipaddr)).join(', ') end private def normalize_networks(networks) Array(networks).concat(ALWAYS_WHITELISTED_NETWORKS) end def coerce_network_to_ipaddr(network) if network.is_a?(IPAddr) network else IPAddr.new(network) end end def human_readable_ipaddr(ipaddr) ipaddr.to_range.to_s.split('..').uniq.join('/') end end end web-console-2.2.1/lib/web_console/whiny_request.rb0000644000076400007640000000175212560702257021300 0ustar pravipravimodule WebConsole # Noisy wrapper around +Request+. # # If any calls to +from_whitelisted_ip?+ and +acceptable_content_type?+ # return false, an info log message will be displayed in users' logs. class WhinyRequest < SimpleDelegator def from_whitelited_ip? whine_unless request.from_whitelited_ip? do "Cannot render console from #{request.remote_ip}! " \ "Allowed networks: #{request.whitelisted_ips}" end end def acceptable_content_type? whine_unless request.acceptable_content_type? do "Cannot render console with content type #{request.content_type}" \ "Allowed content types: #{request.acceptable_content_types}" end end private def whine_unless(condition) unless condition logger.info { yield } end condition end def logger env['action_dispatch.logger'] || WebConsole.logger end def request __getobj__ end end end web-console-2.2.1/lib/web_console/errors.rb0000644000076400007640000000034112560702257017677 0ustar pravipravimodule WebConsole # The base class for every Web Console related error. Error = Class.new(StandardError) # Raised when there is an attempt to render a console more than once. DoubleRenderError = Class.new(Error) end web-console-2.2.1/lib/web_console/extensions.rb0000644000076400007640000000151712560702257020570 0ustar pravipraviActionDispatch::DebugExceptions.class_eval do def render_exception_with_web_console(env, exception) render_exception_without_web_console(env, exception).tap do error = ActionDispatch::ExceptionWrapper.new(env, exception).exception # Get the original exception if ExceptionWrapper decides to follow it. env['web_console.exception'] = error # ActionView::Template::Error bypass ExceptionWrapper original # exception following. The backtrace in the view is generated from # reaching out to original_exception in the view. if error.is_a?(ActionView::Template::Error) env['web_console.exception'] = error.original_exception end end end alias_method :render_exception_without_web_console, :render_exception alias_method :render_exception, :render_exception_with_web_console end web-console-2.2.1/lib/web_console/evaluator.rb0000644000076400007640000000170612560702257020373 0ustar pravipravimodule WebConsole # Simple Ruby code evaluator. # # This class wraps a +Binding+ object and evaluates code inside of it. The # difference of a regular +Binding+ eval is that +Evaluator+ will always # return a string and will format exception output. class Evaluator # Cleanses exceptions raised inside #eval. cattr_reader :cleaner @@cleaner = ActiveSupport::BacktraceCleaner.new @@cleaner.add_silencer { |line| line.start_with?(File.expand_path('..', __FILE__)) } def initialize(binding = TOPLEVEL_BINDING) @binding = binding end def eval(input) "=> #{@binding.eval(input).inspect}\n" rescue Exception => exc format_exception(exc) end private def format_exception(exc) backtrace = cleaner.clean(Array(exc.backtrace) - caller) format = "#{exc.class.name}: #{exc}\n" format << backtrace.map { |trace| "\tfrom #{trace}\n" }.join format end end end web-console-2.2.1/lib/web_console/railtie.rb0000644000076400007640000000466112560702257020025 0ustar pravipravirequire 'rails/railtie' module WebConsole class Railtie < ::Rails::Railtie config.web_console = ActiveSupport::OrderedOptions.new config.web_console.whitelisted_ips = %w( 127.0.0.1 ::1 ) # See rails/web-console#150 and rails/rails#20319. Revert when Ruby on # Rails 4.2.4 is released. config.web_console.development_only = false initializer 'web_console.initialize' do require 'web_console/extensions' ActiveSupport.on_load(:action_view) do ActionView::Base.send(:include, Helper) end ActiveSupport.on_load(:action_controller) do ActionController::Base.send(:include, Helper) end end initializer 'web_console.development_only' do unless (config.web_console.development_only == false) || Rails.env.development? abort <<-END.strip_heredoc Web Console is activated in the #{Rails.env} environment, which is usually a mistake. To ensure it's only activated in development mode, move it to the development group of your Gemfile: gem 'web-console', group: :development If you still want to run it the #{Rails.env} environment (and know what you are doing), put this in your Rails application configuration: config.web_console.development_only = false END end end initializer 'web_console.insert_middleware' do |app| app.middleware.insert_before ActionDispatch::DebugExceptions, Middleware end initializer 'web_console.template_paths' do if template_paths = config.web_console.template_paths Template.template_paths.unshift(*Array(template_paths)) end end initializer 'web_console.whitelisted_ips' do if whitelisted_ips = config.web_console.whitelisted_ips Request.whitelisted_ips = Whitelist.new(whitelisted_ips) end end initializer 'web_console.whiny_requests' do if config.web_console.key?(:whiny_requests) Middleware.whiny_requests = config.web_console.whiny_requests end end # Leave this undocumented so we treat such content type misses as bugs, # while still being able to help the affected users in the meantime. initializer 'web_console.acceptable_content_types' do if acceptable_content_types = config.web_console.acceptable_content_types Request.acceptable_content_types.concat(Array(acceptable_content_types)) end end end end web-console-2.2.1/lib/web_console/tracer.rb0000644000076400007640000000031712560702257017646 0ustar pravipravimodule WebConsole class BindingTracer def initialize(exception) @bindings = exception.bindings @backtrace = exception.backtrace end def binding_for_trace(trace) end end end web-console-2.2.1/lib/web_console/request.rb0000644000076400007640000000346512560702257020065 0ustar pravipravimodule WebConsole # Web Console tailored request object. class Request < ActionDispatch::Request # While most of the servers will return blank content type if none given, # Puma will return text/plain. cattr_accessor :acceptable_content_types @@acceptable_content_types = [Mime::HTML, Mime::TEXT, Mime::URL_ENCODED_FORM] # Configurable set of whitelisted networks. cattr_accessor :whitelisted_ips @@whitelisted_ips = Whitelist.new # Define a vendor MIME type. We can call it using Mime::WEB_CONSOLE_V2 constant. Mime::Type.register 'application/vnd.web-console.v2', :web_console_v2 # Returns whether a request came from a whitelisted IP. # # For a request to hit Web Console features, it needs to come from a white # listed IP. def from_whitelited_ip? whitelisted_ips.include?(strict_remote_ip) end # Determines the remote IP using our much stricter whitelist. def strict_remote_ip GetSecureIp.new(env, whitelisted_ips).to_s end # Returns whether the request is from an acceptable content type. # # We can render a console for HTML and TEXT by default. If a client didn't # specified any content type and the server returned it as blank, we'll # render it as well. def acceptable_content_type? content_type.blank? || content_type.in?(acceptable_content_types) end # Returns whether the request is acceptable. def acceptable? xhr? && accepts.any? { |mime| Mime::WEB_CONSOLE_V2 == mime } end class GetSecureIp < ActionDispatch::RemoteIp::GetIp def initialize(env, proxies) @env = env @check_ip = true @proxies = proxies end def filter_proxies(ips) ips.reject do |ip| @proxies.include?(ip) end end end end end web-console-2.2.1/lib/web_console/template.rb0000644000076400007640000000333512560702257020204 0ustar pravipravimodule WebConsole # A facade that handles template rendering and composition. # # It introduces template helpers to ease the inclusion of scripts only on # Rails error pages. class Template class Context < ActionView::Base # Execute a block only on error pages. # # The error pages are special, because they are the only pages that # currently require multiple bindings. We get those from exceptions. def only_on_error_page(*args) yield if @env['web_console.exception'].present? end # Render JavaScript inside a script tag and a closure. # # This one lets write JavaScript that will automatically get wrapped in a # script tag and enclosed in a closure, so you don't have to worry for # leaking globals, unless you explicitly want to. def render_javascript(template) render(template: template, layout: 'layouts/javascript') end # Render inlined string to be used inside of JavaScript code. # # The inlined string is returned as an actual JavaScript string. You # don't need to wrap the result yourself. def render_inlined_string(template) render(template: template, layout: 'layouts/inlined_string') end end # Lets you customize the default templates folder location. cattr_accessor :template_paths @@template_paths = [ File.expand_path('../templates', __FILE__) ] def initialize(env, session) @env = env @session = session end # Render a template (inferred from +template_paths+) as a plain string. def render(template) context = Context.new(template_paths, instance_values) context.render(template: template, layout: false) end end end web-console-2.2.1/lib/web_console/version.rb0000644000076400007640000000005212560702257020047 0ustar pravipravimodule WebConsole VERSION = '2.2.1' end web-console-2.2.1/lib/web_console/integration/0000755000076400007640000000000012560702257020363 5ustar pravipraviweb-console-2.2.1/lib/web_console/integration/cruby.rb0000644000076400007640000000271112560702257022035 0ustar pravipraviclass Exception begin # We share the same exception binding extraction mechanism as better_errors, # so try to use it if it is already available. It also solves problems like # charliesome/better_errors#272, caused by an infinite recursion. require 'better_errors' # The bindings in which the exception originated in. def bindings @bindings || __better_errors_bindings_stack end rescue LoadError # The bindings in which the exception originated in. def bindings @bindings || [] end # CRuby calls #set_backtrace every time it raises an exception. Overriding # it to assign the #bindings. def set_backtrace_with_binding_of_caller(*args) # Thanks to @charliesome who wrote this bit for better_errors. unless Thread.current[:__web_console_exception_lock] Thread.current[:__web_console_exception_lock] = true begin # Raising an exception here will cause all of the rubies to go into a # stack overflow. Some rubies may even segfault. See # https://bugs.ruby-lang.org/issues/10164 for details. @bindings = binding.callers.drop(1) ensure Thread.current[:__web_console_exception_lock] = false end end set_backtrace_without_binding_of_caller(*args) end alias_method :set_backtrace_without_binding_of_caller, :set_backtrace alias_method :set_backtrace, :set_backtrace_with_binding_of_caller end end web-console-2.2.1/lib/web_console/integration/rubinius.rb0000644000076400007640000000362012560702257022551 0ustar pravipravimodule WebConsole module Rubinius # Filters internal Rubinius locations. # # There are a couple of reasons why we wanna filter out the locations. # # * ::Kernel.raise, is implemented in Ruby for Rubinius. We don't wanna # have the frame for it to align with the CRuby and JRuby implementations. # # * For internal methods location variables can be nil. We can't create a # bindings for them. # # * Bindings from the current file are considered internal and ignored. # # We do that all that so we can align the bindings with the backtraces # entries. class InternalLocationFilter def initialize(locations) @locations = locations end def filter @locations.reject do |location| location.file.start_with?('kernel/delta/kernel.rb') || location.file == __FILE__ || location.variables.nil? end end end # Gets the current bindings for all available Ruby frames. # # Filters the internal Rubinius and WebConsole frames. def self.current_bindings locations = ::Rubinius::VM.backtrace(1, true) InternalLocationFilter.new(locations).filter.map do |location| Binding.setup( location.variables, location.variables.method, location.constant_scope, location.variables.self, location ) end end end end ::Exception.class_eval do def bindings @bindings || [] end end ::Rubinius.singleton_class.class_eval do def raise_exception_with_current_bindings(exc) if exc.bindings.empty? exc.instance_variable_set(:@bindings, WebConsole::Rubinius.current_bindings) end raise_exception_without_current_bindings(exc) end alias_method :raise_exception_without_current_bindings, :raise_exception alias_method :raise_exception, :raise_exception_with_current_bindings end web-console-2.2.1/lib/web_console/integration/jruby.rb0000644000076400007640000000635112560702257022050 0ustar pravipravirequire 'English' require 'active_support/core_ext/string/strip' java_import org.jruby.RubyInstanceConfig module WebConsole module JRuby class << self # Returns whether JRuby is ran in interpreted mode. def interpreted_mode? compile_mode = ::JRuby.runtime.instance_config.compile_mode interpreted_mode = RubyInstanceConfig::CompileMode::OFF compile_mode == interpreted_mode end # A proc to be used in Kernel#set_trace_func. # # It sets Exception#bindings for an error with all the bindings the # current ThreadContext contains. def set_exception_bindings_trace_func proc do |event, file, line, id, binding, classname| case event when 'raise' if $ERROR_INFO.bindings.empty? # binding_of_caller will generate an improperly built binding at # caller[1]. Every call to a non existent method, constant or a # local variable will result in a Java NullPointerException. # # The binding that Kernel#set_trace_func is giving us is properly # built, so we can use in place of the broken one. bindings = ::Kernel.binding.callers.drop(2).unshift(binding) $ERROR_INFO.instance_variable_set(:@bindings, bindings) end end end end end # A fake binding for JRuby in non interpreted mode. # # It won't actually evaluate any code, rather it will tell the user how to # enable interpreted mode. class FakeJRubyBinding TURN_ON_INTERPRETED_MODE_TEXT = <<-END.strip_heredoc JRuby needs to run in interpreted mode for introspection support. To turn on interpreted mode, put -J-Djruby.compile.mode=OFF in the JRUBY_OPTS environment variable. END def TURN_ON_INTERPRETED_MODE_TEXT.inspect self end TURN_ON_INTERPRETED_MODE_TEXT.freeze def eval(*) TURN_ON_INTERPRETED_MODE_TEXT end end # A fake array of FakeJRubyBinding objects. # # It is used in Exception#bindings to make sure that when users switch # bindings on the UI, they get a FakeJRubyBinding notifying them what to do # if they want introspection. class FakeJRubyBindingsArray < Array def [](*) FakeJRubyBinding.new end # For Kernel#Array and other implicit conversion API. JRuby expects it to # return an object that is_a? an Array. This is the reasoning of # FakeJRubyBindingsArray being a subclass of Array. def to_ary self end end end end if WebConsole::JRuby.interpreted_mode? ::Exception.class_eval do def bindings @bindings || [] end end # Kernel#set_trace_func will complain about not being able to capture all the # events without the JRuby --debug flag. silence_warnings do set_trace_func WebConsole::JRuby.set_exception_bindings_trace_func end else ::Exception.class_eval do def bindings WebConsole::JRuby::FakeJRubyBindingsArray.new end end ::Binding.class_eval do def of_caller(*) WebConsole::JRuby::FakeJRubyBinding.new end def callers WebConsole::JRuby::FakeJRubyBindingsArray.new end end end web-console-2.2.1/lib/web_console/integration.rb0000644000076400007640000000027212560702257020711 0ustar pravipravicase RUBY_ENGINE when 'rbx' require 'web_console/integration/rubinius' when 'jruby' require 'web_console/integration/jruby' when 'ruby' require 'web_console/integration/cruby' end web-console-2.2.1/lib/web_console/session.rb0000644000076400007640000000320412560702257020047 0ustar pravipravimodule WebConsole # A session lets you persist wrap an +Evaluator+ instance in memory # associated with multiple bindings. # # Each newly created session is persisted into memory and you can find it # later its +id+. # # A session may be associated with multiple bindings. This is used by the # error pages only, as currently, this is the only client that needs to do # that. class Session cattr_reader :inmemory_storage @@inmemory_storage = {} class << self # Finds a persisted session in memory by its id. # # Returns a persisted session if found in memory. # Raises NotFound error unless found in memory. def find(id) inmemory_storage[id] end # Create a Session from an exception. def from_exception(exc) new(exc.bindings) end # Create a Session from a single binding. def from_binding(binding) new(binding) end end # An unique identifier for every REPL. attr_reader :id def initialize(bindings) @id = SecureRandom.hex(16) @bindings = Array(bindings) @evaluator = Evaluator.new(@bindings[0]) store_into_memory end # Evaluate +input+ on the current Evaluator associated binding. # # Returns a string of the Evaluator output. def eval(input) @evaluator.eval(input) end # Switches the current binding to the one at specified +index+. # # Returns nothing. def switch_binding_to(index) @evaluator = Evaluator.new(@bindings[index.to_i]) end private def store_into_memory inmemory_storage[id] = self end end end web-console-2.2.1/lib/web_console/helper.rb0000644000076400007640000000136212560702257017646 0ustar pravipravimodule WebConsole module Helper # Communicates with the middleware to render a console in a +binding+. # # If +bidning+ isn't explicitly given, Binding#of_caller will be used to # get the binding of the previous frame. E.g. the one that invoked # +console+. # # Raises DoubleRenderError if a double +console+ invocation per request is # detected. def console(binding = nil) raise DoubleRenderError if request.env['web_console.binding'] request.env['web_console.binding'] = binding || ::Kernel.binding.of_caller(1) # Make sure nothing is rendered from the view helper. Otherwise # you're gonna see unexpected # in the # templates. nil end end end web-console-2.2.1/lib/web_console/templates/0000755000076400007640000000000012560702257020036 5ustar pravipraviweb-console-2.2.1/lib/web_console/templates/_prompt_box_markup.html.erb0000644000076400007640000000012612560702257025401 0ustar pravipravi

web-console-2.2.1/lib/web_console/templates/layouts/0000755000076400007640000000000012560702257021536 5ustar  pravipraviweb-console-2.2.1/lib/web_console/templates/layouts/javascript.erb0000644000076400007640000000012612560702257024375 0ustar  pravipravi
web-console-2.2.1/lib/web_console/templates/layouts/inlined_string.erb0000644000076400007640000000002112560702257025231 0ustar  pravipravi"<%= j yield %>"
web-console-2.2.1/lib/web_console/templates/_inner_console_markup.html.erb0000644000076400007640000000037112560702257026047 0ustar  pravipravi
x
web-console-2.2.1/lib/web_console/templates/style.css.erb0000644000076400007640000000334712560702257022466 0ustar pravipravi.console .pos-absolute { position: absolute; } .console .pos-fixed { position: fixed; } .console .pos-right { right: 0; } .console .border-box { box-sizing: border-box; } .console .layer { width: 100%; height: 100%; } .console .layer.console-outer { z-index: 1; } .console .layer.resizer { z-index: 2; } .console { position: fixed; left: 0; bottom: 0; width: 100%; height: 148px; padding: 0; margin: 0; background: none repeat scroll 0% 0% #333; z-index: 9999; } .console .console-outer { overflow: auto; padding-top: 4px; } .console .console-inner { font-family: monospace; font-size: 11px; width: 100%; height: 100%; overflow: none; background: #333; } .console .console-prompt-box { color: #FFF; } .console .console-message { color: #1AD027; margin: 0; border: 0; white-space: pre-wrap; background-color: #333; padding: 0; } .console .console-focus .console-cursor { background: #FEFEFE; color: #333; font-weight: bold; } .console .resizer { background: #333; width: 100%; height: 4px; cursor: ns-resize; } .console .console-actions { padding-right: 3px; } .console .console-actions .button { float: left; } .console .button { cursor: pointer; border-radius: 1px; font-family: monospace; font-size: 13px; width: 14px; height: 14px; line-height: 14px; text-align: center; color: #ccc; } .console .button:hover { background: #666; color: #fff; } .console .button.close-button:hover { background: #966; } .console .clipboard { height: 0px; padding: 0px; margin: 0px; width: 0px; margin-left: -1000px; } .console .console-prompt-label { display: inline; color: #FFF; background: none repeat scroll 0% 0% #333; border: 0; padding: 0; } .console .console-prompt-display { display: inline; color: #FFF; background: none repeat scroll 0% 0% #333; border: 0; padding: 0; } web-console-2.2.1/lib/web_console/templates/index.html.erb0000644000076400007640000000025212560702257022601 0ustar pravipravi<%= render 'markup' %> <%= render_javascript 'console' %> <%= render_javascript 'main' %> <% only_on_error_page do %> <%= render_javascript 'error_page' %> <% end %> web-console-2.2.1/lib/web_console/templates/error_page.js.erb0000644000076400007640000000465012560702257023275 0ustar pravipravi// Try intercept traces links in Rails 4.2. var traceFrames = document.getElementsByClassName('trace-frames'); var selectedFrame, currentSource = document.getElementById('frame-source-0'); // Add click listeners for all stack frames for (var i = 0; i < traceFrames.length; i++) { traceFrames[i].addEventListener('click', function(e) { e.preventDefault(); var target = e.target; var frameId = target.dataset.frameId; // Change the binding of the console. changeBinding(frameId, function() { if (selectedFrame) { selectedFrame.className = selectedFrame.className.replace("selected", ""); } target.className += " selected"; selectedFrame = target; }); // Change the extracted source code changeSourceExtract(frameId); }); } function changeBinding(frameId, callback) { REPLConsole.currentSession.switchBindingTo(frameId, callback); } function changeSourceExtract(frameId) { var el = document.getElementById('frame-source-' + frameId); if (currentSource && el) { currentSource.className += " hidden"; el.className = el.className.replace(" hidden", ""); currentSource = el; } } // Push the error page body upwards the size of the console. // // While, I wouldn't like to do that on every custom page (so I don't screw // user's layouts), I think a lot of developers want to see all of the content // on the default Rails error page. // // Since it's quite special as is now, being a bit more special in the name of // better user experience, won't hurt. document.addEventListener('DOMContentLoaded', function() { var consoleElement = document.getElementById('console'); var resizerElement = consoleElement.getElementsByClassName('resizer')[0]; var containerElement = document.getElementById('container'); function setContainerElementBottomMargin(pixels) { containerElement.style.marginBottom = pixels + 'px'; } var currentConsoleElementHeight = consoleElement.offsetHeight; setContainerElementBottomMargin(currentConsoleElementHeight); resizerElement.addEventListener('mousedown', function(event) { function recordConsoleElementHeight(event) { resizerElement.removeEventListener('mouseup', recordConsoleElementHeight); var currentConsoleElementHeight = consoleElement.offsetHeight; setContainerElementBottomMargin(currentConsoleElementHeight); } resizerElement.addEventListener('mouseup', recordConsoleElementHeight); }); }); web-console-2.2.1/lib/web_console/templates/_markup.html.erb0000644000076400007640000000017112560702257023130 0ustar pravipravi
web-console-2.2.1/lib/web_console/templates/console.js.erb0000644000076400007640000003424412560702257022614 0ustar pravipravi/** * Constructor for command storage. * It uses localStorage if available. Otherwise fallback to normal JS array. */ function CommandStorage() { this.previousCommands = []; var previousCommandOffset = 0; var hasLocalStorage = typeof window.localStorage !== 'undefined'; var STORAGE_KEY = "web_console_previous_commands"; var MAX_STORAGE = 100; if (hasLocalStorage) { this.previousCommands = JSON.parse(localStorage.getItem(STORAGE_KEY)) || []; previousCommandOffset = this.previousCommands.length; } this.addCommand = function(command) { previousCommandOffset = this.previousCommands.push(command); if (previousCommandOffset > MAX_STORAGE) { this.previousCommands.splice(0, 1); previousCommandOffset = MAX_STORAGE; } if (hasLocalStorage) { localStorage.setItem(STORAGE_KEY, JSON.stringify(this.previousCommands)); } }; this.navigate = function(offset) { previousCommandOffset += offset; if (previousCommandOffset < 0) { previousCommandOffset = -1; return null; } if (previousCommandOffset >= this.previousCommands.length) { previousCommandOffset = this.previousCommands.length; return null; } return this.previousCommands[previousCommandOffset]; } } // HTML strings for dynamic elements. var consoleInnerHtml = <%= render_inlined_string '_inner_console_markup.html' %>; var promptBoxHtml = <%= render_inlined_string '_prompt_box_markup.html' %>; // CSS var consoleStyleCss = <%= render_inlined_string 'style.css' %>; // Insert a style element with the unique ID var styleElementId = 'sr02459pvbvrmhco'; // REPLConsole Constructor function REPLConsole(config) { this.commandStorage = new CommandStorage(); this.prompt = config && config.promptLabel ? config.promptLabel : ' >>'; this.commandHandle = config && config.commandHandle ? config.commandHandle : function() { return this; } } REPLConsole.prototype.install = function(container) { var _this = this; document.onkeydown = function(ev) { if (_this.focused) { _this.onKeyDown(ev); } }; document.onkeypress = function(ev) { if (_this.focused) { _this.onKeyPress(ev); } }; document.addEventListener('mousedown', function(ev) { var el = ev.target || ev.srcElement; if (el) { do { if (el === container) { _this.focus(); return; } } while (el = el.parentNode); _this.blur(); } }); // Render the console. container.innerHTML = consoleInnerHtml; var consoleOuter = findChild(container, 'console-outer'); var consoleActions = findChild(consoleOuter, 'console-actions'); addClass(container, 'console'); addClass(container.getElementsByClassName('layer'), 'pos-absolute border-box'); addClass(container.getElementsByClassName('button'), 'border-box'); addClass(consoleActions, 'pos-fixed pos-right'); // Make the console resizable. function resizeContainer(ev) { var startY = ev.clientY; var startHeight = parseInt(document.defaultView.getComputedStyle(container).height, 10); var scrollTopStart = consoleOuter.scrollTop; var clientHeightStart = consoleOuter.clientHeight; var doDrag = function(e) { container.style.height = (startHeight + startY - e.clientY) + 'px'; consoleOuter.scrollTop = scrollTopStart + (clientHeightStart - consoleOuter.clientHeight); shiftConsoleActions(); }; var stopDrag = function(e) { document.documentElement.removeEventListener('mousemove', doDrag, false); document.documentElement.removeEventListener('mouseup', stopDrag, false); }; document.documentElement.addEventListener('mousemove', doDrag, false); document.documentElement.addEventListener('mouseup', stopDrag, false); } function closeContainer(ev) { container.parentNode.removeChild(container); } var shifted = false; function shiftConsoleActions() { if (consoleOuter.scrollHeight > consoleOuter.clientHeight) { var widthDiff = document.documentElement.clientWidth - consoleOuter.clientWidth; if (shifted || ! widthDiff) return; shifted = true; consoleActions.style.marginRight = widthDiff + 'px'; } else if (shifted) { shifted = false; consoleActions.style.marginRight = '0px'; } } // Initialize this.outer = consoleOuter; this.inner = findChild(this.outer, 'console-inner'); this.clipboard = findChild(container, 'clipboard'); this.remotePath = container.dataset.remotePath; this.newPromptBox(); this.insertCss(); findChild(container, 'resizer').addEventListener('mousedown', resizeContainer); findChild(consoleActions, 'close-button').addEventListener('click', closeContainer); consoleOuter.addEventListener('DOMNodeInserted', shiftConsoleActions); REPLConsole.currentSession = this; }; // Add CSS styles dynamically. This probably doesnt work for IE <8. REPLConsole.prototype.insertCss = function() { if (document.getElementById(styleElementId)) { return; // already inserted } var style = document.createElement('style'); style.type = 'text/css'; style.innerHTML = consoleStyleCss; style.id = styleElementId; document.getElementsByTagName('head')[0].appendChild(style); }; REPLConsole.prototype.focus = function() { if (! this.focused) { this.focused = true; if (! hasClass(this.inner, "console-focus")) { addClass(this.inner, "console-focus"); } this.scrollToBottom(); } }; REPLConsole.prototype.blur = function() { this.focused = false; removeClass(this.inner, "console-focus"); }; /** * Add a new empty prompt box to the console. */ REPLConsole.prototype.newPromptBox = function() { // Remove the caret from previous prompt display if any. if (this.promptDisplay) { this.removeCaretFromPrompt(); } var promptBox = document.createElement('div'); promptBox.className = "console-prompt-box"; promptBox.innerHTML = promptBoxHtml; this.promptLabel = promptBox.getElementsByClassName('console-prompt-label')[0]; this.promptDisplay = promptBox.getElementsByClassName('console-prompt-display')[0]; // Render the prompt box this.setInput(""); this.promptLabel.innerHTML = this.prompt; this.inner.appendChild(promptBox); this.scrollToBottom(); }; /** * Remove the caret from the prompt box, * mainly before adding a new prompt box. * For simplicity, just re-render the prompt box * with caret position -1. */ REPLConsole.prototype.removeCaretFromPrompt = function() { this.setInput(this._input, -1); }; REPLConsole.prototype.setInput = function(input, caretPos) { this._caretPos = caretPos === undefined ? input.length : caretPos; this._input = input; this.renderInput(); }; /** * Add some text to the existing input. */ REPLConsole.prototype.addToInput = function(val, caretPos) { caretPos = caretPos || this._caretPos; var before = this._input.substring(0, caretPos); var after = this._input.substring(caretPos, this._input.length); var newInput = before + val + after; this.setInput(newInput, caretPos + val.length); }; /** * Render the input prompt. This is called whenever * the user input changes, sometimes not very efficient. */ REPLConsole.prototype.renderInput = function() { // Clear the current input. removeAllChildren(this.promptDisplay); var promptCursor = document.createElement('span'); promptCursor.className = "console-cursor"; var before, current, after; if (this._caretPos < 0) { before = this._input; current = after = ""; } else if (this._caretPos === this._input.length) { before = this._input; current = "\u00A0"; after = ""; } else { before = this._input.substring(0, this._caretPos); current = this._input.charAt(this._caretPos); after = this._input.substring(this._caretPos + 1, this._input.length); } this.promptDisplay.appendChild(document.createTextNode(before)); promptCursor.appendChild(document.createTextNode(current)); this.promptDisplay.appendChild(promptCursor); this.promptDisplay.appendChild(document.createTextNode(after)); }; REPLConsole.prototype.writeOutput = function(output) { var consoleMessage = document.createElement('pre'); consoleMessage.className = "console-message"; consoleMessage.innerHTML = escapeHTML(output); this.inner.appendChild(consoleMessage); this.newPromptBox(); }; REPLConsole.prototype.onEnterKey = function() { var input = this._input; if(input != "" && input !== undefined) { this.commandStorage.addCommand(input); } this.commandHandle(input); }; REPLConsole.prototype.onNavigateHistory = function(offset) { var command = this.commandStorage.navigate(offset) || ""; this.setInput(command); }; /** * Handle control keys like up, down, left, right. */ REPLConsole.prototype.onKeyDown = function(ev) { switch (ev.keyCode) { case 13: // Enter key this.onEnterKey(); ev.preventDefault(); break; case 80: // Ctrl-P if (! ev.ctrlKey) break; case 38: // Up arrow this.onNavigateHistory(-1); ev.preventDefault(); break; case 78: // Ctrl-N if (! ev.ctrlKey) break; case 40: // Down arrow this.onNavigateHistory(1); ev.preventDefault(); break; case 37: // Left arrow var caretPos = this._caretPos > 0 ? this._caretPos - 1 : this._caretPos; this.setInput(this._input, caretPos); ev.preventDefault(); break; case 39: // Right arrow var length = this._input.length; var caretPos = this._caretPos < length ? this._caretPos + 1 : this._caretPos; this.setInput(this._input, caretPos); ev.preventDefault(); break; case 8: // Delete this.deleteAtCurrent(); ev.preventDefault(); break; default: break; } if (ev.ctrlKey || ev.metaKey) { // Set focus to our clipboard in case they hit the "v" key this.clipboard.focus(); if (ev.keyCode == 86) { // Pasting to clipboard doesn't happen immediately, // so we have to wait for a while to get the pasted text. var _this = this; setTimeout(function() { _this.addToInput(_this.clipboard.value); _this.clipboard.value = ""; _this.clipboard.blur(); }, 10); } } ev.stopPropagation(); }; /** * Handle input key press. */ REPLConsole.prototype.onKeyPress = function(ev) { // Only write to the console if it's a single key press. if (ev.ctrlKey || ev.metaKey) { return; } var keyCode = ev.keyCode || ev.which; this.insertAtCurrent(String.fromCharCode(keyCode)); ev.stopPropagation(); ev.preventDefault(); }; /** * Delete a character at the current position. */ REPLConsole.prototype.deleteAtCurrent = function() { if (this._caretPos > 0) { var caretPos = this._caretPos - 1; var before = this._input.substring(0, caretPos); var after = this._input.substring(this._caretPos, this._input.length); this.setInput(before + after, caretPos); } }; /** * Insert a character at the current position. */ REPLConsole.prototype.insertAtCurrent = function(char) { var before = this._input.substring(0, this._caretPos); var after = this._input.substring(this._caretPos, this._input.length); this.setInput(before + char + after, this._caretPos + 1); }; REPLConsole.prototype.scrollToBottom = function() { this.outer.scrollTop = this.outer.scrollHeight; }; // Change the binding of the console REPLConsole.prototype.switchBindingTo = function(frameId, callback) { var url = this.remotePath + "/trace"; var params = "frame_id=" + encodeURIComponent(frameId); postRequest(url, params, callback); }; /** * Install the console into the element with a specific ID. * Example: REPLConsole.installInto("target-id") */ REPLConsole.installInto = function(id) { var consoleElement = document.getElementById(id); var remotePath = consoleElement.dataset.remotePath; var replConsole = new REPLConsole({ promptLabel: consoleElement.dataset.initialPrompt, commandHandle: function(line) { var _this = this; var url = remotePath; var params = "input=" + encodeURIComponent(line); putRequest(url, params, function(xhr) { var response = JSON.parse(xhr.responseText); _this.writeOutput(response.output); }); } }); replConsole.install(consoleElement); return replConsole; }; // This is to store the latest single session, and the stored session // is updated by the REPLConsole#install() method. // It allows to operate the current session from the other scripts. REPLConsole.currentSession = null; // DOM helpers function hasClass(el, className) { var regex = new RegExp('(?:^|\\s)' + className + '(?!\\S)', 'g'); return el.className && el.className.match(regex); } function isNodeList(el) { return typeof el.length === 'number' && typeof el.item === 'function'; } function addClass(el, className) { if (isNodeList(el)) { for (var i = 0; i < el.length; ++ i) { addClass(el[i], className); } } else { el.className += " " + className; } } function removeClass(el, className) { var regex = new RegExp('(?:^|\\s)' + className + '(?!\\S)', 'g'); el.className = el.className.replace(regex, ''); } function removeAllChildren(el) { while (el.firstChild) { el.removeChild(el.firstChild); } } function findChild(el, className) { for (var i = 0; i < el.childNodes.length; ++ i) { if (hasClass(el.childNodes[i], className)) { return el.childNodes[i]; } } } function escapeHTML(html) { return html .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, ''') .replace(/`/g, '`'); } // XHR helpers function request(method, url, params, callback) { var xhr = new XMLHttpRequest(); xhr.open(method, url, true); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); xhr.setRequestHeader("Accept", "<%= Mime::WEB_CONSOLE_V2 %>"); xhr.send(params); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { callback(xhr); } } } function postRequest(url, params, callback) { request("POST", url, params, callback); } function putRequest(url, params, callback) { request("PUT", url, params, callback); } window.REPLConsole = REPLConsole; web-console-2.2.1/lib/web_console/templates/main.js.erb0000644000076400007640000000004412560702257022065 0ustar pravipraviREPLConsole.installInto('console'); web-console-2.2.1/lib/web_console/middleware.rb0000644000076400007640000000733112560702257020506 0ustar pravipravirequire 'active_support/core_ext/string/strip' module WebConsole class Middleware TEMPLATES_PATH = File.expand_path('../templates', __FILE__) DEFAULT_OPTIONS = { update_re: %r{/repl_sessions/(?.+?)\z}, binding_change_re: %r{/repl_sessions/(?.+?)/trace\z} } UNAVAILABLE_SESSION_MESSAGE = <<-END.strip_heredoc Session %{id} is is no longer available in memory. If you happen to run on a multi-process server (like Unicorn) the process this request hit doesn't store %{id} in memory. END UNACCEPTABLE_REQUEST_MESSAGE = "A supported version is expected in the Accept header." cattr_accessor :whiny_requests @@whiny_requests = true def initialize(app, options = {}) @app = app @options = DEFAULT_OPTIONS.merge(options) end def call(env) request = create_regular_or_whiny_request(env) return @app.call(env) unless request.from_whitelited_ip? if id = id_for_repl_session_update(request) return update_repl_session(id, request) elsif id = id_for_repl_session_stack_frame_change(request) return change_stack_trace(id, request) end status, headers, body = @app.call(env) if exception = env['web_console.exception'] session = Session.from_exception(exception) elsif binding = env['web_console.binding'] session = Session.from_binding(binding) end if session && request.acceptable_content_type? headers["X-Web-Console-Session-Id"] = session.id response = Rack::Response.new(body, status, headers) template = Template.new(env, session) response.write(template.render('index')) response.finish else [ status, headers, body ] end end private def json_response(opts = {}) status = opts.fetch(:status, 200) headers = { 'Content-Type' => 'application/json; charset = utf-8' } body = yield.to_json Rack::Response.new(body, status, headers).finish end def json_response_with_session(id, request, opts = {}) json_response(opts) do unless request.acceptable? return respond_with_unacceptable_request end unless session = Session.find(id) return respond_with_unavailable_session(id) end yield session end end def create_regular_or_whiny_request(env) request = Request.new(env) whiny_requests ? WhinyRequest.new(request) : request end def update_re @options[:update_re] end def binding_change_re @options[:binding_change_re] end def id_for_repl_session_update(request) if request.xhr? && request.put? update_re.match(request.path_info) { |m| m[:id] } end end def id_for_repl_session_stack_frame_change(request) if request.xhr? && request.post? binding_change_re.match(request.path_info) { |m| m[:id] } end end def update_repl_session(id, request) json_response_with_session(id, request) do |session| { output: session.eval(request.params[:input]) } end end def change_stack_trace(id, request) json_response_with_session(id, request) do |session| session.switch_binding_to(request.params[:frame_id]) { ok: true } end end def respond_with_unavailable_session(id) json_response(status: 404) do { output: format(UNAVAILABLE_SESSION_MESSAGE, id: id)} end end def respond_with_unacceptable_request json_response(status: 406) do { error: UNACCEPTABLE_REQUEST_MESSAGE } end end end end web-console-2.2.1/Rakefile0000644000076400007640000000253512560702257014445 0ustar pravipravibegin require 'bundler/setup' rescue LoadError puts 'You must `gem install bundler` and `bundle install` to run rake tasks' end require 'socket' require 'rake/testtask' require 'tmpdir' require 'securerandom' EXPANDED_CWD = File.expand_path(File.dirname(__FILE__)) Rake::TestTask.new(:test) do |t| t.libs << 'lib' t.libs << 'test' t.pattern = 'test/**/*_test.rb' t.verbose = false end namespace :test do desc "Run tests for templates" task :templates => "templates:all" namespace :templates do task :all => [:daemonize, :npm, :rackup, :mocha, :kill] task :serve => [:npm, :rackup] work_dir = Pathname(__FILE__).dirname.join("test/templates") pid_file = Pathname(Dir.tmpdir).join("web_console.#{SecureRandom.uuid}.pid") server_port = 29292 rackup_opts = "-p #{server_port}" task :daemonize do rackup_opts += " -D -P #{pid_file}" end task :npm do Dir.chdir(work_dir) { system "npm install --silent" } end task :rackup do Dir.chdir(work_dir) { system "bundle exec rackup #{rackup_opts}" } end task :mocha do Dir.chdir(work_dir) { system "$(npm bin)/mocha-phantomjs http://localhost:#{server_port}/html/spec_runner.html" } end task :kill do system "kill #{File.read pid_file}" end end end Bundler::GemHelper.install_tasks task default: :test web-console-2.2.1/MIT-LICENSE0000644000076400007640000000212612560702257014430 0ustar pravipraviCopyright 2014 Charlie Somerville, Genadi Samokovarov, Guillermo Iguaran and Ryan Dao Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.