pax_global_header00006660000000000000000000000064144757054070014527gustar00rootroot0000000000000052 comment=a3b7c2a89c13f8e9960a34df41674be5d3e47dcd web-console-4.2.1/000077500000000000000000000000001447570540700137505ustar00rootroot00000000000000web-console-4.2.1/.devcontainer/000077500000000000000000000000001447570540700165075ustar00rootroot00000000000000web-console-4.2.1/.devcontainer/devcontainer.json000066400000000000000000000016561447570540700220730ustar00rootroot00000000000000// For format details, see https://aka.ms/devcontainer.json. For config options, see the // README at: https://github.com/devcontainers/templates/tree/main/src/ruby { "name": "Ruby", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile "image": "mcr.microsoft.com/devcontainers/ruby:1-3.2-bullseye", "features": { "ghcr.io/devcontainers/features/github-cli:1": {} }, // Features to add to the dev container. More info: https://containers.dev/features. // "features": {}, // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. "postCreateCommand": "bundle install" // Configure tool-specific properties. // "customizations": {}, // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. // "remoteUser": "root" } web-console-4.2.1/.github/000077500000000000000000000000001447570540700153105ustar00rootroot00000000000000web-console-4.2.1/.github/workflows/000077500000000000000000000000001447570540700173455ustar00rootroot00000000000000web-console-4.2.1/.github/workflows/ci.yml000066400000000000000000000014371447570540700204700ustar00rootroot00000000000000name: CI on: - push - pull_request permissions: contents: read jobs: test: runs-on: ubuntu-latest continue-on-error: ${{ matrix.experimental }} strategy: fail-fast: false matrix: ruby: ['2.7', '3.0', '3.1', '3.2', 'head'] rails: [ '6.0', '6.1', '7.0', 'edge' ] script: [test] experimental: [false] include: - ruby: '2.7' rails: '7.0' script: templates:test experimental: true env: RAILS_VERSION: ${{ matrix.rails }} steps: - uses: actions/checkout@v3 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - name: Run tests run: bundle exec rake ${{ matrix.script }} web-console-4.2.1/.gitignore000066400000000000000000000003161447570540700157400ustar00rootroot00000000000000.bundle/ log/*.log pkg/ coverage/ test/dummy/db/*.sqlite3 test/dummy/db/*.sqlite3-journal test/dummy/log/*.log test/dummy/tmp/ test/dummy/.sass-cache Gemfile.lock node_modules/ package-lock.json dist/ tmp/ web-console-4.2.1/.rubocop.yml000066400000000000000000000070721447570540700162300ustar00rootroot00000000000000AllCops: TargetRubyVersion: 2.4 # RuboCop has a bunch of cops enabled by default. This setting tells RuboCop # to ignore them, so only the ones explicitly set in this file are enabled. DisabledByDefault: true Exclude: - '**/templates/**/*' - '**/vendor/**/*' # Prefer &&/|| over and/or. Style/AndOr: Enabled: true # Do not use braces for hash literals when they are the last argument of a # method call. Style/BracesAroundHashParameters: Enabled: true EnforcedStyle: context_dependent # Align `when` with `case`. Layout/CaseIndentation: Enabled: true # Align comments with method definitions. Layout/CommentIndentation: Enabled: true Layout/ElseAlignment: Enabled: true # Align `end` with the matching keyword or starting expression except for # assignments, where it should be aligned with the LHS. Layout/EndAlignment: Enabled: true EnforcedStyleAlignWith: variable AutoCorrect: true Layout/EmptyLineAfterMagicComment: Enabled: true # In a regular class definition, no empty lines around the body. Layout/EmptyLinesAroundClassBody: Enabled: true # In a regular method definition, no empty lines around the body. Layout/EmptyLinesAroundMethodBody: Enabled: true # In a regular module definition, no empty lines around the body. Layout/EmptyLinesAroundModuleBody: Enabled: true Layout/FirstParameterIndentation: Enabled: true # Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }. Style/HashSyntax: Enabled: true # Method definitions after `private` or `protected` isolated calls need one # extra level of indentation. Layout/IndentationConsistency: Enabled: true EnforcedStyle: rails # Two spaces, no tabs (for indentation). Layout/IndentationWidth: Enabled: true Layout/LeadingCommentSpace: Enabled: true Layout/SpaceAfterColon: Enabled: true Layout/SpaceAfterComma: Enabled: true Layout/SpaceAroundEqualsInParameterDefault: Enabled: true Layout/SpaceAroundKeyword: Enabled: true Layout/SpaceAroundOperators: Enabled: true Layout/SpaceBeforeComma: Enabled: true Layout/SpaceBeforeFirstArg: Enabled: true Style/DefWithParentheses: Enabled: true # Defining a method with parameters needs parentheses. Style/MethodDefParentheses: Enabled: true Style/FrozenStringLiteralComment: Enabled: true EnforcedStyle: always Exclude: - 'actionview/test/**/*.builder' - 'actionview/test/**/*.ruby' - 'actionpack/test/**/*.builder' - 'actionpack/test/**/*.ruby' - 'activestorage/db/migrate/**/*.rb' # Use `foo {}` not `foo{}`. Layout/SpaceBeforeBlockBraces: Enabled: true # Use `foo { bar }` not `foo {bar}`. Layout/SpaceInsideBlockBraces: Enabled: true # Use `{ a: 1 }` not `{a:1}`. Layout/SpaceInsideHashLiteralBraces: Enabled: true Layout/SpaceInsideParens: Enabled: true # Check quotes usage according to lint rule below. Style/StringLiterals: Enabled: true EnforcedStyle: double_quotes # Detect hard tabs, no hard tabs. Layout/Tab: Enabled: true # Blank lines should not have any spaces. Layout/TrailingBlankLines: Enabled: true # No trailing whitespace. Layout/TrailingWhitespace: Enabled: true # Use quotes for string literals when they are enough. Style/UnneededPercentQ: Enabled: true # Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg. Lint/RequireParentheses: Enabled: true Lint/StringConversionInInterpolation: Enabled: true Style/RedundantReturn: Enabled: true AllowMultipleReturnValues: true Style/Semicolon: Enabled: true AllowAsExpressionSeparator: true # Prefer Foo.method over Foo::method Style/ColonMethodCall: Enabled: true web-console-4.2.1/CHANGELOG.markdown000066400000000000000000000227611447570540700170130ustar00rootroot00000000000000# CHANGELOG ## main (unreleased) # 4.2.1 * Support to Rails 7.1 * Support to Rack 3.0 ## 4.2.0 * [#308](https://github.com/rails/web-console/pull/308) Fix web-console inline templates rendering ([@voxik]) * [#306](https://github.com/rails/web-console/pull/306) Support Ruby 3.0 and above ([@ryanwood]) ## 4.1.0 * [#304](https://github.com/rails/web-console/pull/304) Add support for Rails 6.1 ([@stephannv]) * [#298](https://github.com/rails/web-console/pull/298) Prevent deprecation warnings by removing template formats ([@mikelkew]) * [#297](https://github.com/rails/web-console/pull/297) Use MutationObserver instead of Mutation Events ([@mikelkew]) * [#296](https://github.com/rails/web-console/pull/296) Add CSP nonce to injected scripts and styles ([@mikelkew]) ## 4.0.4 * [fb483743](https://github.com/rails/web-console/commit/fb483743a6a2a4168cdc0b2e03f48fc393991b73) Fix a crash on webrick with Rack 2.2.3 ([@gsamokovarov]) ## 4.0.3 * [#291](https://github.com/rails/web-console/pull/291) Deprecate config.web_console.whitelisted_ips ([@JuanitoFatas]) * [#290](https://github.com/rails/web-console/pull/290) Fix Content-Length for rack >= 2.1.0 ([@p8]) ## 4.0.2 * [#285](https://github.com/rails/web-console/pull/285) Increase timeout on paste ([@celvro]) ## 4.0.1 * [#279](https://github.com/rails/web-console/pull/279) Fix initial config.web_console.permissions value ([@patorash]) ## 4.0.0 * [61ce65b5](https://github.com/rails/web-console/commit/61ce65b599f56809de1bd8da6590a80acbd92017) Move to config.web_console.permissions ([@gsamokovarov]) * [96127ac1](https://github.com/rails/web-console/commit/96127aac143e1e653fffdc4bb65e1ce0b5ff342d) Introduce Binding#console as an alternative interface ([@gsamokovarov]) * [d4591ca5](https://github.com/rails/web-console/commit/d4591ca5396ed15a08818f3da11134852a485b27) Introduce Rails 6 support ([@gsamokovarov]) * [f97d8a88](https://github.com/rails/web-console/commit/f97d8a889a38366485e5c5e8985995c19bf61d13) Introduce Ruby 2.6 support ([@gsamokovarov]) * [d6deacd9](https://github.com/rails/web-console/commit/d6deacd9d5fcaabf3e3051d6985b53f924f86956) Drop Rails 5 support ([@gsamokovarov]) * [90fda878](https://github.com/rails/web-console/commit/90fda8789d402f05647c18f8cdf8e5c3d01692dd) Drop Ruby 2.4 support ([@gsamokovarov]) * [#265](https://github.com/rails/web-console/pull/265) Add support for nested exceptions ([@yuki24]) ## 3.7.0 * [#263](https://github.com/rails/web-console/pull/263) Show binding changes ([@causztic]) * [#258](https://github.com/rails/web-console/pull/258) Support Ctrl-A, Ctrl-W and Ctrl-U ([@gsamokovarov]) * [#257](https://github.com/rails/web-console/pull/257) Always try to keep the console underneath the website content ([@gsamokovarov]) ## 3.6.2 * [#255](https://github.com/rails/web-console/pull/255) Fix the truncated HTML body, because of wrong Content-Length header ([@timomeh]) ## 3.6.1 * [#252](https://github.com/rails/web-console/pull/252) Fix improper injection in Rack bodies like ActionDispatch::Response::RackBody ([@gsamokovarov]) ## 3.6.0 * [#254](https://github.com/rails/web-console/pull/254) Rescue ActionDispatch::RemoteIp::IpSpoofAttackError ([@wjordan]) * [#250](https://github.com/rails/web-console/pull/250) Close original body to comply with Rack SPEC ([@wagenet]) * [#249](https://github.com/rails/web-console/pull/249) Update for frozen-string-literal friendliness ([@pat]) * [#248](https://github.com/rails/web-console/pull/248) Fix copy on Safari ([@ybart]) * [#246](https://github.com/rails/web-console/pull/246) International keyboard special character input fixes ([@fl0l0u]) * [#244](https://github.com/rails/web-console/pull/244) Let WebConsole.logger respect Rails.logger ([@gsamokovarov]) ## 3.5.1 * [#239](https://github.com/rails/web-console/pull/239) Fix the ActionDispatch::DebugExceptions integration ([@gsamokovarov]) ## 3.5.0 * [#237](https://github.com/rails/web-console/pull/237) Bindex integration for JRuby 9k support ([@gsamokovarov]) * [#236](https://github.com/rails/web-console/pull/236) Remove unused Active Support lazy load hook ([@betesh]) * [#230](https://github.com/rails/web-console/pull/230) Handle invalid remote addresses ([@akirakoyasu]) ## 3.4.0 * [#205](https://github.com/rails/web-console/pull/205) Introduce autocompletion ([@sh19910711]) ## 3.3.1 Drop support for Rails `4.2.0`. ## 3.3.0 * [#203](https://github.com/rails/web-console/pull/203) Map bindings to traces based on the trace __FILE__ and __LINE__ ([@gsamokovarov]) ## 3.2.1 * [#202](https://github.com/rails/web-console/pull/202) Use first binding when there is no application binding ([@sh19910711]) ## 3.2.0 * [#198](https://github.com/rails/web-console/pull/198) Pick the first application trace binding on errors ([@sh19910711]) * [#189](https://github.com/rails/web-console/pull/189) Silence ActionView rendering information ([@gsamokovarov]) ## 3.1.1 * [#185](https://github.com/rails/web-console/pull/185) Fix `rails console` startup ([@gsamokovarov]) ## 3.1.0 * [#182](https://github.com/rails/web-console/pull/182) Let `#console` live in `Kernel` ([@schneems]) * [#181](https://github.com/rails/web-console/pull/181) Log internal Web Console errors ([@gsamokovarov]) * [#180](https://github.com/rails/web-console/pull/180) Autoload Web Console constants for faster Rails boot time ([@herminiotorres]) ## 3.0.0 * [#173](https://github.com/rails/web-console/pull/173) Revert "Change config.development_only default until 4.2.4 is released" ([@gsamokovarov]) * [#171](https://github.com/rails/web-console/pull/171) Fixed blocked IP logging ([@gsamokovarov]) * [#162](https://github.com/rails/web-console/pull/162) Render the console inside the body tag ([@gsamokovarov]) * [#165](https://github.com/rails/web-console/pull/165) Revamped integrations for CRuby and Rubinius ([@gsamokovarov]) ## 2.3.0 This is mainly a Rails 5 compatibility release. If you have the chance, please go to 3.1.0 instead. * [#181](https://github.com/rails/web-console/pull/181) Log internal Web Console errors ([@schneems]) * [#150](https://github.com/rails/web-console/pull/150) Revert #150. ([@gsamokovarov]) ## 2.2.1 * [#150](https://github.com/rails/web-console/pull/150) Change config.development_only default until 4.2.4 is released ([@gsamokovarov]) ## 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]) [@stephannv]: https://github.com/stephannv [@mikelkew]: https://github.com/mikelkew [@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 [@herminiotorres]: https://github.com/herminiotorres [@schneems]: https://github.com/schneems [@betesh]: https://github.com/betesh [@akirakoyasu]: https://github.com/akirakoyasu [@wagenet]: https://github.com/wagenet [@wjordan]: https://github.com/wjordan [@pat]: https://github.com/pat [@ybart]: https://github.com/ybart [@fl0l0u]: https://github.com/fl0l0u [@timomeh]: https://github.com/timomeh [@causztic]: https://github.com/causztic [@yuki24]: https://github.com/yuki24 [@patorash]: https://github.com/patorash [@celvro]: https://github.com/celvro [@JuanitoFatas]: https://github.com/JuanitoFatas [@p8]: https://github.com/p8 [@voxik]: https://github.com/voxik [@ryanwood]: https://github.com/ryanwood web-console-4.2.1/Gemfile000066400000000000000000000010061447570540700152400ustar00rootroot00000000000000# frozen_string_literal: true source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } gemspec if ENV["RAILS_VERSION"] == "edge" gem "rails", github: "rails/rails", branch: "main" elsif ENV["RAILS_VERSION"] gem "activesupport", "~> #{ENV["RAILS_VERSION"]}.0" else gem "rails" end gem "rack" group :development do platform :ruby do gem "byebug" end gem "puma" end group :test do gem "rake" gem "mocha", require: false gem "simplecov", require: false end web-console-4.2.1/MIT-LICENSE000066400000000000000000000021321447570540700154020ustar00rootroot00000000000000Copyright 2014-2016 Hailey 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. web-console-4.2.1/README.markdown000066400000000000000000000135161447570540700164570ustar00rootroot00000000000000

Current version: 4.2.1 | Documentation for: v1.0.4 v2.2.1 v3.7.0

