PageRenderTime 42ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/better_errors/middleware.rb

https://gitlab.com/KiaraGrouwstra/better_errors
Ruby | 142 lines | 88 code | 19 blank | 35 comment | 7 complexity | 7f89cf63367c02190dd4f8384c0be370 MD5 | raw file
  1. require "json"
  2. require "ipaddr"
  3. require "set"
  4. module BetterErrors
  5. # Better Errors' error handling middleware. Including this in your middleware
  6. # stack will show a Better Errors error page for exceptions raised below this
  7. # middleware.
  8. #
  9. # If you are using Ruby on Rails, you do not need to manually insert this
  10. # middleware into your middleware stack.
  11. #
  12. # @example Sinatra
  13. # require "better_errors"
  14. #
  15. # if development?
  16. # use BetterErrors::Middleware
  17. # end
  18. #
  19. # @example Rack
  20. # require "better_errors"
  21. # if ENV["RACK_ENV"] == "development"
  22. # use BetterErrors::Middleware
  23. # end
  24. #
  25. class Middleware
  26. # The set of IP addresses that are allowed to access Better Errors.
  27. #
  28. # Set to `{ "127.0.0.1/8", "::1/128" }` by default.
  29. ALLOWED_IPS = Set.new
  30. # Adds an address to the set of IP addresses allowed to access Better
  31. # Errors.
  32. def self.allow_ip!(addr)
  33. ALLOWED_IPS << IPAddr.new(addr)
  34. end
  35. allow_ip! "127.0.0.0/8"
  36. allow_ip! "::1/128" rescue nil # windows ruby doesn't have ipv6 support
  37. # A new instance of BetterErrors::Middleware
  38. #
  39. # @param app The Rack app/middleware to wrap with Better Errors
  40. # @param handler The error handler to use.
  41. def initialize(app, handler = ErrorPage)
  42. @app = app
  43. @handler = handler
  44. end
  45. # Calls the Better Errors middleware
  46. #
  47. # @param [Hash] env
  48. # @return [Array]
  49. def call(env)
  50. if allow_ip? env
  51. better_errors_call env
  52. else
  53. @app.call env
  54. end
  55. end
  56. private
  57. def allow_ip?(env)
  58. # REMOTE_ADDR is not in the rack spec, so some application servers do
  59. # not provide it.
  60. return true # <--- fixed
  61. return true unless env["REMOTE_ADDR"] and !env["REMOTE_ADDR"].strip.empty?
  62. ip = IPAddr.new env["REMOTE_ADDR"].split("%").first
  63. ALLOWED_IPS.any? { |subnet| subnet.include? ip }
  64. end
  65. def better_errors_call(env)
  66. case env["PATH_INFO"]
  67. when %r{/__better_errors/(?<id>.+?)/(?<method>\w+)\z}
  68. internal_call env, $~
  69. when %r{/__better_errors/?\z}
  70. show_error_page env
  71. else
  72. protected_app_call env
  73. end
  74. end
  75. def protected_app_call(env)
  76. @app.call env
  77. rescue Exception => ex
  78. @error_page = @handler.new ex, env
  79. log_exception
  80. show_error_page(env, ex)
  81. end
  82. def show_error_page(env, exception=nil)
  83. type, content = if @error_page
  84. if text?(env)
  85. [ 'plain', @error_page.render('text') ]
  86. else
  87. [ 'html', @error_page.render ]
  88. end
  89. else
  90. [ 'html', no_errors_page ]
  91. end
  92. status_code = 500
  93. if defined? ActionDispatch::ExceptionWrapper
  94. status_code = ActionDispatch::ExceptionWrapper.new(env, exception).status_code
  95. end
  96. [status_code, { "Content-Type" => "text/#{type}; charset=utf-8" }, [content]]
  97. end
  98. def text?(env)
  99. env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" ||
  100. !env["HTTP_ACCEPT"].to_s.include?('html')
  101. end
  102. def log_exception
  103. return unless BetterErrors.logger
  104. message = "\n#{@error_page.exception.type} - #{@error_page.exception.message}:\n"
  105. @error_page.backtrace_frames.each do |frame|
  106. message << " #{frame}\n"
  107. end
  108. BetterErrors.logger.fatal message
  109. end
  110. def internal_call(env, opts)
  111. if opts[:id] != @error_page.id
  112. return [200, { "Content-Type" => "text/plain; charset=utf-8" }, [JSON.dump(error: "Session expired")]]
  113. end
  114. env["rack.input"].rewind
  115. response = @error_page.send("do_#{opts[:method]}", JSON.parse(env["rack.input"].read))
  116. [200, { "Content-Type" => "text/plain; charset=utf-8" }, [JSON.dump(response)]]
  117. end
  118. def no_errors_page
  119. "<h1>No errors</h1><p>No errors have been recorded yet.</p><hr>" +
  120. "<code>Better Errors v#{BetterErrors::VERSION}</code>"
  121. end
  122. end
  123. end