diff --git a/lib/fileboost/helpers.rb b/lib/fileboost/helpers.rb index 50909cc..ab2c969 100644 --- a/lib/fileboost/helpers.rb +++ b/lib/fileboost/helpers.rb @@ -5,7 +5,7 @@ module Helpers # Generate an optimized image tag using Fileboost # # @param asset [ActiveStorage::Blob, ActiveStorage::Attached, ActiveStorage::VariantWithRecord] The ActiveStorage image asset - # @param options [Hash] Image transformation and HTML options + # @param options [Hash] Accepts a `:resize` hash for transformations plus standard HTML options # @return [String] HTML image tag # # Examples: @@ -25,7 +25,7 @@ def fileboost_image_tag(asset, **options) # Generate an optimized URL using Fileboost # # @param asset [ActiveStorage::Blob, ActiveStorage::Attached, ActiveStorage::VariantWithRecord] The ActiveStorage image asset - # @param options [Hash] Image transformation options + # @param options [Hash] Supports a `:resize` hash for image transformations and an optional `:disposition` # @return [String, nil] The optimized URL or nil if generation failed # # Examples: diff --git a/lib/fileboost/url_builder.rb b/lib/fileboost/url_builder.rb index f2b6e59..ada2530 100644 --- a/lib/fileboost/url_builder.rb +++ b/lib/fileboost/url_builder.rb @@ -45,7 +45,10 @@ def self.build_url(asset, **options) # Extract and normalize transformation parameters transformation_params = extract_transformation_params(asset, options) - # Generate HMAC signature for secure authentication + # Separate disposition from transformation params for signature generation + disposition = transformation_params.delete("disposition") + + # Generate HMAC signature for secure authentication (excluding disposition) signature = Fileboost::SignatureGenerator.generate( asset_path: asset_path, params: transformation_params @@ -53,8 +56,9 @@ def self.build_url(asset, **options) raise SignatureGenerationError, "Failed to generate signature" unless signature - # Add signature to parameters + # Add signature and disposition back to final URL parameters all_params = transformation_params.merge("sig" => signature) + all_params["disposition"] = disposition if disposition # Build final URL uri = URI.join(base_url, full_path) @@ -118,6 +122,11 @@ def self.extract_transformation_params(asset, options) end end + disposition = options[:disposition] || options["disposition"] + if disposition.present? + params["disposition"] = disposition.to_s.strip.downcase + end + params end diff --git a/spec/fileboost/helpers_spec.rb b/spec/fileboost/helpers_spec.rb index 15c729b..6b8e5df 100644 --- a/spec/fileboost/helpers_spec.rb +++ b/spec/fileboost/helpers_spec.rb @@ -60,6 +60,12 @@ expect(url).to include("fit=scale-down") end + it "includes disposition parameter when provided" do + url = fileboost_url_for(blob, disposition: :attachment) + + expect(url).to include("disposition=attachment") + end + it "raises ArgumentError for invalid asset type" do expect { fileboost_url_for("invalid", resize: { w: 300 }) diff --git a/spec/fileboost/url_builder_spec.rb b/spec/fileboost/url_builder_spec.rb index fe10f59..ea6cf2b 100644 --- a/spec/fileboost/url_builder_spec.rb +++ b/spec/fileboost/url_builder_spec.rb @@ -150,5 +150,24 @@ expect(url).to include("fit=scale-down") end end + + context "with disposition parameter" do + it "includes disposition in URL but excludes from signature generation" do + # Generate URLs with and without disposition + url_without_disposition = Fileboost::UrlBuilder.build_url(blob, resize: { w: 300 }) + url_with_disposition = Fileboost::UrlBuilder.build_url(blob, resize: { w: 300 }, disposition: "attachment") + + # Extract signatures from both URLs + sig_without = URI.decode_www_form(URI.parse(url_without_disposition).query).to_h["sig"] + sig_with = URI.decode_www_form(URI.parse(url_with_disposition).query).to_h["sig"] + + # Signatures should be identical (disposition not affecting signature) + expect(sig_without).to eq(sig_with) + + # But the URL with disposition should include the disposition parameter + expect(url_with_disposition).to include("disposition=attachment") + expect(url_without_disposition).not_to include("disposition=") + end + end end end