# Web Console [![CI](https://github.com/rails/web-console/actions/workflows/ci.yml/badge.svg)](https://github.com/rails/web-console/actions/workflows/ci.yml) _Web Console_ is a debugging tool for your Ruby on Rails applications. - [Installation](#installation) - [Configuration](#configuration) - [Usage](#usage) - [FAQ](#faq) - [Credits](#credits) ## Installation Add the following to your `Gemfile`: ```ruby group :development do gem 'web-console' end ``` ## Usage The web console allows you to create an interactive Ruby session in your browser. Those sessions are launched automatically in case of an error and can also be launched manually 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 ``` The method is defined in `Kernel` and you can invoke it any application code. Only one `console` invocation per request is allowed. If you happen to have multiple ones, `WebConsole::DoubleRenderError` will be raised. ## Configuration _Web Console_ allows you to execute arbitrary code on the server. Therefore, be very careful who you give access to. ### config.web_console.permissions By default, only requests coming from IPv4 and IPv6 localhosts are allowed. `config.web_console.permissions` lets you control which IP's have access to the console. You can allow single IP's or whole networks. Say you want to share your console with `192.168.0.100`: ```ruby class Application < Rails::Application config.web_console.permissions = '192.168.0.100' end ``` If you want to allow the whole private network: ```ruby Rails.application.configure do config.web_console.permissions = '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, messages such as 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 want to 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 want to style the console yourself, then 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 want to check the [templates] folder at the source tree for the files you may override. ### config.web_console.mount_point Usually the middleware of _Web Console_ is mounted at `/__web_console`. If there is a need to change the path, then you can specify it by `config.web_console.mount_point`: ```ruby Rails.application.configure do config.web_console.mount_point = '/path/to/web_console' end ``` ## FAQ ### Where did /console go? The remote terminal emulator was extracted in its own gem which is no longer bundled with _Web Console_. If you miss this feature, check out [rvt]. ### Why do 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 encounter 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 serve requests only out of one process. #### Passenger Enable sticky sessions for [Passenger on Nginx] or [Passenger on Apache] to prevent unavailable session errors. ### 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 the 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 am I getting an undefined method `web_console`? Make sure your configuration lives in `config/environments/development.rb`. ## Credits * Shoutout to [Hailey Somerville] for [better_errors]. * Kudos to [John Mair] for [binding_of_caller] and [debug_inspector]. * Thanks to [Charles Oliver Nutter] for all the _JRuby_ feedback. * Hugs and kisses to all of our [contributors]! [better_errors]: https://github.com/BetterErrors/better_errors [debug_inspector]: https://github.com/banister/debug_inspector [binding_of_caller]: https://github.com/banister/binding_of_caller [Hailey Somerville]: https://github.com/haileys [John Mair]: https://github.com/banister [Charles Oliver Nutter]: https://github.com/headius [templates]: https://github.com/rails/web-console/tree/main/lib/web_console/templates [rvt]: https://github.com/gsamokovarov/rvt [contributors]: https://github.com/rails/web-console/graphs/contributors [Passenger on Nginx]: https://www.phusionpassenger.com/library/config/nginx/reference/#passengerstickysessions [Passenger on Apache]: https://www.phusionpassenger.com/library/config/apache/reference/#passengerstickysessions web-console-4.2.1/Rakefile000066400000000000000000000011461447570540700154170ustar00rootroot00000000000000# frozen_string_literal: true begin 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" require "json" require "web_console/testing/erb_precompiler" 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 Dir["lib/web_console/tasks/**/*.rake"].each { |task| load task } Bundler::GemHelper.install_tasks task default: :test web-console-4.2.1/extensions/000077500000000000000000000000001447570540700161475ustar00rootroot00000000000000web-console-4.2.1/extensions/README.markdown000066400000000000000000000003031447570540700206440ustar00rootroot00000000000000# Web Console Browser Extensions ## Development ### Quickstart ``` $ git clone https://github.com/rails/web-console.git $ cd web-console $ bundle install $ bundle exec rake ext:chrome:run ``` web-console-4.2.1/extensions/chrome/000077500000000000000000000000001447570540700174245ustar00rootroot00000000000000web-console-4.2.1/extensions/chrome/html/000077500000000000000000000000001447570540700203705ustar00rootroot00000000000000web-console-4.2.1/extensions/chrome/html/devtools.html000066400000000000000000000001111447570540700231060ustar00rootroot00000000000000 web-console-4.2.1/extensions/chrome/html/panel.html000066400000000000000000000003221447570540700223520ustar00rootroot00000000000000
web-console-4.2.1/extensions/chrome/js/000077500000000000000000000000001447570540700200405ustar00rootroot00000000000000web-console-4.2.1/extensions/chrome/js/background.js000066400000000000000000000047441447570540700225260ustar00rootroot00000000000000var sessions = {}; var ports = {}; initPanelMessage(); initReqRes(); initHttpListener(); initNavListener(); function panelMessage(tabId, type, msg) { msg = msg || {}; msg.type = type; if (ports[tabId]) { ports[tabId].postMessage(msg); } } function sendSession(tabId) { panelMessage(tabId, 'update-session', sessions[tabId]); } function removeConsole(tabId) { panelMessage(tabId, 'remove-console'); } function initPanelMessage() { chrome.runtime.onConnect.addListener(onConnect); function handleMessage(msg) { if (msg.type === 'session') { sendSession(msg.tabId); } } function onConnect(newPort) { ports[newPort.name] = newPort; newPort.onMessage.addListener(handleMessage); } } function initReqRes() { chrome.runtime.onMessage.addListener(handleMessage); function extractProps(xhr) { var props = {}; for (var key in xhr) { if (typeof xhr[key] === 'string' || typeof xhr[key] === 'number') { props[key] = xhr[key]; } } return props; } function handleMessage(req, sender, sendResponse) { if (req.type === 'request') { var url = sessions[req.tabId].remoteHost + '/' + req.url; REPLConsole.request(req.method, url, req.params, function(xhr) { sendResponse(extractProps(xhr)); }); } return true; } } function initHttpListener() { var requestFilter = { types: [ 'main_frame' ], urls: [ 'http://*/*', 'https://*/*' ] }; // Fired when a request is completed. chrome.webRequest.onCompleted.addListener( onResponse, requestFilter, [ 'responseHeaders' ] ); function getHeaders(details) { return details.responseHeaders.reduce(reduceFunc, {}); } function reduceFunc(obj, header) { obj[header.name] = header.value; return obj; } function onResponse(details) { var headers = getHeaders(details); var sessionId; if (sessionId = headers['x-web-console-session-id']) { sessions[details.tabId] = { sessionId: sessionId, mountPoint: headers['x-web-console-mount-point'], remoteHost: details.url.match(/([^:]+:\/\/[^\/]+)\/?/)[1] }; } } } function initNavListener() { // Fired when a document is completely loaded and initialized. chrome.webNavigation.onCompleted.addListener(function(details) { if (filter(details)) { sendSession(details.tabId); removeConsole(details.tabId); } }); function filter(details) { return details.frameId === 0 && sessions[details.tabId]; } } web-console-4.2.1/extensions/chrome/js/devtools.js000066400000000000000000000002201447570540700222270ustar00rootroot00000000000000var label = 'Console (Rails)'; var icon = 'img/icon_128.png'; var html = 'html/panel.html'; chrome.devtools.panels.create(label, icon, html); web-console-4.2.1/extensions/chrome/js/panel.js000066400000000000000000000022141447570540700214740ustar00rootroot00000000000000var tabId = chrome.devtools.inspectedWindow.tabId; var port = chrome.runtime.connect({ name: tabId.toString() }); var repl; // We need to avoid the sandbox of Chrome DevTools via the messaging system. REPLConsole.request = function(method, url, params, callback) { chrome.runtime.sendMessage({ tabId: tabId, type: 'request', method: method, url: url, params: params }, callback); }; // Handle messages from the background script. port.onMessage.addListener(function(msg) { if (msg.type === 'update-session') { updateSession(msg); } else if (msg.type === 'remove-console') { removeConsole(); } }); function updateSession(info) { if (repl) { repl.sessionId = info.sessionId; repl.mountPoint = info.mountPoint; } else { var options = { sessionId: info.sessionId, mountPoint: info.mountPoint }; repl = REPLConsole.installInto('console', options); } } function removeConsole() { var script = 'if (REPLConsole && REPLConsole.currentSession) REPLConsole.currentSession.uninstall()'; chrome.devtools.inspectedWindow.eval(script); } port.postMessage({ type: 'session', tabId: tabId }); removeConsole(); web-console-4.2.1/extensions/chrome/manifest.json000066400000000000000000000006241447570540700221270ustar00rootroot00000000000000{ "name": "Rails Web Console", "version": "0.0.0", "manifest_version": 2, "background": { "scripts": ["lib/console.js", "js/background.js"] }, "icons": { "128": "img/icon_128.png" }, "devtools_page": "html/devtools.html", "permissions": [ "webRequest", "webNavigation", "http://*/*", "https://*/*" ], "homepage_url": "https://github.com/rails/web-console" } web-console-4.2.1/extensions/img/000077500000000000000000000000001447570540700167235ustar00rootroot00000000000000web-console-4.2.1/extensions/img/icon_128.png000066400000000000000000000070401447570540700207540ustar00rootroot00000000000000PNG  IHDR>asBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< IDATx{pTU?w# !eH1hdj٩QkuuTT``Ĕ,S[V`25[?NA H $1ژ1Fʀy@پ_:}{sOKeee2b@t@t@t@t@t@t@t@t@t@t@t@t@Fl1X,ֿ@$6oݻȈ1԰o>nԟ@$6mĚ5kXh{e1=jq.\Ⱦ}(((iaxD4ygyGBUU\x1׭[ǓO>ygNee%}}}|WQ+Z֮]{f+V099Ʌ V駟uĊ+$ΨWB P]]O<1%I'++3g 4cQSSs%%%̛7I0ԴRRݛQPP@aa!'OiRGeÆ ޛOii)NǣI}5$d$(~}Gss3sդȈ*N!Aݜ?ÁjU&55~ .pс ))IQX)R~;Fyy9iiiXV***'9r ~+%arss1,_ڐr\G^^6$QVVp:ڊlfŊRPPɓ'a|#Iv7"evYt){wp8p:\z5cŋ8F6nF  ػwoa+ +ߌ 77FU]jyU hjjߎV%KsNū{@۰UTTĮ]TMŮ]p:WJB L^oo/uuu\t)M-ՐC999:̙3lܸQT]]]lذ! "O>d2dɒ[СC444h2:q\楗^jҿ )@GGeƇ>BfO|> Y)--阮 hoofhѢrؾ};~T={@qq^x<ܹFZVk<|1?r4<<̶m4Y@ /Su֨ ne˖YARYYY`h4j2.%}CB܈P(vnl|Hqr$_WpC1b>_ťeh:B03/|zэ@e&O|J'T^4D5>&W_e+0&wB(qG~&~z`> @)Yq\#/vjX%23 sq&3&p("QYf֡|Ed&1Ph$l/xKⷲf3~L*ulgF2mH.$l??SL$}d^`h:}ޢ .2$aOtv:7݄1'ٻ옿Tew+퇏q0*I$Rq6uz55!@鿱P1sa?#i݅D9k]`3#@0 jL8bpnzÄ KFûoeȌlT`DKbk >qHdS^_9c!T2$DoUC &cBw0نeOymn*f1q ɧ.OH~ IE.LD@-0VP Y_Ė sZH96(adsCc1@]fY"@@ol:ʄc.1!F6 f jLXCAԨ[a@ƞ$CY0XI-އqL}4~vHobe׳oj#]!Eq`6Y",$͆BB(_bt`Hu 0fL2~i k9Q R#{m?RQ } $E$2; %D%3#[ `_KQdA v ߦj߀h#HpR`IWDkk cgq=6#A<[@,|!!!?:kIENDB`web-console-4.2.1/extensions/package.json000066400000000000000000000001321447570540700204310ustar00rootroot00000000000000{ "name": "dev", "version": "0.0.0", "devDependencies": { "crx": "^3.0.2" } } web-console-4.2.1/extensions/script/000077500000000000000000000000001447570540700174535ustar00rootroot00000000000000web-console-4.2.1/extensions/script/run_chrome.sh000066400000000000000000000006631447570540700221550ustar00rootroot00000000000000#!/bin/sh is_command() { type $1 > /dev/null 2>&1 } find_chrome_binary() { for name in 'chromium' 'google-chrome' 'chromium-browser'; do if is_command $name; then echo $name return fi done } CHROME_BINARY=${CHROME_BINARY:-`find_chrome_binary`} if is_command $CHROME_BINARY; then $CHROME_BINARY $@ else echo 'ERROR: Chrome is not found.' echo 'Please try "CHROME_BINARY=path/to/chrome".' exit 1 fi web-console-4.2.1/lib/000077500000000000000000000000001447570540700145165ustar00rootroot00000000000000web-console-4.2.1/lib/web-console.rb000066400000000000000000000000651447570540700172610ustar00rootroot00000000000000# frozen_string_literal: true require "web_console" web-console-4.2.1/lib/web_console.rb000066400000000000000000000014521447570540700173440ustar00rootroot00000000000000# frozen_string_literal: true require "active_support/dependencies/autoload" require "active_support/logger" module WebConsole extend ActiveSupport::Autoload autoload :View autoload :Evaluator autoload :ExceptionMapper autoload :Session autoload :Injector autoload :Interceptor autoload :Request autoload :WhinyRequest autoload :Permissions autoload :Template autoload :Middleware autoload :Context autoload :SourceLocation autoload_at "web_console/errors" do autoload :Error autoload :DoubleRenderError end def self.logger (defined?(Rails.logger) && Rails.logger) || (@logger ||= ActiveSupport::Logger.new($stderr)) end def self.deprecator @deprecator ||= ActiveSupport::Deprecation.new("5.0", "WebConsole") end end require "web_console/railtie" web-console-4.2.1/lib/web_console/000077500000000000000000000000001447570540700170155ustar00rootroot00000000000000web-console-4.2.1/lib/web_console/context.rb000066400000000000000000000020221447570540700210220ustar00rootroot00000000000000# frozen_string_literal: true module WebConsole # A context lets you get object names related to the current session binding. class Context def initialize(binding) @binding = binding end # Extracts entire objects which can be called by the current session unless # the inputs is present. # # Otherwise, it extracts methods and constants of the object specified by # the input. def extract(input = nil) input.present? ? local(input) : global end private GLOBAL_OBJECTS = [ "instance_variables", "local_variables", "methods", "class_variables", "Object.constants", "global_variables" ] def global GLOBAL_OBJECTS.map { |cmd| eval(cmd) } end def local(input) [ eval("#{input}.methods").map { |m| "#{input}.#{m}" }, eval("#{input}.constants").map { |c| "#{input}::#{c}" }, ] end def eval(cmd) @binding.eval(cmd) rescue [] end end end web-console-4.2.1/lib/web_console/errors.rb000066400000000000000000000004001447570540700206500ustar00rootroot00000000000000# frozen_string_literal: true module 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-4.2.1/lib/web_console/evaluator.rb000066400000000000000000000023341447570540700213460ustar00rootroot00000000000000# frozen_string_literal: true module 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, default: begin cleaner = ActiveSupport::BacktraceCleaner.new cleaner.add_silencer { |line| line.start_with?(File.expand_path("..", __FILE__)) } cleaner end def initialize(binding = TOPLEVEL_BINDING) @binding = binding end def eval(input) # Binding#source_location is available since Ruby 2.6. if @binding.respond_to? :source_location "=> #{@binding.eval(input, *@binding.source_location).inspect}\n" else "=> #{@binding.eval(input).inspect}\n" end 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".dup format << backtrace.map { |trace| "\tfrom #{trace}\n" }.join format end end end web-console-4.2.1/lib/web_console/exception_mapper.rb000066400000000000000000000024471447570540700227130ustar00rootroot00000000000000# frozen_string_literal: true module WebConsole class ExceptionMapper attr_reader :exc def self.follow(exc) mappers = [new(exc)] while cause = (cause || exc).cause mappers << new(cause) end mappers end def self.find_binding(mappers, exception_object_id) mappers.detect do |exception_mapper| exception_mapper.exc.object_id == exception_object_id.to_i end || mappers.first end def initialize(exception) @backtrace = exception.backtrace @bindings = exception.bindings @exc = exception end def first guess_the_first_application_binding || @bindings.first end def [](index) guess_binding_for_index(index) || @bindings[index] end private def guess_binding_for_index(index) file, line = @backtrace[index].to_s.split(":") line = line.to_i @bindings.find do |binding| source_location = SourceLocation.new(binding) source_location.path == file && source_location.lineno == line end end def guess_the_first_application_binding @bindings.find do |binding| source_location = SourceLocation.new(binding) source_location.path.to_s.start_with?(Rails.root.to_s) end end end end web-console-4.2.1/lib/web_console/extensions.rb000066400000000000000000000020001447570540700215310ustar00rootroot00000000000000# frozen_string_literal: true module Kernel module_function # Instructs Web Console to render a console in the specified binding. # # If +binding+ isn't explicitly given it will default to the binding of the # previous frame. E.g. the one that invoked +console+. # # Raises +DoubleRenderError+ if a more than one +console+ invocation per # request is detected. def console(binding = Bindex.current_bindings.second) raise WebConsole::DoubleRenderError if Thread.current[:__web_console_binding] Thread.current[:__web_console_binding] = binding # Make sure nothing is rendered from the view helper. Otherwise # you're gonna see unexpected # in the # templates. nil end end class Binding # Instructs Web Console to render a console in the current binding, without # the need to unroll the stack. # # Raises +DoubleRenderError+ if a more than one +console+ invocation per # request is detected. def console Kernel.console(self) end end web-console-4.2.1/lib/web_console/injector.rb000066400000000000000000000014071447570540700211610ustar00rootroot00000000000000# frozen_string_literal: true module WebConsole # Injects content into a Rack body. class Injector def initialize(body, headers) @body = "".dup body.each { |part| @body << part } body.close if body.respond_to?(:close) @headers = headers end def inject(content) # Set content-length header to the size of the current body # + the extra content. Otherwise the response will be truncated. if @headers[Rack::CONTENT_LENGTH] @headers[Rack::CONTENT_LENGTH] = (@body.bytesize + content.bytesize).to_s end [ if position = @body.rindex("") [ @body.insert(position, content) ] else [ @body << content ] end, @headers ] end end end web-console-4.2.1/lib/web_console/interceptor.rb000066400000000000000000000011641447570540700217020ustar00rootroot00000000000000module WebConsole module Interceptor def self.call(request, error) backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner") # Get the original exception if ExceptionWrapper decides to follow it. Thread.current[:__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) Thread.current[:__web_console_exception] = error.cause end end end end web-console-4.2.1/lib/web_console/locales/000077500000000000000000000000001447570540700204375ustar00rootroot00000000000000web-console-4.2.1/lib/web_console/locales/en.yml000066400000000000000000000011131447570540700215600ustar00rootroot00000000000000en: errors: unavailable_session: | Session %{id} is no longer available in memory. If you happen to run on a multi-process server (like Unicorn or Puma) the process this request hit doesn't store %{id} in memory. Consider turning the number of processes/workers to one (1) or using a different server in development. unacceptable_request: | A supported version is expected in the Accept header. connection_refused: | Oops! Failed to connect to the Web Console middleware. Please make sure a rails development server is running. web-console-4.2.1/lib/web_console/middleware.rb000066400000000000000000000077601447570540700214710ustar00rootroot00000000000000# frozen_string_literal: true require "active_support/core_ext/string/strip" module WebConsole class Middleware TEMPLATES_PATH = File.expand_path("../templates", __FILE__) cattr_accessor :mount_point, default: "/__web_console" cattr_accessor :whiny_requests, default: true def initialize(app) @app = app end def call(env) app_exception = catch :app_exception do request = create_regular_or_whiny_request(env) return call_app(env) unless request.permitted? 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 = call_app(env) if (session = Session.from(Thread.current)) && acceptable_content_type?(headers) headers["x-web-console-session-id"] = session.id headers["x-web-console-mount-point"] = mount_point template = Template.new(env, session) body, headers = Injector.new(body, headers).inject(template.render("index")) end [ status, headers, body ] end rescue => e WebConsole.logger.error("\n#{e.class}: #{e}\n\tfrom #{e.backtrace.join("\n\tfrom ")}") raise e ensure # Clean up the fiber locals after the session creation. Object#console # uses those to communicate the current binding or exception to the middleware. Thread.current[:__web_console_exception] = nil Thread.current[:__web_console_binding] = nil raise app_exception if Exception === app_exception end private def acceptable_content_type?(headers) headers[Rack::CONTENT_TYPE].to_s.include?("html") end def json_response(opts = {}) status = opts.fetch(:status, 200) headers = { Rack::CONTENT_TYPE => "application/json; charset = utf-8" } body = yield.to_json [ status, headers, [ body ] ] end def json_response_with_session(id, request, opts = {}) return respond_with_unavailable_session(id) unless session = Session.find(id) json_response(opts) { yield session } end def create_regular_or_whiny_request(env) request = Request.new(env) whiny_requests ? WhinyRequest.new(request) : request end def repl_sessions_re @_repl_sessions_re ||= %r{#{mount_point}/repl_sessions/(?[^/]+)} end def update_re @_update_re ||= %r{#{repl_sessions_re}\z} end def binding_change_re @_binding_change_re ||= %r{#{repl_sessions_re}/trace\z} end def id_for_repl_session_update(request) if request.xhr? && request.put? update_re.match(request.path) { |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) { |m| m[:id] } end end def update_repl_session(id, request) json_response_with_session(id, request) do |session| if input = request.params[:input] { output: session.eval(input) } elsif input = request.params[:context] { context: session.context(input) } end end end def change_stack_trace(id, request) json_response_with_session(id, request) do |session| session.switch_binding_to(request.params[:frame_id], request.params[:exception_object_id]) { ok: true } end end def respond_with_unavailable_session(id) json_response(status: 404) do { output: format(I18n.t("errors.unavailable_session"), id: id) } end end def respond_with_unacceptable_request json_response(status: 406) do { output: I18n.t("errors.unacceptable_request") } end end def call_app(env) @app.call(env) rescue => e throw :app_exception, e end end end web-console-4.2.1/lib/web_console/permissions.rb000066400000000000000000000017201447570540700217150ustar00rootroot00000000000000# frozen_string_literal: true require "ipaddr" module WebConsole class Permissions # IPv4 and IPv6 localhost should be always allowed. ALWAYS_PERMITTED_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? { |permission| permission.include?(network.to_s) } rescue IPAddr::InvalidAddressError false end def to_s @networks.map(&method(:human_readable_ipaddr)).join(", ") end private def normalize_networks(networks) Array(networks).concat(ALWAYS_PERMITTED_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-4.2.1/lib/web_console/railtie.rb000066400000000000000000000055261447570540700210030ustar00rootroot00000000000000# frozen_string_literal: true require "rails/railtie" module WebConsole class Railtie < ::Rails::Railtie config.web_console = ActiveSupport::OrderedOptions.new initializer "web_console.initialize" do require "bindex" require "web_console/extensions" ActionDispatch::DebugExceptions.register_interceptor(Interceptor) 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. This 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 in 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.mount_point" do if mount_point = config.web_console.mount_point Middleware.mount_point = mount_point.chomp("/") end if root = Rails.application.config.relative_url_root Middleware.mount_point = File.join(root, Middleware.mount_point) end 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.deprecator" do |app| app.deprecators[:web_console] = WebConsole.deprecator if app.respond_to?(:deprecators) end initializer "web_console.permissions" do permissions = web_console_permissions Request.permissions = Permissions.new(permissions) end def web_console_permissions case when config.web_console.permissions config.web_console.permissions when config.web_console.allowed_ips config.web_console.allowed_ips when config.web_console.whitelisted_ips WebConsole.deprecator.warn(<<-MSG.squish) The config.web_console.whitelisted_ips is deprecated and will be ignored in future release of web_console. Please use config.web_console.allowed_ips instead. MSG config.web_console.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 initializer "i18n.load_path" do config.i18n.load_path.concat(Dir[File.expand_path("../locales/*.yml", __FILE__)]) end end end web-console-4.2.1/lib/web_console/request.rb000066400000000000000000000017711447570540700210400ustar00rootroot00000000000000# frozen_string_literal: true module WebConsole class Request < ActionDispatch::Request cattr_accessor :permissions, default: Permissions.new def permitted? permissions.include?(strict_remote_ip) end def strict_remote_ip GetSecureIp.new(self, permissions).to_s rescue ActionDispatch::RemoteIp::IpSpoofAttackError "[Spoofed]" end private class GetSecureIp < ActionDispatch::RemoteIp::GetIp def initialize(req, proxies) # After rails/rails@07b2ff0 ActionDispatch::RemoteIp::GetIp initializes # with a ActionDispatch::Request object instead of plain Rack # environment hash. Keep both @req and @env here, so we don't if/else # on Rails versions. @req = req @env = req.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-4.2.1/lib/web_console/session.rb000066400000000000000000000044461447570540700210350ustar00rootroot00000000000000# frozen_string_literal: true module WebConsole # A session lets you persist an +Evaluator+ instance in memory associated # with multiple bindings. # # Each newly created session is persisted into memory and you can find it # later by 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, default: {} 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 binding or exception in a storage. # # The storage is expected to respond to #[]. The binding is expected in # :__web_console_binding and the exception in :__web_console_exception. # # Can return nil, if no binding or exception have been preserved in the # storage. def from(storage) if exc = storage[:__web_console_exception] new(ExceptionMapper.follow(exc)) elsif binding = storage[:__web_console_binding] new([[binding]]) end end end # An unique identifier for every REPL. attr_reader :id def initialize(exception_mappers) @id = SecureRandom.hex(16) @exception_mappers = exception_mappers @evaluator = Evaluator.new(@current_binding = exception_mappers.first.first) 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, exception_object_id) bindings = ExceptionMapper.find_binding(@exception_mappers, exception_object_id) @evaluator = Evaluator.new(@current_binding = bindings[index.to_i]) end # Returns context of the current binding def context(objpath) Context.new(@current_binding).extract(objpath) end private def store_into_memory inmemory_storage[id] = self end end end web-console-4.2.1/lib/web_console/source_location.rb000066400000000000000000000006071447570540700225350ustar00rootroot00000000000000# frozen_string_literal: true module WebConsole class SourceLocation def initialize(binding) @binding = binding end if RUBY_VERSION >= "2.6" def path() @binding.source_location.first end def lineno() @binding.source_location.last end else def path() @binding.eval("__FILE__") end def lineno() @binding.eval("__LINE__") end end end end web-console-4.2.1/lib/web_console/tasks/000077500000000000000000000000001447570540700201425ustar00rootroot00000000000000web-console-4.2.1/lib/web_console/tasks/extensions.rake000066400000000000000000000035031447570540700232060ustar00rootroot00000000000000# frozen_string_literal: true namespace :ext do rootdir = Pathname("extensions") desc "Build Chrome Extension" task chrome: "chrome:build" namespace :chrome do dist = Pathname("dist/crx") extdir = rootdir.join(dist) manifest_json = rootdir.join("chrome/manifest.json") directory extdir task build: [ extdir, "lib:templates" ] do cd rootdir do cp_r [ "img/", "tmp/lib/" ], dist `cd chrome && git ls-files`.split("\n").each do |src| dest = dist.join(src) mkdir_p dest.dirname cp Pathname("chrome").join(src), dest end end end # Generate a .crx file. task crx: [ :build, :npm ] do out = "crx-web-console-#{JSON.parse(File.read(manifest_json))["version"]}.crx" cd(extdir) { sh "node \"$(npm bin)/crx\" pack ./ -p ../crx-web-console.pem -o ../#{out}" } end # Generate a .zip file for Chrome Web Store. task zip: [ :build ] do version = JSON.parse(File.read(manifest_json))["version"] cd(extdir) { sh "zip -r ../crx-web-console-#{version}.zip ./" } end desc "Launch a browser with the chrome extension." task run: [ :build ] do cd(rootdir) { sh "sh ./script/run_chrome.sh --load-extension=#{dist}" } end end task :npm do cd(rootdir) { sh "npm install --silent" } end namespace :lib do templates = Pathname("lib/web_console/templates") tmplib = rootdir.join("tmp/lib/") js_erb = FileList.new(templates.join("**/*.js.erb")) dirs = js_erb.pathmap("%{^#{templates},#{tmplib}}d") task templates: dirs + js_erb.pathmap("%{^#{templates},#{tmplib}}X") dirs.each { |d| directory d } rule ".js" => [ "%{^#{tmplib},#{templates}}X.js.erb" ] do |t| File.write(t.name, WebConsole::Testing::ERBPrecompiler.new(t.source).build) end end end web-console-4.2.1/lib/web_console/tasks/templates.rake000066400000000000000000000022411447570540700230030ustar00rootroot00000000000000# frozen_string_literal: true require "net/http" namespace :templates do desc "Run tests for templates" task test: [ :daemonize, :npm, :rackup, :wait, :mocha, :kill, :exit ] task serve: [ :npm, :rackup ] workdir = Pathname(EXPANDED_CWD).join("test/templates") pid = Pathname(Dir.tmpdir).join("web_console_test.pid") runner = URI.parse("http://#{ENV['IP'] || '127.0.0.1'}:#{ENV['PORT'] || 29292}/html/test_runner.html") rackup = "rackup --host #{runner.host} --port #{runner.port}" result = nil def need_to_wait?(uri) Net::HTTP.start(uri.host, uri.port) { |http| http.get(uri.path) } rescue Errno::ECONNREFUSED retry if yield end task :daemonize do rackup += " -D --pid #{pid}" end task :npm do Dir.chdir(workdir) { system "npm install --silent" } end task :rackup do Dir.chdir(workdir) { system rackup } end task :wait do cnt = 0 need_to_wait?(runner) { sleep 1; cnt += 1; cnt < 5 } end task :mocha do Dir.chdir(workdir) { result = system("npx mocha-headless-chrome -f #{runner} -r dot") } end task :kill do system "kill #{File.read pid}" end task :exit do exit result end end web-console-4.2.1/lib/web_console/template.rb000066400000000000000000000014151447570540700211560ustar00rootroot00000000000000# frozen_string_literal: true module 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 # Lets you customize the default templates folder location. cattr_accessor :template_paths, default: [ File.expand_path("../templates", __FILE__) ] def initialize(env, session) @env = env @session = session @mount_point = Middleware.mount_point end # Render a template (inferred from +template_paths+) as a plain string. def render(template) view = View.with_empty_template_cache.with_view_paths(template_paths, instance_values) view.render(template: template, layout: false) end end end web-console-4.2.1/lib/web_console/templates/000077500000000000000000000000001447570540700210135ustar00rootroot00000000000000web-console-4.2.1/lib/web_console/templates/_inner_console_markup.html.erb000066400000000000000000000003711447570540700270240ustar00rootroot00000000000000
x
web-console-4.2.1/lib/web_console/templates/_markup.html.erb000066400000000000000000000002041447570540700241020ustar00rootroot00000000000000
web-console-4.2.1/lib/web_console/templates/_prompt_box_markup.html.erb000066400000000000000000000001261447570540700263560ustar00rootroot00000000000000

web-console-4.2.1/lib/web_console/templates/console.js.erb000066400000000000000000000713031447570540700235660ustar00rootroot00000000000000/**
 * 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];
  }
}

function Autocomplete(_words, prefix) {
  this.words = prepareWords(_words);
  this.current = -1;
  this.left = 0; // [left, right)
  this.right = this.words.length;
  this.confirmed = false;

  function createSpan(label, className) {
    var el = document.createElement('span');
    addClass(el, className);
    el.innerText = label;
    return el;
  }

  function prepareWords(words) {
    // convert into an object with priority and element
    var res = new Array(words.length);
    for (var i = 0, ind = 0; i < words.length; ++i) {
      res[i] = new Array(words[i].length);
      for (var j = 0; j < words[i].length; ++j) {
        res[i][j] = {
          word: words[i][j],
          priority: i,
          element: createSpan(words[i][j], 'trimmed keyword')
        };
      }
    }
    // flatten and sort by alphabetical order to refine incrementally
    res = flatten(res);
    res.sort(function(a, b) { return a.word == b.word ? 0 : (a.word < b.word ? -1 : 1); });
    for (var i = 0; i < res.length; ++i) res[i].element.dataset.index = i;
    return res;
  }

  this.view = document.createElement('pre');
  addClass(this.view, 'auto-complete console-message');
  this.view.appendChild(this.prefix = createSpan('...', 'trimmed keyword'));
  this.view.appendChild(this.stage = document.createElement('span'));
  this.elements = this.stage.children;
  this.view.appendChild(this.suffix = createSpan('...', 'trimmed keyword'));

  this.refine(prefix || '');
}

Autocomplete.prototype.getSelectedWord = function() {
  return this.lastSelected && this.lastSelected.innerText;
};

Autocomplete.prototype.onFinished = function(callback) {
  this.onFinishedCallback = callback;
  if (this.confirmed) callback(this.confirmed);
};

Autocomplete.prototype.onKeyDown = function(ev) {
  var self = this;
  if (!this.elements.length) return;

  function move(nextCurrent) {
    if (self.lastSelected) removeClass(self.lastSelected, 'selected');
    addClass(self.lastSelected = self.elements[nextCurrent], 'selected');
    self.trim(self.current, true);
    self.trim(nextCurrent, false);
    self.current = nextCurrent;
  }

  switch (ev.keyCode) {
    case 69:
      if (ev.ctrlKey) {
        move(this.current + 1 >= this.elements.length ? 0 : this.current + 1);
        return true;
      }
      return false;
    case 9: // Tab
      if (ev.shiftKey) { // move back
        move(this.current - 1 < 0 ? this.elements.length - 1 : this.current - 1);
      } else { // move next
        move(this.current + 1 >= this.elements.length ? 0 : this.current + 1);
      }
      return true;
    case 13: // Enter
      this.finish();
      return true;
    case 27: // Esc
      this.cancel();
      return true;
    case 37: case 38: case 39: case 40: // disable using arrow keys on completing
      return true;
  }

  return false;
};

Autocomplete.prototype.trim = function(from, needToTrim) {
  var self = this;
  var num = 5;

  if (this.elements.length > num) {
    (0 < from ? removeClass : addClass)(this.prefix, 'trimmed');
    (from + num < this.elements.length ? removeClass : addClass)(this.suffix, 'trimmed');
  } else {
    addClass(this.prefix, 'trimmed');
    addClass(this.suffix, 'trimmed');
  }

  function iterate(x) {
    for (var i = 0; i < num; ++i, ++x) if (0 <= x && x < self.elements.length) {
      toggleClass(self.elements[x], 'trimmed');
    }
  }

  var toggleClass = needToTrim ? addClass : removeClass;
  if (from < 0) {
    iterate(0);
  } else if (from + num - 1 >= this.elements.length) {
    iterate(this.elements.length - num);
  } else {
    iterate(from);
  }
};

Autocomplete.prototype.refine = function(prefix) {
  if (this.confirmed) return;
  var inc = !this.prev || (prefix.length >= this.prev.length);
  this.prev = prefix;
  var self = this;

  function remove(parent, child) {
    if (parent == child.parentNode) parent.removeChild(child);
  }

  function toggle(el) {
    return inc ? remove(self.stage, el) : self.stage.appendChild(el);
  }

  function startsWith(str, prefix) {
    return !prefix || str.substr(0, prefix.length) === prefix;
  }

  function moveRight(l, r) {
    while (l < r && inc !== startsWith(self.words[l].word, prefix)) toggle(self.words[l++].element);
    return l;
  }

  function moveLeft(l, r) {
    while (l < r - 1 && inc !== startsWith(self.words[r-1].word, prefix)) toggle(self.words[--r].element);
    return r;
  }

  self.trim(self.current, true); // reset trimming

  // Refine the range of words having same prefix
  if (inc) {
    self.left = moveRight(self.left, self.right);
    self.right = moveLeft(self.left, self.right);
  } else {
    self.left = moveLeft(-1, self.left);
    self.right = moveRight(self.right, self.words.length);
  }

  // Render elements with sorting by scope groups
  var words = this.words.slice(this.left, this.right);
  words.sort(function(a, b) { return a.priority == b.priority ? (a.word < b.word ? -1 : 1) : (a.priority < b.priority ? -1 : 1); });
  removeAllChildren(this.elements);
  for (var i = 0; i < words.length; ++i) {
    this.stage.appendChild(words[i].element);
  }

  // Keep a previous selected element if the refined range includes the element
  if (this.lastSelected && this.left <= this.lastSelected.dataset.index && this.lastSelected.dataset.index < this.right) {
    this.current = Array.prototype.indexOf.call(this.elements, this.lastSelected);
    this.trim(this.current, false);
  } else {
    if (this.lastSelected) removeClass(this.lastSelected, 'selected');
    this.lastSelected = null;
    this.current = -1;
    this.trim(0, false);
  }

  if (self.left + 1 == self.right) {
    self.current = 0;
    self.finish();
  } else if (self.left == self.right) {
    self.cancel();
  }
};

Autocomplete.prototype.finish = function() {
  if (0 <= this.current && this.current < this.elements.length) {
    this.confirmed = this.elements[this.current].innerText;
    if (this.onFinishedCallback) this.onFinishedCallback(this.confirmed);
    this.removeView();
  } else {
    this.cancel();
  }
};

Autocomplete.prototype.cancel = function() {
  if (this.onFinishedCallback) this.onFinishedCallback();
  this.removeView();
};

Autocomplete.prototype.removeView = function() {
  if (this.view.parentNode) this.view.parentNode.removeChild(this.view);
  removeAllChildren(this.view);
}

// HTML strings for dynamic elements.
var consoleInnerHtml = <%= render_inlined_string '_inner_console_markup' %>;
var promptBoxHtml = <%= render_inlined_string '_prompt_box_markup' %>;
// CSS
var consoleStyleCss = <%= render_inlined_string 'style' %>;
// Insert a style element with the unique ID
var styleElementId = 'sr02459pvbvrmhco';
// Nonce to use for CSP
var styleElementNonce = '<%= @nonce %>';

// REPLConsole Constructor
function REPLConsole(config) {
  function getConfig(key, defaultValue) {
    return config && config[key] || defaultValue;
  }

  this.commandStorage = new CommandStorage();
  this.prompt = getConfig('promptLabel', ' >>');
  this.mountPoint = getConfig('mountPoint');
  this.sessionId = getConfig('sessionId');
  this.autocomplete = false;
}

REPLConsole.prototype.getSessionUrl = function(path) {
  var parts = [ this.mountPoint, 'repl_sessions', this.sessionId ];
  if (path) {
    parts.push(path);
  }
  // Join and remove duplicate slashes.
  return parts.join('/').replace(/([^:]\/)\/+/g, '$1');
};

REPLConsole.prototype.contextRequest = function(keyword, callback) {
  putRequest(this.getSessionUrl(), 'context=' + getContext(keyword), function(xhr) {
    if (xhr.status == 200) {
      callback(null, JSON.parse(xhr.responseText));
    } else {
      callback(xhr.statusText);
    }
  });
};

REPLConsole.prototype.commandHandle = function(line, callback) {
  var self = this;
  var params = 'input=' + encodeURIComponent(line);
  callback = callback || function() {};

  function isSuccess(status) {
    return status >= 200 && status < 300 || status === 304;
  }

  function parseJSON(text) {
    try {
      return JSON.parse(text);
    } catch (e) {
      return null;
    }
  }

  function getErrorText(xhr) {
    if (!xhr.status) {
      return "Connection Refused";
    } else {
      return xhr.status + ' ' + xhr.statusText;
    }
  }

  putRequest(self.getSessionUrl(), params, function(xhr) {
    var response = parseJSON(xhr.responseText);
    var result   = isSuccess(xhr.status);
    if (result) {
      self.writeOutput(response.output);
    } else {
      if (response && response.output) {
        self.writeError(response.output);
      } else {
        self.writeError(getErrorText(xhr));
      }
    }
    callback(result, response);
  });
};

REPLConsole.prototype.uninstall = function() {
  this.container.parentNode.removeChild(this.container);
};

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) {
      var height = startHeight + startY - e.clientY;
      consoleOuter.scrollTop = scrollTopStart + (clientHeightStart - consoleOuter.clientHeight);
      if (height > document.documentElement.clientHeight) {
        container.style.height = document.documentElement.clientHeight;
      } else {
        container.style.height = height + 'px';
      }
      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';
    }
  }

  var observer = new MutationObserver(function(mutationsList) {
    for (let mutation of mutationsList) {
      if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
        shiftConsoleActions();
      }
    }
  });

  // Initialize
  this.container = container;
  this.outer = consoleOuter;
  this.inner = findChild(this.outer, 'console-inner');
  this.clipboard = findChild(container, 'clipboard');
  this.suggestWait = 1500;
  this.newPromptBox();
  this.insertCss();

  findChild(container, 'resizer').addEventListener('mousedown', resizeContainer);
  findChild(consoleActions, 'close-button').addEventListener('click', closeContainer);
  observer.observe(consoleOuter, { childList: true, subtree: true });

  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;
  if (styleElementNonce.length > 0) {
    style.nonce = styleElementNonce;
  }
  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.getSuggestion = function(keyword) {
  var self = this;

  function show(found) {
    if (!found) return;
    var hint = self.promptDisplay.childNodes[1];
    hint.className = 'console-hint';
    hint.dataset.keyword = found;
    hint.innerText = found.substr(self.suggestKeyword.length);
    // clear hinting information after timeout in a few time
    if (self.suggestTimeout) clearTimeout(self.suggestTimeout);
    self.suggestTimeout = setTimeout(function() { self.renderInput() }, self.suggestWait);
  }

  function find(context) {
    var k = self.suggestKeyword;
    for (var i = 0; i < context.length; ++i) if (context[i].substr(0, k.length) === k) {
      if (context[i] === k) return;
      return context[i];
    }
  }

  function request(keyword, callback) {
    self.contextRequest(keyword, function(err, res) {
      if (err) throw new Error(err);
      var c = flatten(res['context']);
      c.sort();
      callback(c);
    });
  }

  self.suggestKeyword = keyword;
  var input = getContext(keyword);
  if (keyword.length - input.length < 3) return;

  if (self.suggestInput !== input) {
    self.suggestInput = input;
    request(keyword, function(c) {
      show(find(self.suggestContext = c));
    });
  } else if (self.suggestContext) {
    show(find(self.suggestContext));
  }
};

REPLConsole.prototype.getHintKeyword = function() {
  var hint = this.promptDisplay.childNodes[1];
  return hint.className === 'console-hint' && hint.dataset.keyword;
};

REPLConsole.prototype.setInput = function(input, caretPos) {
  if (input == null) return; // keep value if input is undefined
  this._caretPos = caretPos === undefined ? input.length : caretPos;
  this._input = input;
  if (this.autocomplete) this.autocomplete.refine(this.getCurrentWord());
  this.renderInput();
  if (!this.autocomplete && input.length == this._caretPos) this.getSuggestion(this.getCurrentWord());
};

/**
 * 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 before, current, after;
  var center = document.createElement('span');

  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));
  this.promptDisplay.appendChild(center);
  this.promptDisplay.appendChild(document.createTextNode(after));

  var hint = this.autocomplete && this.autocomplete.getSelectedWord();
  addClass(center, hint ? 'console-hint' : 'console-cursor');
  center.appendChild(document.createTextNode(hint ? hint.substr(this.getCurrentWord().length) : current));
};

REPLConsole.prototype.writeOutput = function(output) {
  var consoleMessage = document.createElement('pre');
  consoleMessage.className = "console-message";
  consoleMessage.innerHTML = escapeHTML(output);
  this.inner.appendChild(consoleMessage);
  this.newPromptBox();
  return consoleMessage;
};

REPLConsole.prototype.writeError = function(output) {
  var consoleMessage = this.writeOutput(output);
  addClass(consoleMessage, "error-message");
  return consoleMessage;
};

REPLConsole.prototype.writeNotification = function(output) {
  var consoleMessage = this.writeOutput(output);
  addClass(consoleMessage, "notification-message");
  return consoleMessage;
};

REPLConsole.prototype.onEnterKey = function() {
  var input = this._input;

  if(input != "" && input !== undefined) {
    this.commandStorage.addCommand(input);
  }

  this.commandHandle(input);
};

REPLConsole.prototype.onTabKey = function() {
  var self = this;

  var hintKeyword;
  if (hintKeyword = self.getHintKeyword()) {
    self.swapCurrentWord(hintKeyword);
    return;
  }

  if (self.autocomplete) return;
  self.autocomplete = new Autocomplete([]);

  self.contextRequest(self.getCurrentWord(), function(err, obj) {
    if (err) return self.autocomplete = false;
    self.autocomplete = new Autocomplete(obj['context'], self.getCurrentWord());
    self.inner.appendChild(self.autocomplete.view);
    self.autocomplete.onFinished(function(word) {
      self.swapCurrentWord(word);
      self.autocomplete = false;
    });
    self.scrollToBottom();
  });
};

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) {
  if (this.autocomplete && this.autocomplete.onKeyDown(ev)) {
    this.renderInput();
    ev.preventDefault();
    ev.stopPropagation();
    return;
  }

  switch (ev.keyCode) {
    case 65: // Ctrl-A
      if (ev.ctrlKey) {
        this.setInput(this._input, 0);
        ev.preventDefault();
      }
      break;

    case 69: // Ctrl-E
      if (ev.ctrlKey) {
        this.onTabKey();
        ev.preventDefault();
      }
      break;

    case 87: // Ctrl-W
      if (ev.ctrlKey) {
        this.deleteWord();
        ev.preventDefault();
      }
      break;

    case 85: // Ctrl-U
      if (ev.ctrlKey) {
        this.deleteLine();
        ev.preventDefault();
      }
      break;

    case 69: // Ctrl-E
      if (ev.ctrlKey) {
        this.onTabKey();
        ev.preventDefault();
      }
      break;

    case 80: // Ctrl-P
      if (! ev.ctrlKey) break;

    case 78: // Ctrl-N
      if (! ev.ctrlKey) break;

    case 9: // Tab
      this.onTabKey();
      ev.preventDefault();
      break;

    case 13: // Enter key
      this.onEnterKey();
      ev.preventDefault();
      break;

    case 38: // Up arrow
      this.onNavigateHistory(-1);
      ev.preventDefault();
      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) {
    if (ev.keyCode == 86) {
      // Set focus to our clipboard when they hit the "v" key
      this.clipboard.focus();

      // 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();
      }, 100);
    }
  }

  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.altKey || 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);

    if (!this._input) {
      this.autocomplete && this.autocomplete.cancel();
      this.autocomplete = false;
    }
  }
};

/**
 * Deletes the current line.
 */
REPLConsole.prototype.deleteLine = function() {
  if (this._caretPos > 0) {
    this.setInput("", 0);

    if (!this._input) {
      this.autocomplete && this.autocomplete.cancel();
      this.autocomplete = false;
    }
  }
};

/**
 * Deletes the current word.
 */
REPLConsole.prototype.deleteWord = function() {
  if (this._caretPos > 0) {
    var i = 1, current = this._caretPos;
    while (this._input[current - i++] == " ");

    var deleteIndex = 0;
    for (; current - i > 0; i++) {
      if (this._input[current - i] == " ") {
        deleteIndex = current - i;
        break;
      }
    }

    var before = this._input.substring(0, deleteIndex);
    var after = this._input.substring(current, this._input.length);
    this.setInput(before + after, deleteIndex);

    if (!this._input) {
      this.autocomplete && this.autocomplete.cancel();
      this.autocomplete = false;
    }
  }
};

/**
 * 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.swapCurrentWord = function(next) {
  function right(s, pos) {
    var x = s.indexOf(' ', pos);
    return x === -1 ? s.length : x;
  }

  function swap(s, pos) {
    return s.substr(0, s.lastIndexOf(' ', pos) + 1) + next + s.substr(right(s, pos))
  }

  if (!next) return;
  var swapped = swap(this._input, this._caretPos);
  this.setInput(swapped, this._caretPos + swapped.length - this._input.length);
};

REPLConsole.prototype.getCurrentWord = function() {
  return (function(s, pos) {
    var left = s.lastIndexOf(' ', pos);
    if (left === -1) left = 0;
    var right = s.indexOf(' ', pos)
    if (right === -1) right = s.length - 1;
    return s.substr(left, right - left + 1).replace(/^\s+|\s+$/g,'');
  })(this._input, this._caretPos);
};

REPLConsole.prototype.scrollToBottom = function() {
  this.outer.scrollTop = this.outer.scrollHeight;
};

// Change the binding of the console.
REPLConsole.prototype.switchBindingTo = function(frameId, exceptionObjectId, callback) {
  var url = this.getSessionUrl('trace');
  var params = "frame_id=" + encodeURIComponent(frameId);

  if (exceptionObjectId) {
    params = params + "&exception_object_id=" + encodeURIComponent(exceptionObjectId);
  }

  var _this = this;
  postRequest(url, params, function() {
    var text = "Context has changed to: " + callback();
    _this.writeNotification(text);
  });
};

/**
 * Install the console into the element with a specific ID.
 * Example: REPLConsole.installInto("target-id")
 */
REPLConsole.installInto = function(id, options) {
  var consoleElement = document.getElementById(id);

  options = options || {};

  for (var prop in consoleElement.dataset) {
    options[prop] = options[prop] || consoleElement.dataset[prop];
  }

  var replConsole = new REPLConsole(options);
  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;

// This line is for the Firefox Add-on, because it doesn't have XMLHttpRequest as default.
// And so we need to require a module compatible with XMLHttpRequest from SDK.
REPLConsole.XMLHttpRequest = typeof XMLHttpRequest === 'undefined' ? null : XMLHttpRequest;

REPLConsole.request = function request(method, url, params, callback) {
  var xhr = new REPLConsole.XMLHttpRequest();

  xhr.open(method, url, true);
  xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded");
  xhr.setRequestHeader("x-requested-with", "XMLHttpRequest");
  xhr.send(params);

  xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) {
      callback(xhr);
    }
  };
};

