require "open3" module Xray OPEN_PATH = '/_xray/open' UPDATE_CONFIG_PATH = '/_xray/config' # This middleware is responsible for injecting xray.js, xray-backbone.js, and # the Xray bar into the app's pages. It also listens for requests to open files # with the user's editor. class Middleware def initialize(app) @app = app end def call(env) # Request for opening a file path. if env['PATH_INFO'] == OPEN_PATH req, res = Rack::Request.new(env), Rack::Response.new out, err, status = Xray.open_file(req.GET['path']) if status.success? res.status = 200 else res.write out res.status = 500 end res.finish elsif env['PATH_INFO'] == UPDATE_CONFIG_PATH req, res = Rack::Request.new(env), Rack::Response.new if req.post? && Xray.config.editor = req.POST['editor'] res.status = 200 else res.status = 400 end res.finish # Inject xray.js and friends if this is a successful HTML response else status, headers, response = @app.call(env) if html_headers?(status, headers) && body = response_body(response) if body =~ script_matcher('xray') # Inject the xray bar if xray.js is already on the page inject_xray_bar!(body) elsif Rails.application.config.assets.debug # Otherwise try to inject xray.js if assets are unbundled if append_js!(body, 'jquery', 'xray') append_js!(body, 'backbone', 'xray-backbone') inject_xray_bar!(body) end end content_length = body.bytesize.to_s # For rails v4.2.0+ compatibility if defined?(ActionDispatch::Response::RackBody) && ActionDispatch::Response::RackBody === response response = response.instance_variable_get(:@response) end # Modifying the original response obj maintains compatibility with other middlewares if ActionDispatch::Response === response response.body = [body] response.header['Content-Length'] = content_length unless response.try(:committed?) response.to_a else headers['Content-Length'] = content_length [status, headers, [body]] end else [status, headers, response] end end end private def inject_xray_bar!(html) html.sub!(/]*>/) { "#{$~}\n#{render_xray_bar}" } end def render_xray_bar if ApplicationController.respond_to?(:render) # Rails 5 ApplicationController.render(:partial => "/xray_bar").html_safe else # Rails <= 4.2 ac = ActionController::Base.new ac.render_to_string(:partial => '/xray_bar').html_safe end end # Matches: # # # # def script_matcher(script_name) / ]+ \/#{script_name} (2)? # Optional 2 for jQuery2 format ([-.]{1}[\d\.]+)? # Optional version identifier (e.g. -1.9.1) ([-.]{1}min)? # Optional -min suffix (\.self)? # Sprockets 3 appends .self to the filename (-\h{32,64})? # Fingerprint varies based on Sprockets version \.js # Must have .js extension [^>]+><\/script> /x end # Appends the given `script_name` after the `after_script_name`. def append_js!(html, after_script_name, script_name) html.sub!(script_matcher(after_script_name)) do "#{$~}\n" + helper.javascript_include_tag(script_name) end end def helper ActionController::Base.helpers end def html_headers?(status, headers) status == 200 && headers['Content-Type'] && headers['Content-Type'].include?('text/html') && headers["Content-Transfer-Encoding"] != "binary" end def response_body(response) body = '' response.each { |s| body << s.to_s } body end end end