diff --git a/README.md b/README.md index bed73f9b..34ca11f3 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,7 @@ class ThingsController < ApplicationController viewport_size: 'TEXT', # available only with use_xserver or patched QT extra: '', # directly inserted into the command to wkhtmltopdf raise_on_all_errors: nil, # raise error for any stderr output. Such as missing media, image assets + raise_on_missing_assets: nil, # raise when trying to access a missing asset log_level: 'info', # Available values: none, error, warn, or info - only available with wkhtmltopdf 0.12.5+ quiet: false, # `false` is same as `log_level: 'info'`, `true` is same as `log_level: 'none'` outline: { outline: true, diff --git a/lib/wicked_pdf/wicked_pdf_helper/assets.rb b/lib/wicked_pdf/wicked_pdf_helper/assets.rb index 5f8820c2..77163265 100644 --- a/lib/wicked_pdf/wicked_pdf_helper/assets.rb +++ b/lib/wicked_pdf/wicked_pdf_helper/assets.rb @@ -7,6 +7,27 @@ module WickedPdfHelper module Assets ASSET_URL_REGEX = /url\(['"]?([^'"]+?)['"]?\)/ + class MissingAsset < StandardError; end + + class MissingLocalAsset < MissingAsset + attr_reader :path + + def initialize(path) + @path = path + super("Could not find asset '#{path}'") + end + end + + class MissingRemoteAsset < MissingAsset + attr_reader :url, :response + + def initialize(url, response) + @url = url + @response = response + super("Could not fetch asset '#{url}': server responded with #{response.code} #{response.message}") + end + end + class PropshaftAsset < SimpleDelegator def content_type super.to_s @@ -21,9 +42,39 @@ def filename end end + class SprocketsEnvironment + def self.instance + @instance ||= Sprockets::Railtie.build_environment(Rails.application) + end + + def self.find_asset(*args) + instance.find_asset(*args) + end + end + + class LocalAsset + attr_reader :path + + def initialize(path) + @path = path + end + + def content_type + Mime::Type.lookup_by_extension(File.extname(path).delete('.')) + end + + def to_s + IO.read(path) + end + + def filename + path.to_s + end + end + def wicked_pdf_asset_base64(path) asset = find_asset(path) - raise "Could not find asset '#{path}'" if asset.nil? + raise MissingLocalAsset, path if asset.nil? base64 = Base64.encode64(asset.to_s).gsub(/\s+/, '') "data:#{asset.content_type};base64,#{Rack::Utils.escape(base64)}" @@ -150,8 +201,11 @@ def find_asset(path) Rails.application.assets.find_asset(path, :base_path => Rails.application.root.to_s) elsif defined?(Propshaft::Assembly) && Rails.application.assets.is_a?(Propshaft::Assembly) PropshaftAsset.new(Rails.application.assets.load_path.find(path)) + elsif Rails.application.respond_to?(:assets_manifest) + asset_path = File.join(Rails.application.assets_manifest.dir, Rails.application.assets_manifest.assets[path]) + LocalAsset.new(asset_path) if File.file?(asset_path) else - Sprockets::Railtie.build_environment(Rails.application).find_asset(path, :base_path => Rails.application.root.to_s) + SprocketsEnvironment.find_asset(path, :base_path => Rails.application.root.to_s) end end @@ -175,20 +229,35 @@ def precompiled_or_absolute_asset?(source) end def read_asset(source) - if precompiled_or_absolute_asset?(source) - pathname = asset_pathname(source) - if pathname =~ URI_REGEXP - read_from_uri(pathname) - elsif File.file?(pathname) - IO.read(pathname) - end - else - find_asset(source).to_s.force_encoding('UTF-8') + asset = find_asset(source) + return asset.to_s.force_encoding('UTF-8') if asset + + unless precompiled_or_absolute_asset?(source) + raise MissingLocalAsset, source if WickedPdf.config[:raise_on_missing_assets] + + return + end + + pathname = asset_pathname(source) + if pathname =~ URI_REGEXP + read_from_uri(pathname) + elsif File.file?(pathname) + IO.read(pathname) + elsif WickedPdf.config[:raise_on_missing_assets] + raise MissingLocalAsset, pathname if WickedPdf.config[:raise_on_missing_assets] end end def read_from_uri(uri) - asset = Net::HTTP.get(URI(uri)) + response = Net::HTTP.get_response(URI(uri)) + + unless response.is_a?(Net::HTTPSuccess) + raise MissingRemoteAsset.new(uri, response) if WickedPdf.config[:raise_on_missing_assets] + + return + end + + asset = response.body asset.force_encoding('UTF-8') if asset asset = gzip(asset) if WickedPdf.config[:expect_gzipped_remote_assets] asset diff --git a/test/functional/wicked_pdf_helper_assets_test.rb b/test/functional/wicked_pdf_helper_assets_test.rb index d23d18c5..2b8368b1 100644 --- a/test/functional/wicked_pdf_helper_assets_test.rb +++ b/test/functional/wicked_pdf_helper_assets_test.rb @@ -4,6 +4,15 @@ class WickedPdfHelperAssetsTest < ActionView::TestCase include WickedPdf::WickedPdfHelper::Assets + setup do + @saved_config = WickedPdf.config + WickedPdf.config = {} + end + + teardown do + WickedPdf.config = @saved_config + end + if Rails::VERSION::MAJOR > 3 || (Rails::VERSION::MAJOR == 3 && Rails::VERSION::MINOR > 0) test 'wicked_pdf_asset_base64 returns a base64 encoded asset' do assert_match %r{data:text\/css;base64,.+}, wicked_pdf_asset_base64('wicked.css') @@ -15,6 +24,58 @@ class WickedPdfHelperAssetsTest < ActionView::TestCase wicked_pdf_stylesheet_link_tag('wicked') end + test 'wicked_pdf_stylesheet_link_tag should raise if the stylesheet is not available and config is set' do + Rails.configuration.assets.expects(:compile => true) + WickedPdf.config[:raise_on_missing_assets] = true + assert_raise WickedPdf::WickedPdfHelper::Assets::MissingLocalAsset do + wicked_pdf_stylesheet_link_tag('non_existent') + end + end + + test 'wicked_pdf_stylesheet_link_tag should return empty if the stylesheet is not available' do + Rails.configuration.assets.expects(:compile => true) + assert_equal "", + wicked_pdf_stylesheet_link_tag('non_existent') + end + + test 'wicked_pdf_stylesheet_link_tag should raise if the absolute path stylesheet is not available and config is set' do + Rails.configuration.assets.expects(:compile => true) + WickedPdf.config[:raise_on_missing_assets] = true + expects(:precompiled_or_absolute_asset? => true).twice + assert_raise WickedPdf::WickedPdfHelper::Assets::MissingLocalAsset do + wicked_pdf_stylesheet_link_tag('/non_existent') + end + end + + test 'wicked_pdf_stylesheet_link_tag should return empty if the absolute path stylesheet is not available' do + Rails.configuration.assets.expects(:compile => true).twice + assert_equal "", + wicked_pdf_stylesheet_link_tag('/non_existent') + end + + test 'wicked_pdf_stylesheet_link_tag should inline the stylesheets passed in when assets are remote' do + stub_request(:get, 'https://www.example.com/wicked.css').to_return(:status => 200, :body => '/* Wicked styles */') + expects(:precompiled_or_absolute_asset? => true).twice + assert_equal "", + wicked_pdf_stylesheet_link_tag('https://www.example.com/wicked.css') + end + + test 'wicked_pdf_stylesheet_link_tag should raise if remote assets are not available and config is set' do + WickedPdf.config[:raise_on_missing_assets] = true + stub_request(:get, 'https://www.example.com/wicked.css').to_return(:status => 404, :body => 'File not found') + expects(:precompiled_or_absolute_asset? => true).twice + assert_raise WickedPdf::WickedPdfHelper::Assets::MissingRemoteAsset do + wicked_pdf_stylesheet_link_tag('https://www.example.com/wicked.css') + end + end + + test 'wicked_pdf_stylesheet_link_tag should return empty if remote assets are not available' do + stub_request(:get, 'https://www.example.com/wicked.css').to_return(:status => 404, :body => 'File not found') + expects(:precompiled_or_absolute_asset? => true).twice + assert_equal "", + wicked_pdf_stylesheet_link_tag('https://www.example.com/wicked.css') + end + test 'wicked_pdf_image_tag should return the same as image_tag when passed a full path' do Rails.configuration.assets.expects(:compile => true) assert_equal image_tag("file:///#{Rails.root.join('public', 'pdf')}"), diff --git a/test/test_helper.rb b/test/test_helper.rb index 15990f93..4720ff73 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -7,6 +7,7 @@ require 'mocha' require 'rails/test_help' require 'mocha/test_unit' +require 'webmock/minitest' require 'wicked_pdf' diff --git a/wicked_pdf.gemspec b/wicked_pdf.gemspec index 3a4819c4..9c7432d3 100644 --- a/wicked_pdf.gemspec +++ b/wicked_pdf.gemspec @@ -37,4 +37,5 @@ DESC spec.add_development_dependency 'rubocop', '~> 1.46' spec.add_development_dependency 'sqlite3', '~> 1.3' spec.add_development_dependency 'test-unit' + spec.add_development_dependency 'webmock', '~> 3.19' end