// 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 if (!hasClass(el, className)) {
    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 postRequest() {
  REPLConsole.request.apply(this, ["POST"].concat([].slice.call(arguments)));
}

function putRequest() {
  REPLConsole.request.apply(this, ["PUT"].concat([].slice.call(arguments)));
}

if (typeof exports === 'object') {
  exports.REPLConsole = REPLConsole;
} else {
  window.REPLConsole = REPLConsole;
}

// Split string by module operators of ruby
function getContext(s) {
  var methodOp = s.lastIndexOf('.');
  var moduleOp = s.lastIndexOf('::');
  var x = methodOp > moduleOp ? methodOp : moduleOp;
  return x !== -1 ? s.substr(0, x) : '';
}

function flatten(arrays) {
  return Array.prototype.concat.apply([], arrays);
}
web-console-4.2.1/lib/web_console/templates/error_page.js.erb000066400000000000000000000050331447570540700242460ustar00rootroot00000000000000// 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;
    var exceptionObjectId = target.dataset.exceptionObjectId;

    // Change the binding of the console.
    changeBinding(frameId, exceptionObjectId, function() {
      // Rails already handles toggling the select class
      selectedFrame = target;
      return target.innerHTML;
    });

    // Change the extracted source code
    changeSourceExtract(frameId);
  });
}

