diff --git a/middleman-core/features/asset_hash.feature b/middleman-core/features/asset_hash.feature index ec4d7b95..18abf3f9 100644 --- a/middleman-core/features/asset_hash.feature +++ b/middleman-core/features/asset_hash.feature @@ -94,7 +94,7 @@ Feature: Assets get a file hash appended to their and references to them are upd activate :relative_assets activate :directory_indexes require 'lib/middleware.rb' - use Middleware + use ::Middleware """ Given the Server is running at "asset-hash-app" When I go to "/" diff --git a/middleman-core/lib/middleman-core/extensions/asset_hash.rb b/middleman-core/lib/middleman-core/extensions/asset_hash.rb index f8aee261..c0f531d9 100644 --- a/middleman-core/lib/middleman-core/extensions/asset_hash.rb +++ b/middleman-core/lib/middleman-core/extensions/asset_hash.rb @@ -10,13 +10,37 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension require 'digest/sha1' require 'rack/mock' require 'uri' + require 'middleman-core/middleware/inline_url_rewriter' end def after_configuration # Allow specifying regexes to ignore, plus always ignore apple touch icons @ignore = Array(options.ignore) + [/^apple-touch-icon/] - app.use Middleware, exts: options.exts, middleman_app: app, ignore: @ignore + app.use ::Middleman::Middleware::InlineURLRewriter, + :id => :asset_hash, + :url_extensions => options.exts, + :source_extensions => %w(.htm .html .php .css .js), + :ignore => @ignore, + :middleman_app => app, + :proc => method(:rewrite_url) + end + + def rewrite_url(asset_path, dirpath) + relative_path = Pathname.new(asset_path).relative? + + full_asset_path = if relative_path + dirpath.join(asset_path).to_s + else + asset_path + end + + if asset_page = app.sitemap.find_resource_by_path(full_asset_path) + replacement_path = "/#{asset_page.destination_path}" + replacement_path = Pathname.new(replacement_path).relative_path_from(dirpath).to_s if relative_path + + replacement_path + end end # Update the main sitemap resource list @@ -44,7 +68,10 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension return if resource.ignored? # Render through the Rack interface so middleware and mounted apps get a shot - response = @rack_client.get(URI.escape(resource.destination_path), 'bypass_asset_hash' => 'true') + response = @rack_client.get(URI.escape(resource.destination_path), { + 'bypass_inline_url_rewriter_asset_hash' => 'true' + }) + raise "#{resource.path} should be in the sitemap!" unless response.status == 200 digest = Digest::SHA1.hexdigest(response.body)[0..7] @@ -55,74 +82,4 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension def ignored_resource?(resource) @ignore.any? { |ignore| Middleman::Util.path_match(ignore, resource.destination_path) } end - - # The asset hash middleware is responsible for rewriting references to - # assets to include their new, hashed name. - class Middleware - def initialize(app, options={}) - @rack_app = app - @exts = options[:exts] - @ignore = options[:ignore] - @exts_regex_text = @exts.map { |e| Regexp.escape(e) }.join('|') - @middleman_app = options[:middleman_app] - end - - def call(env) - status, headers, response = @rack_app.call(env) - - # We don't want to use this middleware when rendering files to figure out their hash! - return [status, headers, response] if env['bypass_asset_hash'] == 'true' - - path = ::Middleman::Util.full_path(env['PATH_INFO'], @middleman_app) - - if path =~ /(^\/$)|(\.(htm|html|php|css|js)$)/ - body = ::Middleman::Util.extract_response_text(response) - if body - status, headers, response = Rack::Response.new(rewrite_paths(body, path), status, headers).finish - end - end - - [status, headers, response] - end - - private - - def rewrite_paths(body, path) - dirpath = Pathname.new(File.dirname(path)) - - # TODO: This regex will change some paths in plan HTML (not in a tag) - is that OK? - body.gsub(/([=\'\"\(]\s*)([^\s\'\"\)]+(#{@exts_regex_text}))/) do |match| - opening_character = $1 - asset_path = $2 - - relative_path = Pathname.new(asset_path).relative? - - asset_path = dirpath.join(asset_path).to_s if relative_path - - if @ignore.any? { |r| asset_path.match(r) } - match - elsif asset_page = @middleman_app.sitemap.find_resource_by_path(asset_path) - replacement_path = "/#{asset_page.destination_path}" - replacement_path = Pathname.new(replacement_path).relative_path_from(dirpath).to_s if relative_path - - "#{opening_character}#{replacement_path}" - else - match - end - end - end - end end - -# =================Temp Generate Test data============================== -# ["jpg", "png", "gif"].each do |ext| -# [["

", "

"], ["

"], ["

background-image:url(", ");

"]].each do |outer| -# [["",""], ["'", "'"], ['"','"']].each do |inner| -# [["", ""], ["/", ""], ["../", ""], ["../../", ""], ["../../../", ""], ["http://example.com/", ""], ["a","a"], ["1","1"], [".", "."], ["-","-"], ["_","_"]].each do |path_parts| -# name = 'images/100px.' -# puts outer[0] + inner[0] + path_parts[0] + name + ext + path_parts[1] + inner[1] + outer[1] -# end -# end -# end -# puts "


" -# end diff --git a/middleman-core/lib/middleman-core/middleware/inline_url_rewriter.rb b/middleman-core/lib/middleman-core/middleware/inline_url_rewriter.rb new file mode 100644 index 00000000..6b12f23e --- /dev/null +++ b/middleman-core/lib/middleman-core/middleware/inline_url_rewriter.rb @@ -0,0 +1,67 @@ +require 'middleman-core/util' +require 'rack' +require 'rack/response' + +module Middleman + module Middleware + class InlineURLRewriter + def initialize(app, options={}) + @rack_app = app + @middleman_app = options[:middleman_app] + + @uid = options[:id] + @proc = options[:proc] + + raise "InlineURLRewriter requires a :proc to call with inline URL results" unless @proc + + @exts = options[:url_extensions] + + @source_exts = options[:source_extensions] + @source_exts_regex_text = Regexp.union(@source_exts).to_s + + @ignore = options[:ignore] + end + + def call(env) + status, headers, response = @rack_app.call(env) + + # Allow upstream request to skip all rewriting + return [status, headers, response] if env['bypass_inline_url_rewriter'] == 'true' + + # Allow upstream request to skip this specific rewriting + if @uid + uid_key = "bypass_inline_url_rewriter_#{@uid}" + return [status, headers, response] if env[uid_key] == 'true' + end + + path = ::Middleman::Util.full_path(env['PATH_INFO'], @middleman_app) + + if path =~ /(^\/$)|(#{@source_exts_regex_text}$)/ + if body = ::Middleman::Util.extract_response_text(response) + dirpath = Pathname.new(File.dirname(path)) + + rewritten = ::Middleman::Util.rewrite_paths(body, path, @exts) do |asset_path| + relative_path = Pathname.new(asset_path).relative? + + full_asset_path = if relative_path + dirpath.join(asset_path).to_s + else + asset_path + end + + @ignore.none? { |r| full_asset_path.match(r) } && @proc.call(asset_path, dirpath) + end + + status, headers, response = ::Rack::Response.new( + rewritten, + status, + headers + ).finish + end + end + + [status, headers, response] + end + end + end +end diff --git a/middleman-core/lib/middleman-core/util.rb b/middleman-core/lib/middleman-core/util.rb index 2621cee9..03c06e05 100644 --- a/middleman-core/lib/middleman-core/util.rb +++ b/middleman-core/lib/middleman-core/util.rb @@ -222,6 +222,19 @@ module Middleman end end + def rewrite_paths(body, path, exts, &block) + body.dup.gsub(/([=\'\"\(]\s*)([^\s\'\"\)]+(#{Regexp.union(exts)}))/) do |match| + opening_character = $1 + asset_path = $2 + + if result = yield(asset_path) + "#{opening_character}#{result}" + else + match + end + end + end + private # Is mime type known to be non-binary?