Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/assets/stylesheets/blazer/bootstrap-propshaft.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
*/
@font-face {
font-family: "Glyphicons Halflings";
src: url('/blazer/glyphicons-halflings-regular.eot');
src: url('/blazer/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('/blazer/glyphicons-halflings-regular.woff2') format('woff2'), url('/blazer/glyphicons-halflings-regular.woff') format('woff'), url('/blazer/glyphicons-halflings-regular.ttf') format('truetype'), url('/blazer/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
src: url('glyphicons-halflings-regular.eot');
src: url('glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('glyphicons-halflings-regular.woff2') format('woff2'), url('glyphicons-halflings-regular.woff') format('woff'), url('glyphicons-halflings-regular.ttf') format('truetype'), url('glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
}
48 changes: 48 additions & 0 deletions app/controllers/blazer/assets_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
module Blazer
# Serves Blazer's bundled JS/CSS/fonts directly from the engine so Blazer
# works without a host asset pipeline (no Sprockets, Propshaft, or importmap).
class AssetsController < ActionController::Base
# Public, GET-only static files. Skip CSRF and the cross-origin JavaScript
# check so the vendored JS can be embedded via plain <script> tags.
self.allow_forgery_protection = false
skip_forgery_protection
skip_after_action :verify_same_origin_request, raise: false

ASSET_ROOTS = [
Blazer::Engine.root.join("app", "assets", "javascripts", "blazer"),
Blazer::Engine.root.join("app", "assets", "stylesheets", "blazer"),
Blazer::Engine.root.join("app", "assets", "fonts", "blazer"),
Blazer::Engine.root.join("app", "assets", "images", "blazer")
].map { |path| File.expand_path(path.to_s) }.freeze

def show
file = resolve(params[:path].to_s)

if file
expires_in 1.year, public: true
send_file file, type: content_type_for(file), disposition: "inline"
else
head :not_found
end
end

private

# Resolve a request path to a real file inside one of the asset roots.
# Rejects path traversal and only serves files that actually exist.
def resolve(path)
return nil if path.empty? || path.include?("..") || path.include?("\0")

ASSET_ROOTS.each do |root|
candidate = File.expand_path(File.join(root, path))
return candidate if candidate.start_with?(root + File::SEPARATOR) && File.file?(candidate)
end

nil
end

def content_type_for(file)
Rack::Mime.mime_type(File.extname(file), "application/octet-stream")
end
end
end
22 changes: 15 additions & 7 deletions app/views/layouts/blazer/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,22 @@
<title><%= blazer_title ? blazer_title : "Blazer" %></title>

<meta charset="utf-8" />
<%= favicon_link_tag "blazer/favicon.png" %>
<% if defined?(Propshaft::Railtie) && Rails.application.assets.is_a?(Propshaft::Assembly) %>
<%= stylesheet_link_tag "blazer/bootstrap-propshaft", "blazer/bootstrap", "blazer/tom-select", "blazer/github", "blazer/daterangepicker", "blazer/application", nonce: true %>
<%= javascript_include_tag "blazer/tablesort", "blazer/tom-select.base", "blazer/highlight.min", "blazer/moment", "blazer/moment-timezone-with-data", "blazer/daterangepicker", "blazer/chart.umd", "blazer/chartjs-adapter-date-fns.bundle", "blazer/chartkick", "blazer/mapkick.bundle", "blazer/ace/ace", "blazer/ace/ext-language_tools", "blazer/ace/theme-twilight", "blazer/ace/mode-sql", "blazer/ace/snippets/text", "blazer/ace/snippets/sql", "blazer/Sortable", "blazer/routes", "blazer/queries", "blazer/fuzzysearch", "blazer/application", nonce: true %>
<% else %>
<%= stylesheet_link_tag "blazer/application", nonce: true %>
<%= javascript_include_tag "blazer/application", nonce: true %>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="<%= blazer.asset_file_path("favicon.png") %>" />

<% %w[bootstrap-propshaft bootstrap tom-select github daterangepicker application].each do |css| %>
<link rel="stylesheet" href="<%= blazer.asset_file_path("#{css}.css") %>" nonce="<%= content_security_policy_nonce %>" />
<% end %>

<% %w[
tablesort tom-select.base highlight.min moment moment-timezone-with-data
daterangepicker chart.umd chartjs-adapter-date-fns.bundle chartkick mapkick.bundle
ace/ace ace/ext-language_tools ace/theme-twilight ace/mode-sql
ace/snippets/text ace/snippets/sql Sortable routes queries fuzzysearch application
].each do |js| %>
<script src="<%= blazer.asset_file_path("#{js}.js") %>" nonce="<%= content_security_policy_nonce %>"></script>
<% end %>

<%= javascript_tag nonce: true do %>
<%= blazer_js_var "rootPath", root_path %>
<% end %>
Expand Down
3 changes: 3 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
Blazer::Engine.routes.draw do
# Serve Blazer's bundled assets from the engine (no host asset pipeline required)
get "blazer-assets/*path", to: "assets#show", as: :asset_file, format: false

resources :queries do
post :run, on: :collection # err on the side of caution
post :cancel, on: :collection
Expand Down
21 changes: 2 additions & 19 deletions lib/blazer/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,8 @@ class Engine < ::Rails::Engine
isolate_namespace Blazer

initializer "blazer" do |app|
if app.config.respond_to?(:assets) && defined?(Sprockets)
if Sprockets::VERSION.to_i >= 4
app.config.assets.precompile += [
"blazer/application.js",
"blazer/application.css",
"blazer/glyphicons-halflings-regular.eot",
"blazer/glyphicons-halflings-regular.svg",
"blazer/glyphicons-halflings-regular.ttf",
"blazer/glyphicons-halflings-regular.woff",
"blazer/glyphicons-halflings-regular.woff2",
"blazer/favicon.png"
]
else
# use a proc instead of a string
app.config.assets.precompile << proc { |path| path =~ /\Ablazer\/application\.(js|css)\z/ }
app.config.assets.precompile << proc { |path| path =~ /\Ablazer\/.+\.(eot|svg|ttf|woff|woff2)\z/ }
app.config.assets.precompile << proc { |path| path == "blazer/favicon.png" }
end
end
# Blazer serves its own bundled assets via Blazer::AssetsController, so it
# no longer depends on the host app's asset pipeline (Sprockets/Propshaft).

Blazer.time_zone ||= Blazer.settings["time_zone"] || Time.zone
Blazer.audit = Blazer.settings.key?("audit") ? Blazer.settings["audit"] : true
Expand Down
59 changes: 59 additions & 0 deletions test/assets_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
require_relative "test_helper"

class AssetsTest < ActionDispatch::IntegrationTest
def test_javascript
get blazer.asset_file_path("moment.js")
assert_response :success
assert_match(/javascript/, content_type)
assert response.body.size > 0
end

def test_stylesheet
get blazer.asset_file_path("application.css")
assert_response :success
assert_equal "text/css", content_type
assert response.body.size > 0
end

def test_nested_asset
get blazer.asset_file_path("ace/ace.js")
assert_response :success
assert_match(/javascript/, content_type)
end

def test_font
get blazer.asset_file_path("glyphicons-halflings-regular.woff2")
assert_response :success
assert response.body.size > 0
end

def test_favicon
get blazer.asset_file_path("favicon.png")
assert_response :success
assert_equal "image/png", content_type
end

def test_missing_asset
get blazer.asset_file_path("does-not-exist.js")
assert_response :not_found
end

def test_path_traversal_rejected
get "/blazer-assets/../../../Gemfile"
assert_response :not_found
end

def test_layout_references_engine_assets
get blazer.root_path
assert_response :success
assert_match %r{blazer-assets/moment\.js}, response.body
assert_match %r{blazer-assets/application\.css}, response.body
assert_no_match %r{/assets/blazer}, response.body
end

private

def content_type
response.media_type
end
end