// Change the binding of the current session and prompt the user.
function changeBinding(frameId, exceptionObjectId, callback) {
  REPLConsole.currentSession.switchBindingTo(frameId, exceptionObjectId, 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-4.2.1/lib/web_console/templates/index.html.erb000066400000000000000000000003751447570540700235640ustar00rootroot00000000000000<%= render 'markup' %>

<%= render_javascript 'console' %>
<%= render_javascript 'main' %>

<% only_on_error_page do %>
  <%= render_javascript 'error_page' %>
<% end %>

<% only_on_regular_page do %>
  <%= render_javascript 'regular_page' %>
<% end %>
web-console-4.2.1/lib/web_console/templates/layouts/000077500000000000000000000000001447570540700225135ustar00rootroot00000000000000web-console-4.2.1/lib/web_console/templates/layouts/inlined_string.erb000066400000000000000000000000211447570540700262060ustar00rootroot00000000000000`<%= j yield %>`
web-console-4.2.1/lib/web_console/templates/layouts/javascript.erb000066400000000000000000000002151447570540700253510ustar00rootroot00000000000000
web-console-4.2.1/lib/web_console/templates/main.js.erb000066400000000000000000000000441447570540700230420ustar00rootroot00000000000000REPLConsole.installInto('console');
web-console-4.2.1/lib/web_console/templates/regular_page.js.erb000066400000000000000000000016561447570540700245650ustar00rootroot00000000000000// Push the error page body upwards the size of the console.
document.addEventListener('DOMContentLoaded', function() {
  var consoleElement = document.getElementById('console');
  var resizerElement = consoleElement.getElementsByClassName('resizer')[0];
  var bodyElement = document.body;

  function setBodyElementBottomMargin(pixels) {
    bodyElement.style.marginBottom = pixels + 'px';
  }

  var currentConsoleElementHeight = consoleElement.offsetHeight;
  setBodyElementBottomMargin(currentConsoleElementHeight);

  resizerElement.addEventListener('mousedown', function(event) {
    function recordConsoleElementHeight(event) {
      resizerElement.removeEventListener('mouseup', recordConsoleElementHeight);

      var currentConsoleElementHeight = consoleElement.offsetHeight;
      setBodyElementBottomMargin(currentConsoleElementHeight);
    }

    resizerElement.addEventListener('mouseup', recordConsoleElementHeight);
  });
});
web-console-4.2.1/lib/web_console/templates/style.css.erb000066400000000000000000000052041447570540700234350ustar00rootroot00000000000000.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: unset;
  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-message.error-message {
  color: #fc9;
}

.console .console-message.notification-message {
  color: #99f;
}

.console .console-message.auto-complete {
  word-break: break-all;
}

.console .console-message.auto-complete .keyword {
  margin-right: 11px;
}

.console .console-message.auto-complete .keyword.selected {
  background: #fff;
  color: #000;
}

.console .console-message.auto-complete .hidden {
  display: none;
}

.console .console-message.auto-complete .trimmed {
  display: none;
}

.console .console-hint {
  color: #096;
}

.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;
}

.console.full-screen {
  height: 100%;
}

.console.full-screen .console-outer {
  padding-top: 3px;
}

.console.full-screen .resizer {
  display: none;
}

.console.full-screen .close-button {
  display: none;
}
web-console-4.2.1/lib/web_console/testing/000077500000000000000000000000001447570540700204725ustar00rootroot00000000000000web-console-4.2.1/lib/web_console/testing/erb_precompiler.rb000066400000000000000000000012151447570540700241670ustar00rootroot00000000000000# frozen_string_literal: true

require "web_console/testing/helper"
require "web_console/testing/fake_middleware"

module WebConsole
  module Testing
    # This class is to pre-compile 'templates/*.erb'.
    class ERBPrecompiler
      def initialize(path)
        @erb  = ERB.new(File.read(path))
        @view = FakeMiddleware.new(
          view_path: Helper.gem_root.join("lib/web_console/templates"),
        ).view
      end

      def build
        @erb.result(binding)
      end

      def method_missing(name, *args, &block)
        return super unless @view.respond_to?(name)
        @view.send(name, *args, &block)
      end
    end
  end
end
web-console-4.2.1/lib/web_console/testing/fake_middleware.rb000066400000000000000000000021771447570540700241310ustar00rootroot00000000000000# frozen_string_literal: true

require "action_view"
require "action_dispatch" # This is needed to use Mime::Type
require "web_console"
require "web_console/testing/helper"

module WebConsole
  module Testing
    class FakeMiddleware
      I18n.load_path.concat(Dir[Helper.gem_root.join("lib/web_console/locales/*.yml")])

      DEFAULT_HEADERS = { Rack::CONTENT_TYPE => "application/javascript" }

      def initialize(opts)
        @headers        = opts.fetch(:headers, DEFAULT_HEADERS)
        @req_path_regex = opts[:req_path_regex]
        @view_path      = opts[:view_path]
      end

      def call(env)
        body = render(req_path(env))
        @headers[Rack::CONTENT_LENGTH] = body.bytesize.to_s

        [ 200, @headers, [ body ] ]
      end

      def view
        @view = View.with_empty_template_cache.with_view_paths(@view_path)
      end

      private

        # extract target path from REQUEST_PATH
        def req_path(env)
          File.basename(env["REQUEST_PATH"].match(@req_path_regex)[1], ".*")
        end

        def render(template)
          view.render(template: template, layout: nil)
        end
    end
  end
end
web-console-4.2.1/lib/web_console/testing/helper.rb000066400000000000000000000003051447570540700222740ustar00rootroot00000000000000# frozen_string_literal: true

module WebConsole
  module Testing
    module Helper
      def self.gem_root
        Pathname(File.expand_path("../../../../", __FILE__))
      end
    end
  end
end
web-console-4.2.1/lib/web_console/version.rb000066400000000000000000000001111447570540700210200ustar00rootroot00000000000000# frozen_string_literal: true

module WebConsole
  VERSION = "4.2.1"
end
web-console-4.2.1/lib/web_console/view.rb000066400000000000000000000037761447570540700203310ustar00rootroot00000000000000# frozen_string_literal: true

module WebConsole
  class View < 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 Thread.current[:__web_console_exception].present?
    end

    # Execute a block only on regular, non-error, pages.
    def only_on_regular_page(*args)
      yield if Thread.current[:__web_console_binding].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)
      assign(template: template)
      assign(nonce: @env["action_dispatch.content_security_policy_nonce"])
      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

    # Custom ActionView::Base#render wrapper which silences all the log
    # printings.
    #
    # Helps to keep the Rails logs clean during errors.
    def render(*)
      if (logger = WebConsole.logger) && logger.respond_to?(:silence)
        WebConsole.logger.silence { super }
      else
        super
      end
    end

    # Override method for ActionView::Helpers::TranslationHelper#t.
    #
    # This method escapes the original return value for JavaScript, since the
    # method returns a HTML tag with some attributes when the key is not found,
    # so it could cause a syntax error if we use the value in the string literals.
    def t(key, options = {})
      j super
    end
  end
