require 'rack' require 'rack/response' module Rollbar module Middleware class Js attr_reader :app attr_reader :config JS_IS_INJECTED_KEY = 'rollbar.js_is_injected' SNIPPET = File.read(File.expand_path('../../../../data/rollbar.snippet.js', __FILE__)) def initialize(app, config) @app = app @config = config end def call(env) result = app.call(env) _call(env, result) end private def _call(env, result) return result unless should_add_js?(env, result[0], result[1]) if response_string = add_js(env, result[2]) env[JS_IS_INJECTED_KEY] = true response = ::Rack::Response.new(response_string, result[0], result[1]) response.finish else result end rescue => e Rollbar.log_error("[Rollbar] Rollbar.js could not be added because #{e} exception") result end def enabled? !!config[:enabled] end def should_add_js?(env, status, headers) enabled? && status == 200 && !env[JS_IS_INJECTED_KEY] && html?(headers) && !attachment?(headers) && !streaming?(env) end def html?(headers) headers['Content-Type'] && headers['Content-Type'].include?('text/html') end def attachment?(headers) headers['Content-Disposition'].to_s.include?('attachment') end def streaming?(env) return false unless defined?(ActionController::Live) env['action_controller.instance'].class.included_modules.include?(ActionController::Live) end def add_js(env, response) body = join_body(response) close_old_response(response) return nil unless body head_open_end = find_end_of_head_open(body) return nil unless head_open_end if head_open_end body = body[0..head_open_end] << config_js_tag(env) << snippet_js_tag(env) << body[head_open_end + 1..-1] end body rescue => e Rollbar.log_error("[Rollbar] Rollbar.js could not be added because #{e} exception") nil end def find_end_of_head_open(body) head_open = body.index(/<head\W/) body.index('>', head_open) if head_open end def join_body(response) source = nil response.each { |fragment| source ? (source << fragment.to_s) : (source = fragment.to_s)} source end def close_old_response(response) response.close if response.respond_to?(:close) end def config_js_tag(env) script_tag("var _rollbarConfig = #{config[:options].to_json};", env) end def snippet_js_tag(env) script_tag(js_snippet, env) end def js_snippet SNIPPET end def script_tag(content, env) if defined?(::SecureHeaders) && ::SecureHeaders.respond_to?(:content_security_policy_script_nonce) nonce = ::SecureHeaders.content_security_policy_script_nonce(::Rack::Request.new(env)) script_tag_content = "\n<script type=\"text/javascript\" nonce=\"#{nonce}\">#{content}</script>" else script_tag_content = "\n<script type=\"text/javascript\">#{content}</script>" end html_safe_if_needed(script_tag_content) end def html_safe_if_needed(string) string = string.html_safe if string.respond_to?(:html_safe) string end end end end