# frozen-string-literal: true # class Roda module RodaPlugins # The exception_page plugin provides an exception_page method that is designed # to be called inside the error handler to provide a page to the developer # with debugging information. It should only be used in developer environments # with trusted clients, as it can leak source code and other information that # may be useful for attackers if used in other environments. # # Example: # # plugin :exception_page # plugin :error_handler do |e| # next exception_page(e) if ENV['RACK_ENV'] == 'development' # # ... # end # # The exception_page plugin is based on Rack::ShowExceptions, with the following # differences: # # * Not a middleware, so it doesn't handle exceptions itself, and has no effect # on the callstack unless the exception_page method is called. # * Supports external javascript and stylesheets, allowing context toggling to # work in applications that use a content security policy to restrict inline # javascript and stylesheets (:assets, :css_file, and :js_file options). # * Has fewer dependencies (does not require ostruct and erb). # * Sets the Content-Type for the response, and returns the body string, but does # not modify other headers or the response status. # * Supports a configurable amount of context lines in backtraces (:context option). # * Supports optional JSON formatted output, if used with the json plugin (:json option). # # To use the external javascript and stylesheets, you can call +r.exception_page_assets+ # in your routing tree: # # route do |r| # # ... # # # serve GET /exception_page.{css,js} requests # # Use with assets: true +exception_page+ option # r.exception_page_assets # # r.on "static" do # # serve GET /static/exception_page.{css,js} requests # # Use with assets: '/static' +exception_page+ option # r.exception_page_assets # end # end # # It's also possible to store the asset information in static files and serve those, # you can get the current assets by calling: # # Roda::RodaPlugins::ExceptionPage.css # Roda::RodaPlugins::ExceptionPage.js # # As the exception_page plugin is based on Rack::ShowExceptions, it is also under # rack's license: # # Copyright (C) 2007-2018 Christian Neukirchen # # 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 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. # # The HTML template used in Rack::ShowExceptions was based on Django's # template and is under the following license: # # adapted from Django # Copyright (c) Django Software Foundation and individual contributors. # Used under the modified BSD license: # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 module ExceptionPage def self.load_dependencies(app) app.plugin :h end # Stylesheet used by the HTML exception page def self.css <div { border-bottom:1px solid #ddd; } h1 { font-weight:normal; } h2 { margin-bottom:.8em; } h2 span { font-size:80%; color:#666; font-weight:normal; } h3 { margin:1em 0 .5em 0; } h4 { margin:0 0 .5em 0; font-weight: normal; } table { border:1px solid #ccc; border-collapse: collapse; background:white; } tbody td, tbody th { vertical-align:top; padding:2px 3px; } thead th { padding:1px 6px 1px 3px; background:#fefefe; text-align:left; font-weight:normal; font-size:11px; border:1px solid #ddd; } tbody th { text-align:right; color:#666; padding-right:.5em; } table.vars { margin:5px 0 2px 40px; } table.vars td, table.req td { font-family:monospace; } table td.code { width:100%;} table td.code div { overflow:hidden; } table.source th { color:#666; } table.source td { font-family:monospace; white-space:pre; border-bottom:1px solid #eee; } ul.traceback { list-style-type:none; } ul.traceback li.frame { margin-bottom:1em; } div.context { margin: 10px 0; } div.context ol { padding-left:30px; margin:0 10px; list-style-position: inside; } div.context ol li { font-family:monospace; white-space:pre; color:#666; cursor:pointer; } div.context ol.context-line li { color:black; background-color:#ccc; } div.context ol.context-line li span { float: right; } div.commands { margin-left: 40px; } div.commands a { color:black; text-decoration:none; } #summary { background: #ffc; } #summary h2 { font-weight: normal; color: #666; font-family: monospace; white-space: pre-wrap;} #summary ul#quicklinks { list-style-type: none; margin-bottom: 2em; } #summary ul#quicklinks li { float: left; padding: 0 1em; } #summary ul#quicklinks>li+li { border-left: 1px #666 solid; } #explanation { background:#eee; } #traceback { background:#eee; } #requestinfo { background:#f6f6f6; padding-left:120px; } #summary table { border:none; background:transparent; } #requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; } #requestinfo h3 { margin-bottom:-1em; } .error { background: #ffc; } .specific { color:#cc3300; font-weight:bold; } END end # Javascript used by the HTML exception page for context toggling def self.js <{ "class"=>exception.class.to_s, "message"=>message, "backtrace"=>exception.backtrace.map(&:to_s) } } elsif env['HTTP_ACCEPT'] =~ /text\/html/ @_response['Content-Type'] = "text/html" context = opts[:context] || 7 css_file = opts[:css_file] js_file = opts[:js_file] case prefix = opts[:assets] when false css_file = false if css_file.nil? js_file = false if js_file.nil? when nil # nothing else prefix = '' if prefix == true css_file ||= "#{prefix}/exception_page.css" js_file ||= "#{prefix}/exception_page.js" end css = case css_file when nil "" when false # :nothing else "" end js = case js_file when nil "" when false # :nothing else "" end frames = exception.backtrace.map.with_index do |line, i| frame = {:id=>i} if line =~ /\A(.*?):(\d+)(?::in `(.*)')?\Z/ filename = frame[:filename] = $1 lineno = frame[:lineno] = $2.to_i frame[:function] = $3 begin lineno -= 1 lines = ::File.readlines(filename) if line = lines[lineno] pre_lineno = [lineno-context, 0].max if (pre_context = lines[pre_lineno...lineno]) && !pre_context.empty? frame[:pre_context_lineno] = pre_lineno frame[:pre_context] = pre_context end post_lineno = [lineno+context, lines.size].min if (post_context = lines[lineno+1..post_lineno]) && !post_context.empty? frame[:post_context_lineno] = post_lineno frame[:post_context] = post_context end frame[:context_line] = line.chomp end rescue end frame end end.compact r = @_request begin post_data = r.POST missing_post = "No POST data" rescue missing_post = "Invalid POST data" end info = lambda do |title, id, var, none| <#{title} #{(var && !var.empty?) ? (<#{none}