end
web-console-4.2.1/lib/web_console/whiny_request.rb000066400000000000000000000013621447570540700222520ustar00rootroot00000000000000# frozen_string_literal: true

module WebConsole
  # Noisy wrapper around +Request+.
  #
  # If any calls to +permitted?+ and +acceptable_content_type?+
  # return false, an info log message will be displayed in users' logs.
  class WhinyRequest < SimpleDelegator
    def permitted?
      whine_unless request.permitted? do
        "Cannot render console from #{request.strict_remote_ip}! " \
          "Allowed networks: #{request.permissions}"
      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-4.2.1/test/000077500000000000000000000000001447570540700147275ustar00rootroot00000000000000web-console-4.2.1/test/dummy/000077500000000000000000000000001447570540700160625ustar00rootroot00000000000000web-console-4.2.1/test/dummy/README.rdoc000066400000000000000000000007361447570540700176760ustar00rootroot00000000000000== README

This README would normally document whatever steps are necessary to get the
application up and running.

Things you may want to cover:

* Ruby version

* System dependencies

* Configuration

* Database creation

* Database initialization

* How to run the test suite

* Services (job queues, cache servers, search engines, etc.)

* Deployment instructions

* ...


Please feel free to use a different markup language if you do not plan to run
rake doc:app.
web-console-4.2.1/test/dummy/Rakefile000066400000000000000000000004311447570540700175250ustar00rootroot00000000000000# frozen_string_literal: true

# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.

require File.expand_path("../config/application", __FILE__)

Dummy::Application.load_tasks
web-console-4.2.1/test/dummy/app/000077500000000000000000000000001447570540700166425ustar00rootroot00000000000000web-console-4.2.1/test/dummy/app/assets/000077500000000000000000000000001447570540700201445ustar00rootroot00000000000000web-console-4.2.1/test/dummy/app/assets/javascripts/000077500000000000000000000000001447570540700224755ustar00rootroot00000000000000web-console-4.2.1/test/dummy/app/assets/javascripts/application.js000066400000000000000000000011231447570540700253330ustar00rootroot00000000000000// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
// GO AFTER THE REQUIRES BELOW.
//
//= require_tree .
web-console-4.2.1/test/dummy/app/assets/stylesheets/000077500000000000000000000000001447570540700225205ustar00rootroot00000000000000web-console-4.2.1/test/dummy/app/assets/stylesheets/application.css000066400000000000000000000010421447570540700255320ustar00rootroot00000000000000/*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 * listed below.
 *
 * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
 * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear at the top of the
 * compiled file, but it's generally better to create a new file per style scope.
 *
 *= require_self
 *= require_tree .
 */
web-console-4.2.1/test/dummy/app/controllers/000077500000000000000000000000001447570540700212105ustar00rootroot00000000000000web-console-4.2.1/test/dummy/app/controllers/application_controller.rb000066400000000000000000000003531447570540700263040ustar00rootroot00000000000000# frozen_string_literal: true

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception
end
web-console-4.2.1/test/dummy/app/controllers/concerns/000077500000000000000000000000001447570540700230225ustar00rootroot00000000000000web-console-4.2.1/test/dummy/app/controllers/concerns/.keep000066400000000000000000000000001447570540700237350ustar00rootroot00000000000000web-console-4.2.1/test/dummy/app/controllers/controller_helper_test_controller.rb000066400000000000000000000002751447570540700305650ustar00rootroot00000000000000# frozen_string_literal: true

class ControllerHelperTestController < ApplicationController
  def index
    @instance_variable = "Helper Test"
    local_variable = 42
    console
  end
end
web-console-4.2.1/test/dummy/app/controllers/exception_test_controller.rb000066400000000000000000000004251447570540700270360ustar00rootroot00000000000000# frozen_string_literal: true

class ExceptionTestController < ApplicationController
  def index
    test = "Test"
    test_method
  end

  def xhr
    raise "asda" if request.xhr?
  end

  def test_method
    test2 = "Test2"
    params.fetch(:error_in_library_code)
  end
end
web-console-4.2.1/test/dummy/app/controllers/helper_error_controller.rb000066400000000000000000000001511447570540700264650ustar00rootroot00000000000000# frozen_string_literal: true

class HelperErrorController < ApplicationController
  def index
  end
end
web-console-4.2.1/test/dummy/app/controllers/helper_test_controller.rb000066400000000000000000000002111447570540700263100ustar00rootroot00000000000000# frozen_string_literal: true

class HelperTestController < ApplicationController
  def index
    @helper_test = "Helper Test"
  end
end
web-console-4.2.1/test/dummy/app/controllers/model_test_controller.rb000066400000000000000000000004031447570540700261340ustar00rootroot00000000000000# frozen_string_literal: true

class ModelTestController < ApplicationController
  def index
    LocalModel.new.work
  end

  class LocalModel
    def initialize
      @state = :state
    end

    def work
      local_var = 42
      console
    end
  end
end
web-console-4.2.1/test/dummy/app/controllers/tests_controller.rb000066400000000000000000000006111447570540700251400ustar00rootroot00000000000000# frozen_string_literal: true

class TestsController < ApplicationController
  def render_console_ontop_of_text
    render text: '

Hello World

' console end def renders_console_only_once render text: '

Hello World

' 2.times { console } end def doesnt_render_console_on_non_html_requests render json: {} console end end web-console-4.2.1/test/dummy/app/helpers/000077500000000000000000000000001447570540700203045ustar00rootroot00000000000000web-console-4.2.1/test/dummy/app/helpers/application_helper.rb000066400000000000000000000000741447570540700244740ustar00rootroot00000000000000# frozen_string_literal: true module ApplicationHelper end web-console-4.2.1/test/dummy/app/mailers/000077500000000000000000000000001447570540700202765ustar00rootroot00000000000000web-console-4.2.1/test/dummy/app/mailers/.keep000066400000000000000000000000001447570540700212110ustar00rootroot00000000000000web-console-4.2.1/test/dummy/app/models/000077500000000000000000000000001447570540700201255ustar00rootroot00000000000000web-console-4.2.1/test/dummy/app/models/.keep000066400000000000000000000000001447570540700210400ustar00rootroot00000000000000web-console-4.2.1/test/dummy/app/models/concerns/000077500000000000000000000000001447570540700217375ustar00rootroot00000000000000web-console-4.2.1/test/dummy/app/models/concerns/.keep000066400000000000000000000000001447570540700226520ustar00rootroot00000000000000web-console-4.2.1/test/dummy/app/views/000077500000000000000000000000001447570540700177775ustar00rootroot00000000000000web-console-4.2.1/test/dummy/app/views/controller_helper_test/000077500000000000000000000000001447570540700245605ustar00rootroot00000000000000web-console-4.2.1/test/dummy/app/views/controller_helper_test/index.html.erb000066400000000000000000000000401447570540700273160ustar00rootroot00000000000000

Controller Helper Test

web-console-4.2.1/test/dummy/app/views/exception_test/000077500000000000000000000000001447570540700230345ustar00rootroot00000000000000web-console-4.2.1/test/dummy/app/views/exception_test/xhr.html.erb000066400000000000000000000000321447570540700252650ustar00rootroot00000000000000

Do an XHR request!

web-console-4.2.1/test/dummy/app/views/helper_error/000077500000000000000000000000001447570540700224675ustar00rootroot00000000000000web-console-4.2.1/test/dummy/app/views/helper_error/index.html.erb000066400000000000000000000000541447570540700252320ustar00rootroot00000000000000<% @ok = 42 %> <% params.fetch(:bad_key) %> web-console-4.2.1/test/dummy/app/views/helper_test/000077500000000000000000000000001447570540700223155ustar00rootroot00000000000000web-console-4.2.1/test/dummy/app/views/helper_test/index.html.erb000066400000000000000000000111071447570540700250610ustar00rootroot00000000000000<%= console %> <% content_for :head do %> <% end %>

Getting started

Here’s how to get rolling:

  1. Use rails generate to create your models and controllers

    To see all available options, run it without parameters.

  2. Set up a default route and remove or rename this file

    Routes are set up in config/routes.rb.

  3. Create your database

    Run rake db:migrate to create your database. If you're not using SQLite (the default), edit config/database.yml with your username and password.

