# frozen_string_literal: true
require 'ostruct'
require 'erb'
require_relative 'constants'
require_relative 'utils'
require_relative 'request'
module Rack
# Rack::ShowExceptions catches all exceptions raised from the app it
# wraps. It shows a useful backtrace with the sourcefile and
# clickable context, the whole Rack environment and the request
# data.
#
# Be careful when you use this on public-facing sites as it could
# reveal information helpful to attackers.
class ShowExceptions
CONTEXT = 7
def initialize(app)
@app = app
end
def call(env)
@app.call(env)
rescue StandardError, LoadError, SyntaxError => e
exception_string = dump_exception(e)
env[RACK_ERRORS].puts(exception_string)
env[RACK_ERRORS].flush
if accepts_html?(env)
content_type = "text/html"
body = pretty(env, e)
else
content_type = "text/plain"
body = exception_string
end
[
500,
{
CONTENT_TYPE => content_type,
CONTENT_LENGTH => body.bytesize.to_s,
},
[body],
]
end
def prefers_plaintext?(env)
!accepts_html?(env)
end
def accepts_html?(env)
Rack::Utils.best_q_match(env["HTTP_ACCEPT"], %w[text/html])
end
private :accepts_html?
def dump_exception(exception)
if exception.respond_to?(:detailed_message)
message = exception.detailed_message(highlight: false)
else
message = exception.message
end
string = "#{exception.class}: #{message}\n".dup
string << exception.backtrace.map { |l| "\t#{l}" }.join("\n")
string
end
def pretty(env, exception)
req = Rack::Request.new(env)
# This double assignment is to prevent an "unused variable" warning.
# Yes, it is dumb, but I don't like Ruby yelling at me.
path = path = (req.script_name + req.path_info).squeeze("/")
# This double assignment is to prevent an "unused variable" warning.
# Yes, it is dumb, but I don't like Ruby yelling at me.
frames = frames = exception.backtrace.map { |line|
frame = OpenStruct.new
if line =~ /(.*?):(\d+)(:in `(.*)')?/
frame.filename = $1
frame.lineno = $2.to_i
frame.function = $4
begin
lineno = frame.lineno - 1
lines = ::File.readlines(frame.filename)
frame.pre_context_lineno = [lineno - CONTEXT, 0].max
frame.pre_context = lines[frame.pre_context_lineno...lineno]
frame.context_line = lines[lineno].chomp
frame.post_context_lineno = [lineno + CONTEXT, lines.size].min
frame.post_context = lines[lineno + 1..frame.post_context_lineno]
rescue
end
frame
else
nil
end
}.compact
template.result(binding)
end
def template
TEMPLATE
end
def h(obj) # :nodoc:
case obj
when String
Utils.escape_html(obj)
else
Utils.escape_html(obj.inspect)
end
end
# :stopdoc:
# adapted from Django No GET data. <%= no_post_data || "No POST data" %>. No cookie data.
You're seeing this error because you use <%=h exception.class %> at <%=h path %>
<% if exception.respond_to?(:detailed_message) %>
<%=h exception.detailed_message(highlight: false) %>
<% else %>
<%=h exception.message %>
<% end %>
Ruby
<% if first = frames.first %>
<%=h first.filename %>: in <%=h first.function %>, line <%=h frames.first.lineno %>
<% else %>
unknown location
<% end %>
Web
<%=h req.request_method %> <%=h(req.host + path)%>Jump to:
Traceback (innermost first)
<% frames.each { |frame| %>
<%=h frame.filename %>: in <%=h frame.function %>
<% if frame.context_line %>
<% frame.pre_context.each { |line| %>
<% end %>
<% if frame.post_context %>
<% frame.post_context.each { |line| %>
<% end %>
Request information
GET
<% if req.GET and not req.GET.empty? %>
<% else %>
<% req.GET.sort_by { |k, v| k.to_s }.each { |key, val| %>
Variable
Value
<% } %>
<%=h key %>
POST
<% if ((req.POST and not req.POST.empty?) rescue (no_post_data = "Invalid POST data"; nil)) %>
<% else %>
<% req.POST.sort_by { |k, v| k.to_s }.each { |key, val| %>
Variable
Value
<% } %>
<%=h key %>
COOKIES
<% unless req.cookies.empty? %>
<% else %>
<% req.cookies.each { |key, val| %>
Variable
Value
<% } %>
<%=h key %>
Rack ENV
<% env.sort_by { |k, v| k.to_s }.each { |key, val| %>
Variable
Value
<% } %>
<%=h key %>
Rack::ShowExceptions.