" #{var.sort_by{|k, _| k.to_s}.map{|key, val| (< END2 }
Variable Value
#{h key}
#{h val.inspect}
END1 } END end < #{h exception.class} at #{h r.path} #{css}

#{h exception.class} at #{h r.path}

#{h message}

Ruby #{(first = frames.first) ? "#{h first[:filename]}: in #{h first[:function]}, line #{first[:lineno]}" : "unknown location"}
Web #{r.request_method} #{h r.host}#{h r.path}

Jump to:

Traceback (innermost first)

    #{frames.map{|frame| id = frame[:id]; (< #{h frame[:filename]}:#{frame[:lineno]} in #{h frame[:function]} #{frame[:context_line] ? (<'
    #{frame[:pre_context] ? (< #{frame[:pre_context].map{|line| "
  • #{h line}
  • "}.join} END3 }
    1. #{h frame[:context_line]}...
    #{frame[:post_context] ? (< #{frame[:post_context].map{|line| "
  • #{h line}
  • "}.join} END4 }
    END2 } END1 }

Request information

#{info.call('GET', 'get-info', r.GET, 'No GET data')} #{info.call('POST', 'post-info', post_data, missing_post)} #{info.call('Cookies', 'cookie-info', r.cookies, 'No cookie data')} #{info.call('Rack ENV', 'env-info', r.env, 'No Rack env?')}

You're seeing this error because you use the Roda exception_page plugin.

#{js} END else @_response['Content-Type'] = "text/plain" "#{exception.class}: #{message}\n#{exception.backtrace.map{|l| "\t#{l}"}.join("\n")}" end end private # :nocov: if RUBY_VERSION >= '3.2' def exception_page_exception_message(exception) exception.detailed_message(highlight: false).to_s end # :nocov: else # Return message to use for exception. def exception_page_exception_message(exception) exception.message.to_s end end end module RequestMethods # Serve exception page assets def exception_page_assets get 'exception_page.css' do response['Content-Type'] = "text/css" ExceptionPage.css end get 'exception_page.js' do response['Content-Type'] = "application/javascript" ExceptionPage.js end end end end register_plugin(:exception_page, ExceptionPage) end end