web-console-4.2.1/test/dummy/app/views/layouts/000077500000000000000000000000001447570540700214775ustar00rootroot00000000000000web-console-4.2.1/test/dummy/app/views/layouts/application.html.erb000066400000000000000000000004751447570540700254450ustar00rootroot00000000000000 Dummy <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %> <%= javascript_include_tag "application", "data-turbolinks-track" => true %> <%= csrf_meta_tags %> <%= yield :head %> <%= yield %> web-console-4.2.1/test/dummy/app/views/model_test/000077500000000000000000000000001447570540700221365ustar00rootroot00000000000000web-console-4.2.1/test/dummy/app/views/model_test/index.html.erb000066400000000000000000000000001447570540700246700ustar00rootroot00000000000000web-console-4.2.1/test/dummy/bin/000077500000000000000000000000001447570540700166325ustar00rootroot00000000000000web-console-4.2.1/test/dummy/bin/bundle000077500000000000000000000002401447570540700200250ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__) load Gem.bin_path("bundler", "bundle") web-console-4.2.1/test/dummy/bin/rails000077500000000000000000000002611447570540700176710ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true APP_PATH = File.expand_path("../../config/application", __FILE__) require_relative "../config/boot" require "rails/commands" web-console-4.2.1/test/dummy/bin/rake000077500000000000000000000001711447570540700175010ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true require_relative "../config/boot" require "rake" Rake.application.run web-console-4.2.1/test/dummy/config.ru000066400000000000000000000002711447570540700176770ustar00rootroot00000000000000# frozen_string_literal: true # This file is used by Rack-based servers to start the application. require ::File.expand_path("../config/environment", __FILE__) run Rails.application web-console-4.2.1/test/dummy/config/000077500000000000000000000000001447570540700173275ustar00rootroot00000000000000web-console-4.2.1/test/dummy/config/application.rb000066400000000000000000000007101447570540700221550ustar00rootroot00000000000000# frozen_string_literal: true require File.expand_path("../boot", __FILE__) require "active_model/railtie" require "action_controller/railtie" require "action_view/railtie" Bundler.require(*Rails.groups) require "web_console" module Dummy class Application < Rails::Application # Run outside of the development mode so our test suite runs. config.web_console.development_only = false config.active_support.test_order = :random end end web-console-4.2.1/test/dummy/config/boot.rb000066400000000000000000000004201447570540700206130ustar00rootroot00000000000000# frozen_string_literal: true # Set up gems listed in the Gemfile. ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../../Gemfile", __FILE__) require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) $LOAD_PATH.unshift File.expand_path("../../../../lib", __FILE__) web-console-4.2.1/test/dummy/config/database.yml000066400000000000000000000011001447570540700216060ustar00rootroot00000000000000# SQLite version 3.x # gem install sqlite3 # # Ensure the SQLite 3 gem is defined in your Gemfile # gem 'sqlite3' development: adapter: sqlite3 database: db/development.sqlite3 pool: 5 timeout: 5000 # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: adapter: sqlite3 database: db/test.sqlite3 pool: 5 timeout: 5000 production: adapter: sqlite3 database: db/production.sqlite3 pool: 5 timeout: 5000 web-console-4.2.1/test/dummy/config/environment.rb000066400000000000000000000002661447570540700222240ustar00rootroot00000000000000# frozen_string_literal: true # Load the rails application. require File.expand_path("../application", __FILE__) # Initialize the rails application. Dummy::Application.initialize! web-console-4.2.1/test/dummy/config/environments/000077500000000000000000000000001447570540700220565ustar00rootroot00000000000000web-console-4.2.1/test/dummy/config/environments/development.rb000066400000000000000000000013311447570540700247230ustar00rootroot00000000000000# frozen_string_literal: true Dummy::Application.configure do # Settings specified here will take precedence over those in config/application.rb. # In the development environment your application's code is reloaded on # every request. This slows down response time but is perfect for development # since you don't have to restart the web server when you make code changes. config.cache_classes = false # Do not eager load code on boot. config.eager_load = false # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log end web-console-4.2.1/test/dummy/config/environments/production.rb000066400000000000000000000054731447570540700246020ustar00rootroot00000000000000# frozen_string_literal: true Dummy::Application.configure do # Settings specified here will take precedence over those in config/application.rb. # Code is not reloaded between requests. config.cache_classes = true # Eager load code on boot. This eager loads most of Rails and # your application in memory, allowing both thread web servers # and those relying on copy on write to perform better. # Rake tasks automatically ignore this option for performance. config.eager_load = true # Full error reports are disabled and caching is turned on. config.consider_all_requests_local = false config.action_controller.perform_caching = true # Enable Rack::Cache to put a simple HTTP cache in front of your application # Add `rack-cache` to your Gemfile before enabling this. # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. # config.action_dispatch.rack_cache = true # Disable Rails's static asset server (Apache or nginx will already do this). config.serve_static_assets = false # Specifies the header that your server uses for sending files. # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # config.force_ssl = true # Set to :debug to see everything in the log. config.log_level = :info # Prepend all log lines with the following tags. # config.log_tags = [ :subdomain, :uuid ] # Use a different logger for distributed setups. # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) # Use a different cache store in production. # config.cache_store = :mem_cache_store # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.action_controller.asset_host = "http://assets.example.com" # Precompile additional assets. # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. # config.assets.precompile += %w( search.js ) # Ignore bad email addresses and do not raise email delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors. # config.action_mailer.raise_delivery_errors = false # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation can not be found). config.i18n.fallbacks = true # Send deprecation notices to registered listeners. config.active_support.deprecation = :notify # Disable automatic flushing of the log to improve performance. # config.autoflush_log = false # Use default logging formatter so that PID and timestamp are not suppressed. config.log_formatter = ::Logger::Formatter.new end web-console-4.2.1/test/dummy/config/environments/test.rb000066400000000000000000000023431447570540700233640ustar00rootroot00000000000000# frozen_string_literal: true Dummy::Application.configure do # Settings specified here will take precedence over those in config/application.rb. # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped # and recreated between test runs. Don't rely on the data there! config.cache_classes = true # Do not eager load code on boot. This avoids loading your whole application # just for the purpose of running a single test. If you are using a tool that # preloads Rails for running tests, you may have to set it to true. config.eager_load = true # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false # Raise exceptions instead of rendering exception templates. config.action_dispatch.show_exceptions = false # Disable request forgery protection in test environment. config.action_controller.allow_forgery_protection = false # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr config.i18n.enforce_available_locales = false end web-console-4.2.1/test/dummy/config/initializers/000077500000000000000000000000001447570540700220355ustar00rootroot00000000000000web-console-4.2.1/test/dummy/config/initializers/backtrace_silencers.rb000066400000000000000000000006631447570540700263550ustar00rootroot00000000000000# frozen_string_literal: true # Be sure to restart your server when you modify this file. # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. # Rails.backtrace_cleaner.remove_silencers! web-console-4.2.1/test/dummy/config/initializers/filter_parameter_logging.rb000066400000000000000000000003411447570540700274130ustar00rootroot00000000000000# frozen_string_literal: true # Be sure to restart your server when you modify this file. # Configure sensitive parameters which will be filtered from the log file. Rails.application.config.filter_parameters += [:password] web-console-4.2.1/test/dummy/config/initializers/inflections.rb000066400000000000000000000012461447570540700247020ustar00rootroot00000000000000# frozen_string_literal: true # Be sure to restart your server when you modify this file. # Add new inflection rules using the following format. Inflections # are locale specific, and you may define rules for as many different # locales as you wish. All of these examples are active by default: # ActiveSupport::Inflector.inflections(:en) do |inflect| # inflect.plural /^(ox)$/i, '\1en' # inflect.singular /^(ox)en/i, '\1' # inflect.irregular 'person', 'people' # inflect.uncountable %w( fish sheep ) # end # These inflection rules are supported but not enabled by default: # ActiveSupport::Inflector.inflections(:en) do |inflect| # inflect.acronym 'RESTful' # end web-console-4.2.1/test/dummy/config/initializers/mime_types.rb000066400000000000000000000003541447570540700245370ustar00rootroot00000000000000# frozen_string_literal: true # Be sure to restart your server when you modify this file. # Add new mime types for use in respond_to blocks: # Mime::Type.register "text/richtext", :rtf # Mime::Type.register_alias "text/html", :iphone web-console-4.2.1/test/dummy/config/initializers/secret_token.rb000066400000000000000000000012621447570540700250500ustar00rootroot00000000000000# frozen_string_literal: true # Be sure to restart your server when you modify this file. # Your secret key is used for verifying the integrity of signed cookies. # If you change this key, all old signed cookies will become invalid! # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. # You can use `rake secret` to generate a secure secret key. # Make sure your secret_key_base is kept private # if you're sharing your code publicly. Dummy::Application.config.secret_key_base = "97a4619f221e596a6987a3e85838e6c65f225096496e0b01a52f4901e26339755682845a1c315969bb33ae8ebe1e3bedc698ec75ad4eb0c4c3dc9bd2338b1adf" web-console-4.2.1/test/dummy/config/initializers/session_store.rb000066400000000000000000000002511447570540700252570ustar00rootroot00000000000000# frozen_string_literal: true # Be sure to restart your server when you modify this file. Dummy::Application.config.session_store :cookie_store, key: "_dummy_session" web-console-4.2.1/test/dummy/config/initializers/wrap_parameters.rb000066400000000000000000000010441447570540700255550ustar00rootroot00000000000000# frozen_string_literal: true # Be sure to restart your server when you modify this file. # This file contains settings for ActionController::ParamsWrapper which # is enabled by default. # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. ActiveSupport.on_load(:action_controller) do wrap_parameters format: [:json] if respond_to?(:wrap_parameters) end # To enable root element in JSON for ActiveRecord objects. # ActiveSupport.on_load(:active_record) do # self.include_root_in_json = true # end web-console-4.2.1/test/dummy/config/locales/000077500000000000000000000000001447570540700207515ustar00rootroot00000000000000web-console-4.2.1/test/dummy/config/locales/en.yml000066400000000000000000000011721447570540700220770ustar00rootroot00000000000000# Files in the config/locales directory are used for internationalization # and are automatically loaded by Rails. If you want to use locales other # than English, add the necessary files in this directory. # # To use the locales, use `I18n.t`: # # I18n.t 'hello' # # In views, this is aliased to just `t`: # # <%= t('hello') %> # # To use a different locale, set it with `I18n.locale`: # # I18n.locale = :es # # This would use the information in config/locales/es.yml. # # To learn more, please read the Rails Internationalization guide # available at http://guides.rubyonrails.org/i18n.html. en: hello: "Hello world" web-console-4.2.1/test/dummy/config/routes.rb000066400000000000000000000010431447570540700211730ustar00rootroot00000000000000# frozen_string_literal: true Rails.application.routes.draw do root to: "exception_test#index" get :exception_test, to: "exception_test#index" get :xhr_test, to: "exception_test#xhr" get :helper_test, to: "helper_test#index" get :model_test, to: "model_test#index" get :helper_error, to: "helper_error#index" get :controller_helper_test, to: "controller_helper_test#index" namespace :tests do get :render_console_ontop_of_text get :renders_console_only_once get :doesnt_render_console_on_non_html_requests end end web-console-4.2.1/test/dummy/lib/000077500000000000000000000000001447570540700166305ustar00rootroot00000000000000web-console-4.2.1/test/dummy/lib/assets/000077500000000000000000000000001447570540700201325ustar00rootroot00000000000000web-console-4.2.1/test/dummy/lib/assets/.keep000066400000000000000000000000001447570540700210450ustar00rootroot00000000000000web-console-4.2.1/test/dummy/log/000077500000000000000000000000001447570540700166435ustar00rootroot00000000000000web-console-4.2.1/test/dummy/log/.keep000066400000000000000000000000001447570540700175560ustar00rootroot00000000000000web-console-4.2.1/test/dummy/public/000077500000000000000000000000001447570540700173405ustar00rootroot00000000000000web-console-4.2.1/test/dummy/public/404.html000066400000000000000000000025071447570540700205410ustar00rootroot00000000000000 The page you were looking for doesn't exist (404)

The page you were looking for doesn't exist.

You may have mistyped the address or the page may have moved.

If you are the application owner check the logs for more information.

web-console-4.2.1/test/dummy/public/422.html000066400000000000000000000024661447570540700205450ustar00rootroot00000000000000 The change you wanted was rejected (422)

The change you wanted was rejected.

Maybe you tried to change something you didn't have access to.

If you are the application owner check the logs for more information.

web-console-4.2.1/test/dummy/public/500.html000066400000000000000000000023621447570540700205350ustar00rootroot00000000000000 We're sorry, but something went wrong (500)

We're sorry, but something went wrong.

If you are the application owner check the logs for more information.

web-console-4.2.1/test/dummy/public/favicon.ico000066400000000000000000000000001447570540700214470ustar00rootroot00000000000000web-console-4.2.1/test/support/000077500000000000000000000000001447570540700164435ustar00rootroot00000000000000web-console-4.2.1/test/support/scenarios/000077500000000000000000000000001447570540700204315ustar00rootroot00000000000000web-console-4.2.1/test/support/scenarios/bad_custom_error_scenario.rb000066400000000000000000000005151447570540700261730ustar00rootroot00000000000000# frozen_string_literal: true module WebConsole class BadCustomErrorScenario class Error < StandardError def initialize(*) # Bad exceptions are exceptions that don't call super in there # #initialize method. end end def call raise Error rescue => exc exc end end end web-console-4.2.1/test/support/scenarios/basic_nested_scenario.rb000066400000000000000000000003341447570540700252640ustar00rootroot00000000000000# frozen_string_literal: true module WebConsole class BasicNestedScenario def call raise_an_error rescue => exc exc end private def raise_an_error raise end end end web-console-4.2.1/test/support/scenarios/custom_error_scenario.rb000066400000000000000000000003001447570540700253550ustar00rootroot00000000000000# frozen_string_literal: true module WebConsole class CustomErrorScenario Error = Class.new(StandardError) def call raise Error rescue => exc exc end end end web-console-4.2.1/test/support/scenarios/eval_nested_scenario.rb000066400000000000000000000004271447570540700251350ustar00rootroot00000000000000# frozen_string_literal: true module WebConsole class EvalNestedScenario def call tap { raise_an_error_in_eval } rescue => exc exc end private def raise_an_error_in_eval eval "raise", binding, __FILE__, __LINE__ end end end web-console-4.2.1/test/support/scenarios/flat_scenario.rb000066400000000000000000000002151447570540700235650ustar00rootroot00000000000000# frozen_string_literal: true module WebConsole class FlatScenario def call raise rescue => exc exc end end end web-console-4.2.1/test/support/scenarios/reraised_scenario.rb000066400000000000000000000005141447570540700244370ustar00rootroot00000000000000# frozen_string_literal: true module WebConsole class ReraisedScenario def call reraise_an_error rescue => exc exc end private def raise_an_error_in_eval method_that_raises rescue => exc raise exc end def method_that_raises raise end end end web-console-4.2.1/test/templates/000077500000000000000000000000001447570540700167255ustar00rootroot00000000000000web-console-4.2.1/test/templates/config.ru000066400000000000000000000027701447570540700205500ustar00rootroot00000000000000require "web_console/testing/fake_middleware" TEST_ROOT = Pathname(__FILE__).dirname # e.g. "/node_modules/mocha/mocha.js" map "/node_modules" do node_modules = TEST_ROOT.join("node_modules") unless node_modules.directory? abort "ERROR: the node_modules/ directory is not found (You must run `npm install`)" end run Rack::Directory.new(node_modules) end # test runners map "/html" do run WebConsole::Testing::FakeMiddleware.new( req_path_regex: %r{^/html/(.*)}, headers: {Rack::CONTENT_TYPE => "text/html"}, view_path: TEST_ROOT.join("html"), ) end map "/test" do run WebConsole::Testing::FakeMiddleware.new( req_path_regex: %r{^/test/(.*)}, view_path: TEST_ROOT.join("test"), ) end map "/templates" do run WebConsole::Testing::FakeMiddleware.new( req_path_regex: %r{^/templates/(.*)}, view_path: TEST_ROOT.join("../../lib/web_console/templates"), ) end map "/mock/repl_sessions/result" do headers = { Rack::CONTENT_TYPE => 'application/json' } body = [ { output: '=> "fake-result"\n', context: [ [ :something, :somewhat, :somewhere ] ] }.to_json ] run lambda { |env| [ 200, headers, body ] } end map "/mock/repl_sessions/error" do headers = { Rack::CONTENT_TYPE => 'application/json' } body = [ { output: 'fake-error-message' }.to_json ] run lambda { |env| [ 400, headers, body ] } end map "/mock/repl_sessions/error.txt" do headers = { Rack::CONTENT_TYPE => 'plain/text' } body = [ 'error message' ] run lambda { |env| [ 400, headers, body ] } end web-console-4.2.1/test/templates/html/000077500000000000000000000000001447570540700176715ustar00rootroot00000000000000web-console-4.2.1/test/templates/html/test_runner.html.erb000066400000000000000000000014451447570540700237020ustar00rootroot00000000000000 Templates Test
<% Pathname.glob(TEST_ROOT.join "test/**/*_test.js") do |t| %> <% end %> web-console-4.2.1/test/templates/package.json000066400000000000000000000003741447570540700212170ustar00rootroot00000000000000{ "name": "for-tests", "version": "0.0.0", "repository": { "type": "git", "url": "https://github.com/rails/web-console" }, "devDependencies": { "chai": "^4.3.6", "mocha": "^10.0.0", "mocha-headless-chrome": "^4.0.0" } } web-console-4.2.1/test/templates/test/000077500000000000000000000000001447570540700177045ustar00rootroot00000000000000web-console-4.2.1/test/templates/test/auto_complete_test.js000066400000000000000000000156141447570540700241500ustar00rootroot00000000000000suite('Autocomplete', function() { suite('Trimming', function() { test('prefix and suffix stands for that the list includes trimmed elements', function() { var ac = new Autocomplete([['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']]); assertTrimmed(ac.prefix); assertNotTrimmed(ac.suffix); // A B C D E ... ac.onKeyDown(TestHelper.keyDown(TestHelper.KEY_TAB)); assertTrimmed(ac.prefix); assertNotTrimmed(ac.suffix); // ... B C D E F ... ac.onKeyDown(TestHelper.keyDown(TestHelper.KEY_TAB)); assertNotTrimmed(ac.prefix); assertNotTrimmed(ac.suffix); // A: A B C D E ... (shift) ac.onKeyDown(TestHelper.keyDown(TestHelper.KEY_TAB, { shiftKey: true })); assertTrimmed(ac.prefix); assertNotTrimmed(ac.suffix); // ... D E F G H for (var i = 0; i < 4; ++i) ac.onKeyDown(TestHelper.keyDown(TestHelper.KEY_TAB)); assertNotTrimmed(ac.prefix); assertTrimmed(ac.suffix); }); test('prefix and suffix are always trimmed if the list has few items', function() { var ac = new Autocomplete([['A', 'B', 'C']]); assertTrimmed(ac.prefix); assertTrimmed(ac.suffix); // A ac.onKeyDown(TestHelper.keyDown(TestHelper.KEY_TAB)); assertTrimmed(ac.prefix); assertTrimmed(ac.suffix); // B ac.onKeyDown(TestHelper.keyDown(TestHelper.KEY_TAB)); assertTrimmed(ac.prefix); assertTrimmed(ac.suffix); // C ac.onKeyDown(TestHelper.keyDown(TestHelper.KEY_TAB)); assertTrimmed(ac.prefix); assertTrimmed(ac.suffix); }); test('shows only five elements after the current element', function() { var ac = new Autocomplete([['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']]); // A B C D E ... assert.equal(-1, ac.current); assertNotClass(ac, 0, 5, 'trimmed'); assertClass(ac, 5, ac.words.length, 'trimmed'); // A: A B C D E ... ac.onKeyDown(TestHelper.keyDown(TestHelper.KEY_TAB)); assertNotClass(ac, 0, 5, 'trimmed'); assertClass(ac, 5, ac.words.length, 'trimmed'); // B: B C D E F ... ac.onKeyDown(TestHelper.keyDown(TestHelper.KEY_TAB)); assertClass(ac, 0, 1, 'trimmed'); assertNotClass(ac, 1, 6, 'trimmed'); assertClass(ac, 6, ac.words.length, 'trimmed'); // A: A B C D E ... (shift) ac.onKeyDown(TestHelper.keyDown(TestHelper.KEY_TAB, { shiftKey: true })); assert.equal(0, ac.current); assertNotClass(ac, 0, 5, 'trimmed'); assertClass(ac, 5, ac.words.length, 'trimmed'); }); test('keeps to show the last five elements', function() { var ac = new Autocomplete([['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']]); // G: D E F G H for (var i = 0; i < 7; ++i) ac.onKeyDown(TestHelper.keyDown(TestHelper.KEY_TAB)); assert.equal(6, ac.current); assertClass(ac, 0, 3, 'trimmed'); assertNotClass(ac, 3, ac.words.length, 'trimmed'); // H: D E F G H (keep last five elements) ac.onKeyDown(TestHelper.keyDown(TestHelper.KEY_TAB)); assert.equal(7, ac.current); assertClass(ac, 0, 3, 'trimmed'); assertNotClass(ac, 3, ac.words.length, 'trimmed'); // A: A B C D E ... ac.onKeyDown(TestHelper.keyDown(TestHelper.KEY_TAB)); assertNotClass(ac, 0, 5, 'trimmed'); assertClass(ac, 5, ac.words.length, 'trimmed'); // H: D E F G H (shift) ac.onKeyDown(TestHelper.keyDown(TestHelper.KEY_TAB, { shiftKey: true })); assert.equal(7, ac.current); assertClass(ac, 0, 3, 'trimmed'); assertNotClass(ac, 3, ac.words.length, 'trimmed'); }); test('handles five elements with prefix', function() { var ac = new Autocomplete([['A', 'B1', 'B2', 'B3', 'C']], 'B'); assert.equal(-1, ac.current); assert.equal(3, ac.elements.length); assertNotClass(ac, 0, 3, 'trimmed'); ac.onKeyDown(TestHelper.keyDown(TestHelper.KEY_TAB)); assert.equal(0, ac.current); assert.equal(3, ac.elements.length); assertNotClass(ac, 0, 3, 'trimmed'); }); test('can select item with the Ctrl-E key', function() { var ac = new Autocomplete([['A', 'B', 'C']]); assert.equal(-1, ac.current); ac.onKeyDown(TestHelper.keyDown(TestHelper.KEY_E, { ctrlKey: true })); assert.equal(0, ac.current); ac.onKeyDown(TestHelper.keyDown(TestHelper.KEY_E, { ctrlKey: true })); assert.equal(1, ac.current); ac.onKeyDown(TestHelper.keyDown(TestHelper.KEY_E, { ctrlKey: true })); assert.equal(2, ac.current); ac.onKeyDown(TestHelper.keyDown(TestHelper.KEY_E, { ctrlKey: true })); assert.equal(0, ac.current); }); }); suite('Refinements', function() { setup(function() { this.ac = new Autocomplete([['other', 'other2', 'something', 'somewhat', 'somewhere', 'test']]); this.refine = function(prefix) { this.ac.refine(prefix); }; }); test('empty string', function() { this.refine(''); assert(!this.ac.confirmed); assertRange(this, 0, this.ac.words.length, 0); }); test('some', function() { this.refine('some'); assert(!this.ac.confirmed); assert.equal(3, this.ac.elements.length); }); test('confirmable', function() { this.refine('somet'); assert.equal(this.ac.confirmed, 'something'); assertRange(this, 2, 3); }); test('decrement', function() { this.refine('o'); this.refine(''); assertRange(this, 0, this.ac.words.length, 0); }); test('other => empty => some', function() { this.refine('other'); this.refine(''); this.refine('some'); assert.equal(3, this.ac.elements.length); }); test('some => empty', function() { this.refine('some'); this.refine(''); assertRange(this, 0, this.ac.words.length); }); }); suite('Edge Cases', function() { test('does nothing if the word list is empty', function() { var ac = new Autocomplete([]); assert.doesNotThrow(function() { ac.onKeyDown(TestHelper.keyDown(TestHelper.KEY_TAB)); ac.onKeyDown(TestHelper.keyDown(TestHelper.KEY_ENTER)); }); }); }); function assertClass(ac, left, right, className) { for (var i = left; i < right; ++i) { assert.ok(hasClass(ac.elements[i], className), i + '-th element should have ' + className); } } function assertNotClass(ac, left, right, className) { for (var i = left; i < right; ++i) { assert.notOk(hasClass(ac.elements[i], className), i + '-th element should not have ' + className); } } function assertRange(self, left, right, hiddenCnt) { assert.equal(left, self.ac.left, 'left'); assert.equal(right, self.ac.right, 'right'); if (hiddenCnt) assert.equal(hiddenCnt, self.ac.view.getElementsByClassName('hidden').length, 'hiddenCnt'); } function assertTrimmed(el) { assert.ok(hasClass(el, 'trimmed')); } function assertNotTrimmed(el) { assert.notOk(hasClass(el, 'trimmed')); } }); web-console-4.2.1/test/templates/test/dom_helper_test.js000066400000000000000000000014531447570540700234220ustar00rootroot00000000000000suite('DOM Helpers', function() { test('hasClass() checks if an element contains a class name', function() { var div = document.createElement('div'); assert.notOk(hasClass(div, 'hello')); div.className = 'hello'; assert.ok(hasClass(div, 'hello')); div.className = 'hello world'; assert.ok(hasClass(div, 'hello')); }); test('addClass() adds a class name to an element', function() { var div = document.createElement('div'); assert.notOk(hasClass(div, 'hello')); addClass(div, 'hello'); assert.ok(hasClass(div, 'hello')); }); test('removeClass() removes a class name from an element', function() { var div = document.createElement('div'); div.className = 'hello world'; removeClass(div, 'hello'); assert.notOk(hasClass(div, 'hello')); }); }); web-console-4.2.1/test/templates/test/repl_console_test.js000066400000000000000000000146221447570540700237720ustar00rootroot00000000000000suite('REPLConsole', function() { suiteSetup(function() { this.stage = document.createElement('div'); document.body.appendChild(this.stage); }); setup(function() { var elm = document.createElement('div'); elm.innerHTML = '
'; this.stage.appendChild(elm); var consoleOptions = { mountPoint: '/mock', sessionId: 'result' }; this.console = REPLConsole.installInto('console', consoleOptions); }); teardown(function() { removeAllChildren(this.stage); }); suite('REPLConsole.installInto()', function() { setup(function() { var elm = document.createElement('div'); elm.innerHTML = '
'; this.stage.appendChild(elm); }); test('console can be installed with dataset', function() { var console = REPLConsole.installInto('my-console'); assert.equal('my-prompt-label', console.prompt); assert.equal('my-mount-point', console.mountPoint); assert.equal('my-session-id', console.sessionId); }); test('console can be installed with argument options', function() { var options = { mountPoint: 'other-mount-point', sessionId: 'other-session-id' }; var console = REPLConsole.installInto('my-console', options); assert.equal('other-mount-point', console.mountPoint); assert.equal('other-session-id', console.sessionId); assert.equal('my-prompt-label', console.prompt); }); }); suite('Installing', function() { test('#install renders console into an element', function() { var div = document.createElement('div'); var console = new REPLConsole; console.install(div); assert.equal(1, div.getElementsByClassName('console-inner').length); assert.equal(1, div.getElementsByClassName('resizer').length); assert.equal(1, div.getElementsByClassName('close-button').length); }); }); suite('Actions', function() { test('close button removes console on clicked', function() { var div = document.createElement('div'); div.id = 'my-console'; this.stage.appendChild(div); REPLConsole.installInto('my-console'); assert.isNotNull(document.getElementById('my-console')); var btn = div.getElementsByClassName('close-button')[0]; TestHelper.triggerEvent(btn, 'click'); assert.isNull(document.getElementById('my-console')); }); }); suite('Command Handling', function() { test('#commandHandle renders output to console', function(done) { var inner = this.console.inner; this.console.commandHandle('fake-input', function(result, response) { var msg = inner.getElementsByClassName('console-message')[0]; assert.ok(result); assert.match(msg.innerText, /"fake-result"/); assert.notOk(hasClass(msg, 'error-message')); done(); }); }); test('#commandHandle shows error message if something is wrong', function(done) { var inner = this.console.inner; this.console.sessionId = 'error'; this.console.commandHandle('fake-input', function(result, response) { var msg = inner.getElementsByClassName('console-message')[0]; assert.notOk(result); assert.match(msg.innerText, /fake-error-message/); assert.ok(hasClass(msg, 'error-message')); done(); }); }); test('#commandHandle shows HTTP status code if something is wrong', function(done) { var inner = this.console.inner; this.console.sessionId = 'error.txt'; this.console.commandHandle('fake-input', function(result, response) { var msg = inner.getElementsByClassName('console-message')[0]; assert.notOk(result); assert.match(msg.innerText, /400 Bad Request/); done(); }); }); }); suite('Auto Completion', function() { test('swap last word', function() { this.console.setInput('hello world'); this.console.swapCurrentWord('word'); assert.equal(this.console._input, 'hello word'); }); test('swap first word', function() { this.console.setInput('hello world'); this.console._caretPos = 0; this.console.swapCurrentWord('my'); assert.equal(this.console._input, 'my world'); }); test('current word', function() { this.console.setInput('hello world'); assert.equal(this.console.getCurrentWord(), 'world'); this.console._caretPos = 0; assert.equal(this.console.getCurrentWord(), 'hello'); this.console._caretPos = 5; assert.equal(this.console.getCurrentWord(), ''); this.console._caretPos = 6; assert.equal(this.console.getCurrentWord(), 'world'); }); test('console can start auto completion by typing tab key', function(done) { this.console.setInput('some'); assert.notOk(this.console.autocomplete); this.console.onKeyDown(TestHelper.keyDown(9)); // tab assert.ok(this.console.autocomplete); var self = this; setTimeout(function() { self.console.autocomplete.onFinished(function(word) { assert.equal('something', word); done(); }); self.console.onKeyDown(TestHelper.keyDown(TestHelper.KEY_TAB)); self.console.onKeyDown(TestHelper.keyDown(TestHelper.KEY_ENTER)); }, 100); }); }); suite('Auto Suggestion', function() { test('shows first matched word in current context', function(done) { var c = this.console; c.setInput('some'); setTimeout(function() { assert.match(c.promptDisplay.innerText, /^something/); done(); }, 100); }); test('hides automatically in a few seconds', function(done) { var c = this.console; c.suggestWait = 200; c.setInput('some'); assert.equal('some\u00A0', c.promptDisplay.innerText); setTimeout(function() { assert.match(c.promptDisplay.innerText, /something/); setTimeout(function() { assert.equal('some\u00A0', c.promptDisplay.innerText); done(); }, 200); }, 100); }); test('inserts the current word if tab key is pressed', function(done) { var c = this.console; c.setInput('some'); setTimeout(function() { c.onKeyDown(TestHelper.keyDown(TestHelper.KEY_TAB)); assert.equal('something', c._input); done(); }, 100); }); }); }); web-console-4.2.1/test/templates/test/test_helper.js000066400000000000000000000012341447570540700225600ustar00rootroot00000000000000(function() { 'use strict'; var TestHelper = { KEY_TAB: 9, KEY_ENTER: 13, KEY_E: 69, triggerEvent: function(el, eventName) { var event = document.createEvent("MouseEvents"); event.initEvent(eventName, true, true); // type, bubbles, cancelable el.dispatchEvent(event); }, keyDown: function(keyCode, options) { options = options || {}; return { keyCode: keyCode, ctrlKey: options.ctrlKey, shiftKey: options.shiftKey, preventDefault: function() {}, stopPropagation: function() {} }; } }; window.TestHelper = TestHelper; window.assert = chai.assert; })(); web-console-4.2.1/test/test_helper.rb000066400000000000000000000043201447570540700175710ustar00rootroot00000000000000# frozen_string_literal: true case RUBY_ENGINE when "ruby", "rbx" require "simplecov" SimpleCov.start "rails" end # Configure Rails Environment ENV["RAILS_ENV"] = "test" require "active_support/core_ext/kernel/reporting.rb" # The warnings in the dummy app are intentional and are used to manually test # the introspection abilities of the console. silence_warnings do require File.expand_path("../dummy/config/environment.rb", __FILE__) end require "rails/test_help" Rails.backtrace_cleaner.remove_silencers! # Load support files Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } # rails-dom-testing assertions doesn't like the JavaScript we inject into the page. module SilenceRailsDomTesting def assert_select(*) silence_warnings { super } end end # Allows you to specify test to run only on specific Ruby platforms. # # Example: # # test 'CRuby specific feature', only: :ruby # test 'CRuby and JRuby specific feature', only: %w(ruby jruby) # # If the :only option isn't present, the test is defined for all the platforms. module PlatformSpecificTestMacro def test(name, options = {}) platforms = Array(options[:only]).map(&:to_s) if platforms.blank? || RUBY_ENGINE.in?(platforms) super(name) end end end ActiveSupport::TestCase.class_eval do extend PlatformSpecificTestMacro end ActionDispatch::IntegrationTest.class_eval do include SilenceRailsDomTesting end # A copy of Kernel#capture in active_support/core_ext/kernel/reporting.rb as # its getting deprecated past 4.2. Its not thread safe, but I don't need it to # be in the tests def capture(stream) stream = stream.to_s captured_stream = Tempfile.new(stream) stream_io = eval("$#{stream}") origin_stream = stream_io.dup stream_io.reopen(captured_stream) yield stream_io.rewind captured_stream.read ensure captured_stream.close captured_stream.unlink stream_io.reopen(origin_stream) end alias silence capture # Load fixtures from the engine if ActiveSupport::TestCase.method_defined?(:fixture_path=) ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__) end require "mocha/minitest" module External def self.exception raise rescue => exc exc end end web-console-4.2.1/test/web_console/000077500000000000000000000000001447570540700172265ustar00rootroot00000000000000web-console-4.2.1/test/web_console/context_test.rb000066400000000000000000000016351447570540700223030ustar00rootroot00000000000000# frozen_string_literal: true require "test_helper" module WebConsole class ContextTest < ActiveSupport::TestCase test "#extract(empty) includes local variables" do local_var = local_var = "local" assert context(binding).include?(:local_var) end test "#extract(empty) includes instance variables" do @instance_var = "instance" assert context(binding).include?(:@instance_var) end test "#extract(empty) includes global variables" do $global_var = "global" assert context(binding).include?(:$global_var) end test "#extract(obj) returns methods" do assert context(binding, "Rails").include?("Rails.root") end test "#extract(obj) returns constants" do assert context(binding, "WebConsole").include?("WebConsole::Middleware") end private def context(b, o = "") Context.new(b).extract(o).flatten end end end web-console-4.2.1/test/web_console/evaluator_test.rb000066400000000000000000000037421447570540700226220ustar00rootroot00000000000000# frozen_string_literal: true require "test_helper" module WebConsole class EvaluatorTest < ActiveSupport::TestCase class TestError < StandardError def backtrace [ "/web-console/lib/web_console/repl.rb:16:in `eval'", "/web-console/lib/web_console/repl.rb:16:in `eval'" ] end end class BadlyDefinedError < StandardError def backtrace nil end end setup do @repl1 = @repl = Evaluator.new @repl2 = Evaluator.new end test "sending input returns the result as output" do assert_equal "=> 42\n", @repl.eval("foo = 42") end test "preserves the session in the binding" do assert_equal "=> 42\n", @repl.eval("foo = 42") assert_equal "=> 50\n", @repl.eval("foo + 8") end test "session preservation requires same bindings" do assert_equal "=> 42\n", @repl1.eval("foo = 42") assert_equal "=> 42\n", @repl2.eval("foo") end test "formats exceptions similarly to IRB" do repl = Evaluator.new(binding) assert_equal <<-END.strip_heredoc, repl.eval("raise TestError, 'panic'") #{TestError.name}: panic \tfrom /web-console/lib/web_console/repl.rb:16:in `eval' \tfrom /web-console/lib/web_console/repl.rb:16:in `eval' END end test "no backtrace is shown if exception backtrace is blank" do repl = Evaluator.new(binding) assert_equal <<-END.strip_heredoc, repl.eval("raise BadlyDefinedError") #{BadlyDefinedError.name}: #{BadlyDefinedError.name} END end test "Evaluator callers are cleaned up of unneeded backtraces", only: :ruby do # Those have to be on the same line to get the same trace. repl, trace = Evaluator.new(binding), current_trace assert_equal <<-END.strip_heredoc, repl.eval("raise 'oops'") RuntimeError: oops \tfrom #{trace} END end private def current_trace caller.first end end end web-console-4.2.1/test/web_console/exception_mapper_test.rb000066400000000000000000000020151447570540700241520ustar00rootroot00000000000000# frozen_string_literal: true require "test_helper" module WebConsole class ExceptionMapperTest < ActiveSupport::TestCase test "#first tries to find the first application binding" do Rails.stubs(:root).returns Pathname(__FILE__).parent mapper = ExceptionMapper.new(External.exception) assert_equal __FILE__, SourceLocation.new(mapper.first).path end test ".[] tries match the binding for trace index" do exception = External.exception mapper = ExceptionMapper.new(exception) last_index = exception.backtrace.count - 1 file, line = exception.backtrace.last.split(":") assert_equal file, SourceLocation.new(mapper[last_index]).path assert_equal line.to_i, SourceLocation.new(mapper[last_index]).lineno end test ".[] fall backs to index if no trace can be found" do exception = External.exception mapper = ExceptionMapper.new(exception) unbound_index = exception.backtrace.count assert_nil mapper[unbound_index] end end end web-console-4.2.1/test/web_console/helper_test.rb000066400000000000000000000036121447570540700220730ustar00rootroot00000000000000# frozen_string_literal: true require "test_helper" module WebConsole class HelperTest < ActionDispatch::IntegrationTest class BaseApplication def call(env) [ status, headers, body ] end private def request Request.new(@env) end def status 500 end def headers { Rack::CONTENT_TYPE => "text/html; charset=utf-8" } end def body Array(<<-HTML.strip_heredoc) Hello world

Hello world

HTML end end class SingleConsoleApplication < BaseApplication def call(env) @env = env console super end end class MultipleConsolesApplication < BaseApplication def call(env) @env = env console console super end end setup do Thread.current[:__web_console_exception] = nil Thread.current[:__web_console_binding] = nil Request.stubs(:permissions).returns(IPAddr.new("0.0.0.0/0")) @app = Middleware.new(SingleConsoleApplication.new) end test "renders a console into a view" do get "/", params: nil, headers: { "CONTENT_TYPE" => "text/html" } assert_select "#console" end test "raises an error when trying to spawn a console more than once" do @app = Middleware.new(MultipleConsolesApplication.new) assert_raises(DoubleRenderError) do get "/", params: nil, headers: { "CONTENT_TYPE" => "text/html" } end end test "doesn't hijack current view" do get "/", params: nil, headers: { "CONTENT_TYPE" => "text/html" } assert_select "#hello-world" assert_select "#console" end end end web-console-4.2.1/test/web_console/injector_test.rb000066400000000000000000000021201447570540700224220ustar00rootroot00000000000000# frozen_string_literal: true require "test_helper" module WebConsole class InjectorTest < ActiveSupport::TestCase test "closes body if closable" do closed = false body = [ "foo" ] body.define_singleton_method(:close) { closed = true } assert_equal [ [ "foobar" ], {} ], Injector.new(body, {}).inject("bar") assert closed end test "support fancy bodies like Rack::BodyProxy" do closed = false body = Rack::BodyProxy.new([ "foo" ]) { closed = true } assert_equal [ [ "foobar" ], {} ], Injector.new(body, {}).inject("bar") assert closed end test "support fancy bodies like ActionDispatch::Response::RackBody" do body = ActionDispatch::Response.create(200, {}, [ "foo" ]).to_a.last assert_equal [ [ "foobar" ], {} ], Injector.new(body, {}).inject("bar") end test "updates the content-length header" do body = [ "foo" ] headers = { Rack::CONTENT_LENGTH => 3 } assert_equal [ [ "foobar" ], { Rack::CONTENT_LENGTH => "6" } ], Injector.new(body, headers).inject("bar") end end end web-console-4.2.1/test/web_console/integration_test.rb000066400000000000000000000026031447570540700231360ustar00rootroot00000000000000# frozen_string_literal: true require "test_helper" module WebConsole class IntegrationTest < ActiveSupport::TestCase test "Exception#bindings returns all the bindings of where the error originated" do exc = FlatScenario.new.call assert_equal 6, SourceLocation.new(exc.bindings.first).lineno end test "Exception#bindings returns all the bindings for a custom error" do exc = CustomErrorScenario.new.call assert_equal 8, SourceLocation.new(exc.bindings.first).lineno end test "Exception#bindings returns all the bindings for a bad custom error" do exc = BadCustomErrorScenario.new.call assert_equal 13, SourceLocation.new(exc.bindings.first).lineno end test "Exception#bindings goes down the stack" do exc = BasicNestedScenario.new.call assert_equal 14, SourceLocation.new(exc.bindings.first).lineno end test "Exception#bindings inside of an eval" do exc = EvalNestedScenario.new.call assert_equal 14, SourceLocation.new(exc.bindings.first).lineno end test "re-raising doesn't lose Exception#bindings information" do exc = ReraisedScenario.new.call assert_equal 6, SourceLocation.new(exc.bindings.first).lineno end test "Exception#bindings is empty when exception is still not raised" do exc = RuntimeError.new assert_equal [], exc.bindings end end end web-console-4.2.1/test/web_console/interceptor_test.rb000066400000000000000000000015441447570540700231540ustar00rootroot00000000000000# frozen_string_literal: true require "test_helper" module WebConsole class InterceptorTest < ActionDispatch::IntegrationTest test "follows ActionView::Template::Error original error in Thread.current[:__web_console_exception]" do request = Request.new({}) request.set_header("action_dispatch.backtrace_cleaner", ActiveSupport::BacktraceCleaner.new) Interceptor.call(request, generate_template_error) assert_equal 42, Thread.current[:__web_console_exception].bindings.first.eval("@ivar") end def generate_template_error lookup_context = ActionView::LookupContext.new([]) WebConsole::View.with_empty_template_cache.with_context(lookup_context).render(inline: <<~ERB) <% @ivar = 42 %> <%= nil.raise %> err err end end end web-console-4.2.1/test/web_console/middleware_test.rb000066400000000000000000000167741447570540700227460ustar00rootroot00000000000000# frozen_string_literal: true require "test_helper" module WebConsole class MiddlewareTest < ActionDispatch::IntegrationTest class Application def initialize(options = {}) @response_content_type = options.fetch(:response_content_type, "text/html") @response_content_length = options.fetch(:response_content_length, nil) end def call(env) [ status, headers, body ] end def body @body ||= StringIO.new(<<-HTML.strip_heredoc) Hello world

Hello world

HTML end private def status 500 end def headers Hash.new.tap do |header_hash| header_hash[Rack::CONTENT_TYPE] = "#{@response_content_type}; charset=utf-8" unless @response_content_type.nil? header_hash[Rack::CONTENT_LENGTH] = @response_content_length unless @response_content_length.nil? end end end setup do Thread.current[:__web_console_exception] = nil Thread.current[:__web_console_binding] = nil Rails.stubs(:root).returns Pathname(__FILE__).parent Request.stubs(:permissions).returns(IPAddr.new("0.0.0.0/0")) Middleware.mount_point = "" @app = Middleware.new(Application.new) end test "render console in an html application from web_console.binding" do Thread.current[:__web_console_binding] = binding get "/", params: nil assert_select "#console" end test "render console in an html application from web_console.exception" do Thread.current[:__web_console_exception] = raise_exception get "/", params: nil assert_select "body > #console" end test "render error_page.js from web_console.exception" do Thread.current[:__web_console_exception] = raise_exception get "/", params: nil assert_select "body > script[data-template=error_page]" end test "render console if response format is HTML" do Thread.current[:__web_console_binding] = binding @app = Middleware.new(Application.new(response_content_type: "text/html")) get "/", params: nil assert_select "#console" end test "sets correct content-length header" do Thread.current[:__web_console_binding] = binding @app = Middleware.new(Application.new(response_content_length: 7)) get "/", params: nil assert_equal(response.body.size, response.headers[Rack::CONTENT_LENGTH].to_i) end test "it closes original body if rendering console" do Thread.current[:__web_console_binding] = binding inner_app = Application.new(response_content_type: "text/html") @app = Middleware.new(inner_app) get "/", params: nil assert(inner_app.body.closed?, "body should be closed") end test "does not render console if response format is empty" do Thread.current[:__web_console_binding] = binding @app = Middleware.new(Application.new(response_content_type: nil)) get "/", params: nil assert_select "#console", 0 end test "does not render console if response format is not HTML" do Thread.current[:__web_console_binding] = binding @app = Middleware.new(Application.new(response_content_type: "application/json")) get "/", params: nil assert_select "#console", 0 end test "returns x-web-console-session-id as response header" do Thread.current[:__web_console_binding] = binding get "/", params: nil session_id = response.headers["x-web-console-session-id"] assert_not Session.find(session_id).nil? end test "doesn't render console in non html response" do Thread.current[:__web_console_binding] = binding @app = Middleware.new(Application.new(response_content_type: "application/json")) get "/", params: nil assert_select "#console", 0 end test "doesn't render console from not allowed IP" do Thread.current[:__web_console_binding] = binding Request.stubs(:permissions).returns(IPAddr.new("127.0.0.1")) silence(:stderr) do get "/", params: nil, headers: { "REMOTE_ADDR" => "1.1.1.1" } end assert_select "#console", 0 end test "doesn't render console without a web_console.binding or web_console.exception" do get "/", params: nil assert_select "#console", 0 end test "can evaluate code and return it as a JSON" do session, line = Session.new([[binding]]), __LINE__ Session.stubs(:from).returns(session) get "/", params: nil put "/repl_sessions/#{session.id}", xhr: true, params: { input: "line" } assert_equal("=> #{line}\n", JSON.parse(response.body)["output"]) end test "can switch bindings on error pages" do session = Session.new([WebConsole::ExceptionMapper.new(raise_exception)]) Session.stubs(:from).returns(session) get "/", params: nil post "/repl_sessions/#{session.id}/trace", xhr: true, params: { frame_id: 1 } assert_equal({ ok: true }.to_json, response.body) end test "can switch to the cause on error pages" do nested_error = begin raise "First error" rescue raise "Second Error" rescue $! end session = Session.new(WebConsole::ExceptionMapper.follow(nested_error)) Session.stubs(:from).returns(session) get "/", params: nil post "/repl_sessions/#{session.id}/trace", xhr: true, params: { frame_id: 1, exception_object_id: nested_error.cause.object_id } assert_equal({ ok: true }.to_json, response.body) end test "can be changed mount point" do Middleware.mount_point = "/customized/path" session, value = Session.new([[binding]]), __LINE__ put "/customized/path/repl_sessions/#{session.id}", params: { input: "value" }, xhr: true assert_equal("=> #{value}\n", JSON.parse(response.body)["output"]) end test "can return context information by passing a context param" do hello = hello = "world" session = Session.new([[binding]]) Session.stubs(:from).returns(session) get "/" put "/repl_sessions/#{session.id}", xhr: true, params: { context: "" } assert_includes(JSON.parse(response.body)["context"], local_variables.map(&:to_s)) end test "unavailable sessions respond to the user with a message" do put "/repl_sessions/no_such_session", xhr: true, params: { input: "__LINE__" } assert_equal(404, response.status) end test "unavailable sessions can occur on binding switch" do post "/repl_sessions/no_such_session/trace", xhr: true, params: { frame_id: 1 } assert_equal(404, response.status) end test "reraises application errors" do @app = proc { raise } assert_raises(RuntimeError) { get "/" } end test "logs internal errors with Rails.logger" do io = StringIO.new logger = ActiveSupport::Logger.new(io) old_logger, Rails.logger = Rails.logger, logger begin @app.stubs(:call_app).raises("whoops") get "/" rescue RuntimeError output = io.rewind && io.read lines = output.lines assert_equal ["\n", "RuntimeError: whoops\n"], lines.slice!(0, 2) ensure Rails.logger = old_logger end end private def raise_exception raise rescue => exc exc end end end web-console-4.2.1/test/web_console/permissions_test.rb000066400000000000000000000022631447570540700231700ustar00rootroot00000000000000# frozen_string_literal: true require "test_helper" module WebConsole class PermissionsTest < ActiveSupport::TestCase test "localhost is always allowed" do permissions = permit("8.8.8.8") assert_includes permissions, "127.0.0.1" assert_includes permissions, "::1" end test "permits single IPs" do permissions = permit("8.8.8.8") assert_includes permissions, "8.8.8.8" end test "permits whole networks" do permissions = permit("172.16.0.0/12") 1.upto(255).each do |n| assert_includes permissions, "172.16.0.#{n}" end end test "permits multiple networks" do permissions = permit %w(172.16.0.0/12 192.168.0.0/16) 1.upto(255).each do |n| assert_includes permissions, "172.16.0.#{n}" assert_includes permissions, "192.168.0.#{n}" end end test "ignores UNIX socket" do permissions = permit("8.8.8.8") assert_not_includes permissions, "unix:" end test "human readable presentation" do assert_includes permit.to_s, "127.0.0.0/127.255.255.255, ::1" end private def permit(*args) Permissions.new(*args) end end end web-console-4.2.1/test/web_console/request_test.rb000066400000000000000000000036371447570540700223130ustar00rootroot00000000000000# frozen_string_literal: true require "test_helper" module WebConsole class RequestTest < ActiveSupport::TestCase setup do Request.stubs(:permissions).returns(IPAddr.new("127.0.0.1")) end test "#permitted? is falsy for not allowed IPs" do req = request("http://example.com", "REMOTE_ADDR" => "0.0.0.0") assert_not req.permitted? end test "#permitted? is truthy for allowed IPs" do req = request("http://example.com", "REMOTE_ADDR" => "127.0.0.1") assert req.permitted? end test "#permitted? is truthy for allowed IPs via allowed proxies" do req = request("http://example.com", "REMOTE_ADDR" => "127.0.0.1", "HTTP_X_FORWARDED_FOR" => "127.0.0.0") assert req.permitted? end test "#permitted? is falsy for not allowed IPs via allowed proxies" do req = request("http://example.com", "REMOTE_ADDR" => "127.0.0.1", "HTTP_X_FORWARDED_FOR" => "0.0.0.0") assert_not req.permitted? end test "#permitted? is falsy for lying not allowed IPs via allowed proxies" do req = request("http://example.com", "REMOTE_ADDR" => "127.0.0.1", "HTTP_X_FORWARDED_FOR" => "10.0.0.0, 127.0.0.0") assert_not req.permitted? end test "#permitted? is falsy for allowed IPs via not allowed proxies" do req = request("http://example.com", "REMOTE_ADDR" => "10.0.0.0", "HTTP_X_FORWARDED_FOR" => "127.0.0.0") assert_not req.permitted? end test "#permitted? is falsy for spoofed IPs" do req = request("http://example.com", "HTTP_CLIENT_IP" => "127.0.0.1", "HTTP_X_FORWARDED_FOR" => "127.0.0.0") assert_not req.permitted? end private def request(*args) Request.new(mock_env(*args)) end def mock_env(*args) Rack::MockRequest.env_for(*args) end def xhr(*args) args[1]["HTTP_X_REQUESTED_WITH"] ||= "XMLHttpRequest" request(*args) end end end web-console-4.2.1/test/web_console/session_test.rb000066400000000000000000000055321447570540700223020ustar00rootroot00000000000000# frozen_string_literal: true require "test_helper" module WebConsole class SessionTest < ActiveSupport::TestCase class ValueAwareError < StandardError def self.raise(value) ::Kernel.raise self, value rescue => exc exc end def self.raise_nested_error(value) ::Kernel.raise self, value rescue value = 1 # Override value so we can target the binding here ::Kernel.raise "Second Error" rescue $! end attr_reader :value def initialize(value) @value = value end end setup do Session.inmemory_storage.clear @session = Session.new([[binding]]) end test "returns nil when a session is not found" do assert_nil Session.find("nonexistent session") end test "find returns a persisted object" do assert_equal @session, Session.find(@session.id) end test "can evaluate code in the currently selected binding" do assert_equal "=> 42\n", @session.eval("40 + 2") end test "use first binding if no application bindings" do binding = Object.new.instance_eval do def eval(string) case string when "__FILE__" then framework when "called?" then "yes" end end def source_location end self end session = Session.new([[binding]]) assert_equal session.eval("called?"), "=> \"yes\"\n" end test "#from can create session from a single binding" do value, saved_binding = __LINE__, binding Thread.current[:__web_console_binding] = saved_binding session = Session.from(__web_console_binding: saved_binding) assert_equal "=> #{value}\n", session.eval("value") end test "#from can create session from an exception" do value = __LINE__ exc = ValueAwareError.raise(value) session = Session.from(__web_console_exception: exc) assert_equal "=> #{exc.value}\n", session.eval("value") end test "#from can switch to bindings" do value = __LINE__ exc = ValueAwareError.raise(value) session = Session.from(__web_console_exception: exc) session.switch_binding_to(1, exc.object_id) assert_equal "=> #{value}\n", session.eval("value") end test "#from can switch to the cause" do value = __LINE__ exc = ValueAwareError.raise_nested_error(value) session = Session.from(__web_console_exception: exc) session.switch_binding_to(1, exc.cause.object_id) assert_equal "=> #{value}\n", session.eval("value") end test "#from prioritizes exceptions over bindings" do exc = ValueAwareError.raise(42) session = Session.from(__web_console_exception: exc, __web_console_binding: binding) assert_equal "=> WebConsole::SessionTest::ValueAwareError\n", session.eval("self") end end end web-console-4.2.1/test/web_console/whiny_request_test.rb000066400000000000000000000014241447570540700235210ustar00rootroot00000000000000# frozen_string_literal: true require "test_helper" module WebConsole class WhinyRequestTest < ActiveSupport::TestCase test "#permitted? logs out to stderr" do Request.stubs(:permissions).returns(IPAddr.new("127.0.0.1")) WebConsole.logger.expects(:info) req = request("http://example.com", "REMOTE_ADDR" => "0.0.0.0") assert_not req.permitted? end test "#permitted? is falsy for spoofed IPs" do WebConsole.logger.expects(:info) req = request("http://example.com", "HTTP_CLIENT_IP" => "127.0.0.1", "HTTP_X_FORWARDED_FOR" => "127.0.0.0") assert_not req.permitted? end private def request(*args) request = Request.new(Rack::MockRequest.env_for(*args)) WhinyRequest.new(request) end end end web-console-4.2.1/web-console.gemspec000066400000000000000000000016251447570540700175360ustar00rootroot00000000000000# frozen_string_literal: true $:.push File.expand_path("../lib", __FILE__) require "web_console/version" Gem::Specification.new do |s| s.name = "web-console" s.version = WebConsole::VERSION s.authors = ["Hailey Somerville", "Genadi Samokovarov", "Guillermo Iguaran", "Ryan Dao"] s.email = ["hailey@hailey.lol", "gsamokovarov@gmail.com", "guilleiguaran@gmail.com", "daoduyducduong@gmail.com"] s.homepage = "https://github.com/rails/web-console" s.summary = "A debugging tool for your Ruby on Rails applications." s.license = "MIT" s.files = Dir["lib/**/*", "MIT-LICENSE", "Rakefile", "README.markdown", "CHANGELOG.markdown"] s.required_ruby_version = ">= 2.5" rails_version = ">= 6.0.0" s.add_dependency "railties", rails_version s.add_dependency "activemodel", rails_version s.add_dependency "actionview", rails_version s.add_dependency "bindex", ">= 0.4.0" end